From 6373507108c978d50495e47468b8200c439a1fdf Mon Sep 17 00:00:00 2001 From: Frederic Jacobs Date: Tue, 6 May 2014 19:41:08 +0200 Subject: [PATCH] initial commit --- .gitignore | 15 + CONTRIBUTING.md | 37 + Libraries/ProtocolBuffers/AbstractMessage.h | 27 + Libraries/ProtocolBuffers/AbstractMessage.m | 74 + .../ProtocolBuffers/AbstractMessage_Builder.h | 25 + .../ProtocolBuffers/AbstractMessage_Builder.m | 119 + Libraries/ProtocolBuffers/Bootstrap.h | 27 + Libraries/ProtocolBuffers/CodedInputStream.h | 184 + Libraries/ProtocolBuffers/CodedInputStream.m | 790 ++++ Libraries/ProtocolBuffers/CodedOutputStream.h | 216 + Libraries/ProtocolBuffers/CodedOutputStream.m | 974 +++++ .../ProtocolBuffers/ConcreteExtensionField.h | 62 + .../ProtocolBuffers/ConcreteExtensionField.m | 543 +++ Libraries/ProtocolBuffers/ExtendableMessage.h | 77 + Libraries/ProtocolBuffers/ExtendableMessage.m | 106 + .../ExtendableMessage_Builder.h | 75 + .../ExtendableMessage_Builder.m | 173 + Libraries/ProtocolBuffers/ExtensionField.h | 36 + Libraries/ProtocolBuffers/ExtensionRegistry.h | 83 + Libraries/ProtocolBuffers/ExtensionRegistry.m | 62 + Libraries/ProtocolBuffers/Field.h | 42 + Libraries/ProtocolBuffers/Field.m | 143 + .../ProtocolBuffers/ForwardDeclarations.h | 32 + Libraries/ProtocolBuffers/GeneratedMessage.h | 33 + Libraries/ProtocolBuffers/GeneratedMessage.m | 42 + .../GeneratedMessage_Builder.h | 30 + .../GeneratedMessage_Builder.m | 92 + Libraries/ProtocolBuffers/Message.h | 70 + Libraries/ProtocolBuffers/Message_Builder.h | 134 + .../MutableExtensionRegistry.h | 26 + .../MutableExtensionRegistry.m | 65 + Libraries/ProtocolBuffers/MutableField.h | 33 + Libraries/ProtocolBuffers/MutableField.m | 138 + Libraries/ProtocolBuffers/ProtocolBuffers.h | 36 + Libraries/ProtocolBuffers/TextFormat.h | 26 + Libraries/ProtocolBuffers/TextFormat.m | 238 + Libraries/ProtocolBuffers/UnknownFieldSet.h | 45 + Libraries/ProtocolBuffers/UnknownFieldSet.m | 161 + .../ProtocolBuffers/UnknownFieldSet_Builder.h | 51 + .../ProtocolBuffers/UnknownFieldSet_Builder.m | 237 + Libraries/ProtocolBuffers/Utilities.h | 26 + Libraries/ProtocolBuffers/Utilities.m | 79 + Libraries/ProtocolBuffers/WireFormat.h | 38 + Libraries/ProtocolBuffers/WireFormat.m | 31 + .../spandsp/spandsp.xcodeproj/project.pbxproj | 777 ++++ Libraries/spandsp/spandsp/spandsp/adsi.h | 516 +++ Libraries/spandsp/spandsp/spandsp/arctan2.h | 108 + Libraries/spandsp/spandsp/spandsp/async.h | 213 + .../spandsp/spandsp/spandsp/at_interpreter.h | 199 + Libraries/spandsp/spandsp/spandsp/awgn.h | 96 + .../spandsp/spandsp/spandsp/bell_r2_mf.h | 275 ++ Libraries/spandsp/spandsp/spandsp/bert.h | 162 + Libraries/spandsp/spandsp/spandsp/biquad.h | 117 + .../spandsp/spandsp/spandsp/bit_operations.h | 314 ++ Libraries/spandsp/spandsp/spandsp/bitstream.h | 81 + Libraries/spandsp/spandsp/spandsp/complex.h | 507 +++ .../spandsp/spandsp/spandsp/complex_filters.h | 75 + .../spandsp/spandsp/complex_vector_float.h | 172 + .../spandsp/spandsp/complex_vector_int.h | 131 + Libraries/spandsp/spandsp/spandsp/crc.h | 98 + .../spandsp/spandsp/spandsp/dc_restore.h | 93 + Libraries/spandsp/spandsp/spandsp/dds.h | 259 ++ Libraries/spandsp/spandsp/spandsp/dtmf.h | 218 + Libraries/spandsp/spandsp/spandsp/echo.h | 194 + Libraries/spandsp/spandsp/spandsp/expose.h | 90 + .../spandsp/spandsp/spandsp/fast_convert.h | 438 ++ Libraries/spandsp/spandsp/spandsp/fax.h | 133 + .../spandsp/spandsp/spandsp/fax_modems.h | 91 + Libraries/spandsp/spandsp/spandsp/fir.h | 304 ++ Libraries/spandsp/spandsp/spandsp/fsk.h | 255 ++ .../spandsp/spandsp/spandsp/g168models.h | 340 ++ Libraries/spandsp/spandsp/spandsp/g711.h | 327 ++ Libraries/spandsp/spandsp/spandsp/g722.h | 130 + Libraries/spandsp/spandsp/spandsp/g726.h | 122 + Libraries/spandsp/spandsp/spandsp/gsm0610.h | 153 + Libraries/spandsp/spandsp/spandsp/hdlc.h | 252 ++ Libraries/spandsp/spandsp/spandsp/ima_adpcm.h | 117 + Libraries/spandsp/spandsp/spandsp/logging.h | 141 + Libraries/spandsp/spandsp/spandsp/lpc10.h | 120 + .../spandsp/spandsp/modem_connect_tones.h | 177 + .../spandsp/spandsp/spandsp/modem_echo.h | 129 + Libraries/spandsp/spandsp/spandsp/noise.h | 131 + Libraries/spandsp/spandsp/spandsp/oki_adpcm.h | 104 + Libraries/spandsp/spandsp/spandsp/playout.h | 216 + Libraries/spandsp/spandsp/spandsp/plc.h | 173 + .../spandsp/spandsp/spandsp/power_meter.h | 154 + .../spandsp/spandsp/spandsp/private/README | 3 + .../spandsp/spandsp/spandsp/private/adsi.h | 120 + .../spandsp/spandsp/spandsp/private/async.h | 91 + .../spandsp/spandsp/private/at_interpreter.h | 130 + .../spandsp/spandsp/spandsp/private/awgn.h | 46 + .../spandsp/spandsp/private/bell_r2_mf.h | 104 + .../spandsp/spandsp/spandsp/private/bert.h | 92 + .../spandsp/spandsp/private/bitstream.h | 44 + .../spandsp/spandsp/spandsp/private/dtmf.h | 111 + .../spandsp/spandsp/spandsp/private/echo.h | 94 + .../spandsp/spandsp/spandsp/private/fax.h | 50 + .../spandsp/spandsp/private/fax_modems.h | 127 + .../spandsp/spandsp/spandsp/private/fsk.h | 100 + .../spandsp/spandsp/spandsp/private/g711.h | 41 + .../spandsp/spandsp/spandsp/private/g722.h | 111 + .../spandsp/spandsp/spandsp/private/g726.h | 87 + .../spandsp/spandsp/spandsp/private/gsm0610.h | 65 + .../spandsp/spandsp/spandsp/private/hdlc.h | 140 + .../spandsp/spandsp/private/ima_adpcm.h | 55 + .../spandsp/spandsp/spandsp/private/logging.h | 48 + .../spandsp/spandsp/spandsp/private/lpc10.h | 220 + .../spandsp/private/modem_connect_tones.h | 105 + .../spandsp/spandsp/private/modem_echo.h | 58 + .../spandsp/spandsp/spandsp/private/noise.h | 48 + .../spandsp/spandsp/private/oki_adpcm.h | 60 + .../spandsp/spandsp/spandsp/private/queue.h | 52 + .../spandsp/spandsp/private/schedule.h | 50 + .../spandsp/spandsp/private/sig_tone.h | 219 + .../spandsp/spandsp/private/silence_gen.h | 43 + .../spandsp/spandsp/private/super_tone_rx.h | 67 + .../spandsp/spandsp/private/super_tone_tx.h | 52 + .../spandsp/spandsp/private/swept_tone.h | 44 + .../spandsp/spandsp/spandsp/private/t30.h | 286 ++ .../spandsp/private/t30_dis_dtc_dcs_bits.h | 387 ++ .../spandsp/spandsp/spandsp/private/t31.h | 199 + .../spandsp/spandsp/private/t38_core.h | 135 + .../spandsp/spandsp/private/t38_gateway.h | 207 + .../spandsp/private/t38_non_ecm_buffer.h | 86 + .../spandsp/spandsp/private/t38_terminal.h | 120 + .../spandsp/spandsp/spandsp/private/t4_rx.h | 131 + .../spandsp/spandsp/spandsp/private/t4_tx.h | 142 + .../spandsp/spandsp/private/time_scale.h | 52 + .../spandsp/spandsp/private/tone_detect.h | 32 + .../spandsp/spandsp/private/tone_generate.h | 68 + .../spandsp/spandsp/spandsp/private/v17rx.h | 232 + .../spandsp/spandsp/spandsp/private/v17tx.h | 111 + .../spandsp/spandsp/spandsp/private/v18.h | 67 + .../spandsp/spandsp/spandsp/private/v22bis.h | 244 ++ .../spandsp/spandsp/private/v27ter_rx.h | 196 + .../spandsp/spandsp/private/v27ter_tx.h | 98 + .../spandsp/spandsp/spandsp/private/v29rx.h | 201 + .../spandsp/spandsp/spandsp/private/v29tx.h | 100 + .../spandsp/spandsp/spandsp/private/v42.h | 119 + .../spandsp/spandsp/spandsp/private/v42bis.h | 151 + .../spandsp/spandsp/spandsp/private/v8.h | 78 + Libraries/spandsp/spandsp/spandsp/queue.h | 180 + Libraries/spandsp/spandsp/spandsp/saturated.h | 224 + Libraries/spandsp/spandsp/spandsp/schedule.h | 70 + Libraries/spandsp/spandsp/spandsp/sig_tone.h | 187 + .../spandsp/spandsp/spandsp/silence_gen.h | 143 + .../spandsp/spandsp/spandsp/super_tone_rx.h | 152 + .../spandsp/spandsp/spandsp/super_tone_tx.h | 87 + .../spandsp/spandsp/spandsp/swept_tone.h | 59 + Libraries/spandsp/spandsp/spandsp/t30.h | 683 +++ Libraries/spandsp/spandsp/spandsp/t30_api.h | 547 +++ Libraries/spandsp/spandsp/spandsp/t30_fcf.h | 123 + .../spandsp/spandsp/spandsp/t30_logging.h | 63 + Libraries/spandsp/spandsp/spandsp/t31.h | 162 + Libraries/spandsp/spandsp/spandsp/t35.h | 73 + Libraries/spandsp/spandsp/spandsp/t38_core.h | 403 ++ .../spandsp/spandsp/spandsp/t38_gateway.h | 215 + .../spandsp/spandsp/t38_non_ecm_buffer.h | 136 + .../spandsp/spandsp/spandsp/t38_terminal.h | 119 + Libraries/spandsp/spandsp/spandsp/t4_rx.h | 344 ++ Libraries/spandsp/spandsp/spandsp/t4_tx.h | 179 + Libraries/spandsp/spandsp/spandsp/telephony.h | 91 + .../spandsp/spandsp/spandsp/time_scale.h | 121 + Libraries/spandsp/spandsp/spandsp/timing.h | 83 + .../spandsp/spandsp/spandsp/tone_detect.h | 248 ++ .../spandsp/spandsp/spandsp/tone_generate.h | 104 + Libraries/spandsp/spandsp/spandsp/v17rx.h | 334 ++ Libraries/spandsp/spandsp/spandsp/v17tx.h | 167 + Libraries/spandsp/spandsp/spandsp/v18.h | 156 + Libraries/spandsp/spandsp/spandsp/v22bis.h | 225 + Libraries/spandsp/spandsp/spandsp/v27ter_rx.h | 164 + Libraries/spandsp/spandsp/spandsp/v27ter_tx.h | 148 + Libraries/spandsp/spandsp/spandsp/v29rx.h | 243 ++ Libraries/spandsp/spandsp/spandsp/v29tx.h | 179 + Libraries/spandsp/spandsp/spandsp/v42.h | 160 + Libraries/spandsp/spandsp/spandsp/v42bis.h | 143 + Libraries/spandsp/spandsp/spandsp/v8.h | 190 + .../spandsp/spandsp/spandsp/vector_float.h | 197 + .../spandsp/spandsp/spandsp/vector_int.h | 180 + Libraries/spandsp/spandsp/spandsp/version.h | 38 + .../spandsp/spandsp/spandsp/version.h.in | 38 + Libraries/spandsp/spandsp/time_scale.c | 300 ++ Libraries/speex/_kiss_fft_guts.h | 160 + Libraries/speex/arch.h | 239 ++ Libraries/speex/bits.c | 373 ++ Libraries/speex/buffer.c | 176 + Libraries/speex/cb_search.c | 612 +++ Libraries/speex/cb_search.h | 103 + Libraries/speex/cb_search_arm4.h | 137 + Libraries/speex/cb_search_bfin.h | 112 + Libraries/speex/cb_search_sse.h | 84 + Libraries/speex/exc_10_16_table.c | 50 + Libraries/speex/exc_10_32_table.c | 66 + Libraries/speex/exc_20_32_table.c | 66 + Libraries/speex/exc_5_256_table.c | 290 ++ Libraries/speex/exc_5_64_table.c | 98 + Libraries/speex/exc_8_128_table.c | 162 + Libraries/speex/fftwrap.c | 397 ++ Libraries/speex/fftwrap.h | 58 + Libraries/speex/filterbank.c | 227 + Libraries/speex/filterbank.h | 66 + Libraries/speex/filters.c | 821 ++++ Libraries/speex/filters.h | 90 + Libraries/speex/filters_arm4.h | 96 + Libraries/speex/filters_bfin.h | 515 +++ Libraries/speex/filters_sse.h | 336 ++ Libraries/speex/fixed_arm4.h | 148 + Libraries/speex/fixed_arm5e.h | 178 + Libraries/speex/fixed_bfin.h | 173 + Libraries/speex/fixed_debug.h | 487 +++ Libraries/speex/fixed_generic.h | 106 + Libraries/speex/gain_table.c | 160 + Libraries/speex/gain_table_lbr.c | 64 + Libraries/speex/hexc_10_32_table.c | 66 + Libraries/speex/hexc_table.c | 162 + Libraries/speex/high_lsp_tables.c | 163 + Libraries/speex/include/bitwise.c | 857 ++++ Libraries/speex/include/config.h | 167 + Libraries/speex/include/framing.c | 2093 +++++++++ Libraries/speex/include/ogg/config_types.h | 25 + Libraries/speex/include/ogg/ogg.h | 210 + Libraries/speex/include/ogg/os_types.h | 147 + Libraries/speex/include/speex/Makefile | 440 ++ Libraries/speex/include/speex/Makefile.am | 9 + Libraries/speex/include/speex/Makefile.in | 440 ++ Libraries/speex/include/speex/speex.h | 424 ++ Libraries/speex/include/speex/speex_bits.h | 174 + Libraries/speex/include/speex/speex_buffer.h | 68 + .../speex/include/speex/speex_callbacks.h | 134 + .../speex/include/speex/speex_config_types.h | 11 + .../include/speex/speex_config_types.h.in | 11 + Libraries/speex/include/speex/speex_echo.h | 170 + Libraries/speex/include/speex/speex_header.h | 94 + Libraries/speex/include/speex/speex_jitter.h | 197 + .../speex/include/speex/speex_preprocess.h | 219 + .../speex/include/speex/speex_resampler.h | 340 ++ Libraries/speex/include/speex/speex_stereo.h | 91 + Libraries/speex/include/speex/speex_types.h | 126 + Libraries/speex/jitter.c | 843 ++++ Libraries/speex/kiss_fft.c | 523 +++ Libraries/speex/kiss_fft.h | 108 + Libraries/speex/kiss_fftr.c | 297 ++ Libraries/speex/kiss_fftr.h | 51 + Libraries/speex/lpc.c | 201 + Libraries/speex/lpc.h | 53 + Libraries/speex/lpc_bfin.h | 131 + Libraries/speex/lsp.c | 656 +++ Libraries/speex/lsp.h | 64 + Libraries/speex/lsp_bfin.h | 89 + Libraries/speex/lsp_tables_nb.c | 360 ++ Libraries/speex/ltp.c | 839 ++++ Libraries/speex/ltp.h | 141 + Libraries/speex/ltp_arm4.h | 187 + Libraries/speex/ltp_bfin.h | 419 ++ Libraries/speex/ltp_sse.h | 92 + Libraries/speex/math_approx.h | 332 ++ Libraries/speex/mdf.c | 1285 ++++++ Libraries/speex/misc_bfin.h | 54 + Libraries/speex/modes.c | 366 ++ Libraries/speex/modes.h | 161 + Libraries/speex/modes_wb.c | 300 ++ Libraries/speex/nb_celp.c | 1903 ++++++++ Libraries/speex/nb_celp.h | 203 + Libraries/speex/os_support.h | 169 + Libraries/speex/preprocess.c | 1219 ++++++ Libraries/speex/pseudofloat.h | 379 ++ Libraries/speex/quant_lsp.c | 385 ++ Libraries/speex/quant_lsp.h | 74 + Libraries/speex/quant_lsp_bfin.h | 165 + Libraries/speex/resample.c | 1131 +++++ Libraries/speex/resample_sse.h | 128 + Libraries/speex/sb_celp.c | 1488 +++++++ Libraries/speex/sb_celp.h | 155 + Libraries/speex/scal.c | 289 ++ Libraries/speex/smallft.c | 1261 ++++++ Libraries/speex/smallft.h | 46 + Libraries/speex/speex.c | 250 ++ .../speex/speex.xcodeproj/project.pbxproj | 670 +++ Libraries/speex/speex_callbacks.c | 144 + Libraries/speex/speex_header.c | 200 + Libraries/speex/stack_alloc.h | 115 + Libraries/speex/stereo.c | 296 ++ Libraries/speex/testdenoise.c | 44 + Libraries/speex/testecho.c | 53 + Libraries/speex/testenc.c | 146 + Libraries/speex/testenc_uwb.c | 137 + Libraries/speex/testenc_wb.c | 140 + Libraries/speex/testjitter.c | 75 + Libraries/speex/vbr.c | 275 ++ Libraries/speex/vbr.h | 70 + Libraries/speex/vorbis_psy.h | 97 + Libraries/speex/vq.c | 147 + Libraries/speex/vq.h | 54 + Libraries/speex/vq_arm4.h | 115 + Libraries/speex/vq_bfin.h | 107 + Libraries/speex/vq_sse.h | 120 + Libraries/speex/window.c | 102 + Podfile | 7 + README.md | 43 + .../Settings.bundle/Acknowledgements.plist | 929 ++++ SettingsBundle/Settings.bundle/Root.plist | 19 + .../en.lproj/Acknowledgements.strings | 153 + .../Settings.bundle/en.lproj/Root.strings | Bin 0 -> 546 bytes .../licenses/MMDrawerController.license | 22 + SettingsBundle/licenses/OpenSSL.license | 124 + SettingsBundle/licenses/ProtoBuffers.license | 283 ++ SettingsBundle/licenses/Spandsp.license | 461 ++ SettingsBundle/licenses/Speex.license | 9 + SettingsBundle/licenses/compileLicences.pl | 57 + .../licenses/libPhoneNumber-iOS.license | 180 + Signal.xcodeproj/project.pbxproj | 3810 +++++++++++++++++ ...171756__nenadsimic__picked-coin-echo-2.wav | Bin 0 -> 454926 bytes Signal/AudioFiles/busy.mp3 | Bin 0 -> 169612 bytes Signal/AudioFiles/completed.mp3 | Bin 0 -> 18390 bytes Signal/AudioFiles/failure.mp3 | Bin 0 -> 7941 bytes Signal/AudioFiles/handshake.mp3 | Bin 0 -> 34690 bytes Signal/AudioFiles/outring.mp3 | Bin 0 -> 97200 bytes Signal/AudioFiles/r.caf | Bin 0 -> 657740 bytes Signal/AudioFiles/sonarping.mp3 | Bin 0 -> 59350 bytes Signal/Default-568h@2x.png | Bin 0 -> 18594 bytes Signal/Default.png | Bin 0 -> 6540 bytes Signal/Default@2x.png | Bin 0 -> 16107 bytes Signal/Fonts/HelveticaNeueLTStd-Bd.otf | Bin 0 -> 20816 bytes Signal/Fonts/HelveticaNeueLTStd-Lt.otf | Bin 0 -> 28120 bytes Signal/Fonts/HelveticaNeueLTStd-Md.otf | Bin 0 -> 28260 bytes Signal/Fonts/HelveticaNeueLTStd-Th.otf | Bin 0 -> 28124 bytes Signal/Icons/AppIcon29x29.jpg | Bin 0 -> 7042 bytes Signal/Icons/AppIcon29x29.png | Bin 0 -> 6170 bytes Signal/Icons/AppIcon29x29@2x.png | Bin 0 -> 7685 bytes Signal/Icons/AppIcon40x40.png | Bin 0 -> 6718 bytes Signal/Icons/AppIcon40x40@2x.png | Bin 0 -> 8974 bytes Signal/Icons/AppIcon60x60.png | Bin 0 -> 7638 bytes Signal/Icons/AppIcon60x60@2x.png | Bin 0 -> 11664 bytes Signal/Icons/AppIcon76x76.png | Bin 0 -> 8608 bytes Signal/Icons/AppIcon76x76@2x.png | Bin 0 -> 13986 bytes Signal/Images/DefaultContactImage.png | Bin 0 -> 258589 bytes Signal/Images/archive_icon.png | Bin 0 -> 3094 bytes Signal/Images/archive_icon@2x.png | Bin 0 -> 505 bytes Signal/Images/backspace.png | Bin 0 -> 3193 bytes Signal/Images/backspace@2x.png | Bin 0 -> 713 bytes Signal/Images/checkbox_checkmark.png | Bin 0 -> 3512 bytes Signal/Images/checkbox_checkmark@2x.png | Bin 0 -> 599 bytes Signal/Images/checkbox_empty.png | Bin 0 -> 2909 bytes Signal/Images/checkbox_empty@2x.png | Bin 0 -> 339 bytes Signal/Images/contact_default_feed.png | Bin 0 -> 18662 bytes Signal/Images/contacts_arrow.png | Bin 0 -> 5631 bytes Signal/Images/contacts_arrow@2x.png | Bin 0 -> 3809 bytes Signal/Images/dismiss_notification_icon.png | Bin 0 -> 3287 bytes .../Images/dismiss_notification_icon@2x.png | Bin 0 -> 718 bytes Signal/Images/drop_down_arrow_icon.png | Bin 0 -> 2908 bytes Signal/Images/drop_down_arrow_icon@2x.png | Bin 0 -> 2908 bytes Signal/Images/expanded_cell_icon.png | Bin 0 -> 2872 bytes Signal/Images/expanded_cell_icon@2x.png | Bin 0 -> 226 bytes Signal/Images/favourite_false_icon.png | Bin 0 -> 3168 bytes Signal/Images/favourite_false_icon@2x.png | Bin 0 -> 697 bytes Signal/Images/favourite_true_icon.png | Bin 0 -> 3188 bytes Signal/Images/favourite_true_icon@2x.png | Bin 0 -> 692 bytes Signal/Images/forward_button.png | Bin 0 -> 3068 bytes Signal/Images/forward_button@2x.png | Bin 0 -> 462 bytes Signal/Images/home_icon.png | Bin 0 -> 5332 bytes Signal/Images/icon_contacts.png | Bin 0 -> 8870 bytes Signal/Images/icon_favourites.png | Bin 0 -> 6780 bytes Signal/Images/icon_keypad.png | Bin 0 -> 6662 bytes Signal/Images/icon_recents.png | Bin 0 -> 6764 bytes Signal/Images/in_call_phone_icon.png | Bin 0 -> 3001 bytes Signal/Images/in_call_phone_icon@2x.png | Bin 0 -> 335 bytes Signal/Images/in_call_phrase_icon.png | Bin 0 -> 3420 bytes Signal/Images/in_call_phrase_icon@2x.png | Bin 0 -> 906 bytes Signal/Images/incoming_call_icon.png | Bin 0 -> 3215 bytes Signal/Images/incoming_call_icon@2x.png | Bin 0 -> 557 bytes Signal/Images/menu_icon.png | Bin 0 -> 2957 bytes Signal/Images/menu_icon@2x.png | Bin 0 -> 256 bytes Signal/Images/message_bubble.png | Bin 0 -> 3217 bytes Signal/Images/message_bubble@2x.png | Bin 0 -> 757 bytes Signal/Images/message_icon.png | Bin 0 -> 5522 bytes Signal/Images/mute_icon.png | Bin 0 -> 2889 bytes Signal/Images/mute_icon@2x.png | Bin 0 -> 171 bytes Signal/Images/mute_icon_selected.png | Bin 0 -> 2889 bytes Signal/Images/mute_icon_selected@2x.png | Bin 0 -> 2877 bytes Signal/Images/notification_detail_icon.png | Bin 0 -> 2890 bytes Signal/Images/notification_detail_icon@2x.png | Bin 0 -> 299 bytes Signal/Images/notification_mini_icon.png | Bin 0 -> 3742 bytes Signal/Images/notification_mini_icon@2x.png | Bin 0 -> 1692 bytes Signal/Images/outgoing_call_icon.png | Bin 0 -> 3243 bytes Signal/Images/outgoing_call_icon@2x.png | Bin 0 -> 3326 bytes Signal/Images/phone_icon.png | Bin 0 -> 6172 bytes Signal/Images/search_cancel.png | Bin 0 -> 3239 bytes Signal/Images/search_cancel@2x.png | Bin 0 -> 3360 bytes Signal/Images/search_icon.png | Bin 0 -> 3250 bytes Signal/Images/search_icon@2x.png | Bin 0 -> 668 bytes Signal/Images/send_code_icon.png | Bin 0 -> 2914 bytes Signal/Images/send_code_icon@2x.png | Bin 0 -> 2984 bytes Signal/Images/speaker_icon.png | Bin 0 -> 3208 bytes Signal/Images/speaker_icon@2x.png | Bin 0 -> 837 bytes Signal/Images/speaker_icon_selected.png | Bin 0 -> 3189 bytes Signal/Images/speaker_icon_selected@2x.png | Bin 0 -> 3057 bytes Signal/Images/spinner_connecting.png | Bin 0 -> 3686 bytes Signal/Images/spinner_connecting@2x.png | Bin 0 -> 3851 bytes Signal/Images/spinner_connecting_flash.png | Bin 0 -> 3721 bytes Signal/Images/spinner_connecting_flash@2x.png | Bin 0 -> 590 bytes Signal/Images/spinner_error.png | Bin 0 -> 3704 bytes Signal/Images/spinner_error@2x.png | Bin 0 -> 521 bytes Signal/Images/spinner_ringing.png | Bin 0 -> 3721 bytes Signal/Images/spinner_ringing@2x.png | Bin 0 -> 3941 bytes Signal/Images/tab_icon_contacts.png | Bin 0 -> 3241 bytes Signal/Images/tab_icon_contacts@2x.png | Bin 0 -> 473 bytes Signal/Images/tab_icon_favourites.png | Bin 0 -> 3208 bytes Signal/Images/tab_icon_favourites@2x.png | Bin 0 -> 626 bytes Signal/Images/tab_icon_inbox.png | Bin 0 -> 3459 bytes Signal/Images/tab_icon_inbox@2x.png | Bin 0 -> 868 bytes Signal/Images/tab_icon_keypad.png | Bin 0 -> 3226 bytes Signal/Images/tab_icon_keypad@2x.png | Bin 0 -> 678 bytes Signal/Images/tab_icon_menu.png | Bin 0 -> 3213 bytes Signal/Images/tab_icon_menu@2x.png | Bin 0 -> 578 bytes Signal/Images/trash_icon.png | Bin 0 -> 3119 bytes Signal/Images/trash_icon@2x.png | Bin 0 -> 424 bytes Signal/Images/volume_high.png | Bin 0 -> 3121 bytes Signal/Images/volume_high@2x.png | Bin 0 -> 467 bytes Signal/Images/volume_low.png | Bin 0 -> 2896 bytes Signal/Images/volume_low@2x.png | Bin 0 -> 194 bytes Signal/Images/whisper_notification_icon.png | Bin 0 -> 3137 bytes .../Images/whisper_notification_icon@2x.png | Bin 0 -> 403 bytes Signal/Signal-Info.plist | 64 + Signal/Signal-Prefix.pch | 10 + Signal/Signal.entitlements | 5 + Signal/iTunesArtwork.png | Bin 0 -> 44263 bytes Signal/iTunesArtwork@2x.png | Bin 0 -> 66142 bytes Signal/main.m | 10 + Signal/src/AppDelegate.h | 8 + Signal/src/AppDelegate.m | 150 + Signal/src/NotificationManifest.h | 16 + Signal/src/NotificationTracker.h | 12 + Signal/src/NotificationTracker.m | 50 + Signal/src/async/AsyncUtil.h | 39 + Signal/src/async/AsyncUtil.m | 102 + .../async/AsyncUtilHelperRacingOperation.h | 21 + .../async/AsyncUtilHelperRacingOperation.m | 76 + Signal/src/async/CancelTokenSource.h | 19 + Signal/src/async/CancelTokenSource.m | 114 + Signal/src/async/CancelledToken.h | 11 + Signal/src/async/CancelledToken.m | 29 + Signal/src/async/Future.h | 48 + Signal/src/async/Future.m | 77 + Signal/src/async/FutureSource.h | 21 + Signal/src/async/FutureSource.m | 103 + Signal/src/async/FutureUtil.h | 15 + Signal/src/async/FutureUtil.m | 75 + Signal/src/async/ObservableValue.h | 37 + Signal/src/async/ObservableValue.m | 125 + Signal/src/async/TimeoutFailure.h | 10 + Signal/src/async/TimeoutFailure.m | 8 + Signal/src/async/protocols/CancelToken.h | 33 + Signal/src/audio/AppAudioManager.h | 40 + Signal/src/audio/AppAudioManager.m | 184 + Signal/src/audio/AudioRouter.h | 16 + Signal/src/audio/AudioRouter.m | 49 + Signal/src/audio/SoundBoard.h | 21 + Signal/src/audio/SoundBoard.m | 54 + Signal/src/audio/SoundInstance.h | 15 + Signal/src/audio/SoundInstance.m | 61 + Signal/src/audio/SoundPlayer.h | 18 + Signal/src/audio/SoundPlayer.m | 68 + Signal/src/audio/incall_audio/AudioPacker.h | 34 + Signal/src/audio/incall_audio/AudioPacker.m | 72 + Signal/src/audio/incall_audio/AudioSocket.h | 20 + Signal/src/audio/incall_audio/AudioSocket.m | 38 + .../src/audio/incall_audio/CallAudioManager.h | 28 + .../src/audio/incall_audio/CallAudioManager.m | 70 + .../audio/incall_audio/EncodedAudioFrame.h | 20 + .../audio/incall_audio/EncodedAudioFrame.m | 23 + .../audio/incall_audio/EncodedAudioPacket.h | 19 + .../audio/incall_audio/EncodedAudioPacket.m | 16 + Signal/src/audio/incall_audio/RemoteIOAudio.h | 54 + Signal/src/audio/incall_audio/RemoteIOAudio.m | 344 ++ .../incall_audio/RemoteIOBufferListWrapper.h | 18 + .../incall_audio/RemoteIOBufferListWrapper.m | 22 + Signal/src/audio/incall_audio/SpeexCodec.h | 37 + Signal/src/audio/incall_audio/SpeexCodec.m | 168 + .../incall_audio/processing/AudioProcessor.h | 42 + .../incall_audio/processing/AudioProcessor.m | 65 + .../incall_audio/processing/AudioStretcher.h | 21 + .../incall_audio/processing/AudioStretcher.m | 53 + .../processing/DesiredBufferDepthController.h | 28 + .../processing/DesiredBufferDepthController.m | 65 + .../incall_audio/processing/DropoutTracker.h | 50 + .../incall_audio/processing/DropoutTracker.m | 91 + .../incall_audio/processing/JitterQueue.h | 38 + .../incall_audio/processing/JitterQueue.m | 172 + .../processing/StretchFactorController.h | 26 + .../processing/StretchFactorController.m | 73 + .../protocols/AudioCallbackHandler.h | 12 + .../protocols/BufferDepthMeasure.h | 5 + .../JitterQueueNotificationReceiver.h | 20 + .../utilities/AnonymousAudioCallbackHandler.h | 18 + .../utilities/AnonymousAudioCallbackHandler.m | 21 + Signal/src/call/RecentCall.h | 38 + Signal/src/call/RecentCall.m | 59 + Signal/src/call/RecentCallManager.h | 34 + Signal/src/call/RecentCallManager.m | 186 + Signal/src/contact/Contact.h | 40 + Signal/src/contact/Contact.m | 84 + Signal/src/contact/ContactsManager.h | 57 + Signal/src/contact/ContactsManager.m | 521 +++ Signal/src/crypto/CryptoTools.h | 40 + Signal/src/crypto/CryptoTools.m | 75 + Signal/src/crypto/EvpMessageDigest.h | 10 + Signal/src/crypto/EvpMessageDigest.m | 52 + Signal/src/crypto/EvpSymetricUtil.h | 16 + Signal/src/crypto/EvpSymetricUtil.m | 99 + Signal/src/crypto/EvpUtil.h | 4 + Signal/src/environment/Environment.h | 77 + Signal/src/environment/Environment.m | 146 + Signal/src/environment/KeyChainStorage.h | 24 + Signal/src/environment/KeyChainStorage.m | 105 + Signal/src/environment/LocalizableText.h | 139 + Signal/src/environment/LocalizableText.m | 39 + Signal/src/environment/PreferencesUtil.h | 38 + Signal/src/environment/PreferencesUtil.m | 193 + .../src/environment/PropertyListPreferences.h | 15 + .../src/environment/PropertyListPreferences.m | 75 + Signal/src/environment/Release.h | 22 + Signal/src/environment/Release.m | 149 + Signal/src/network/IpAddress.h | 34 + Signal/src/network/IpAddress.m | 138 + Signal/src/network/IpEndPoint.h | 34 + Signal/src/network/IpEndPoint.m | 97 + Signal/src/network/NetworkEndPoint.h | 25 + Signal/src/network/PacketHandler.h | 28 + Signal/src/network/PacketHandler.m | 31 + Signal/src/network/dns/DnsManager.h | 21 + Signal/src/network/dns/DnsManager.m | 78 + Signal/src/network/dns/HostNameEndPoint.h | 17 + Signal/src/network/dns/HostNameEndPoint.m | 50 + .../network/failures/IgnoredPacketFailure.h | 14 + .../network/failures/IgnoredPacketFailure.m | 14 + .../failures/UnrecognizedRequestFailure.h | 14 + .../failures/UnrecognizedRequestFailure.m | 14 + Signal/src/network/http/HttpManager.h | 50 + Signal/src/network/http/HttpManager.m | 164 + Signal/src/network/http/HttpRequest.h | 45 + Signal/src/network/http/HttpRequest.m | 163 + .../src/network/http/HttpRequestOrResponse.h | 18 + .../src/network/http/HttpRequestOrResponse.m | 107 + Signal/src/network/http/HttpRequestUtil.h | 18 + Signal/src/network/http/HttpRequestUtil.m | 47 + Signal/src/network/http/HttpResponse.h | 34 + Signal/src/network/http/HttpResponse.m | 134 + Signal/src/network/http/HttpSocket.h | 32 + Signal/src/network/http/HttpSocket.m | 101 + Signal/src/network/rtp/RtpPacket.h | 64 + Signal/src/network/rtp/RtpPacket.m | 369 ++ Signal/src/network/rtp/RtpSocket.h | 28 + Signal/src/network/rtp/RtpSocket.m | 72 + Signal/src/network/rtp/srtp/SequenceCounter.h | 19 + Signal/src/network/rtp/srtp/SequenceCounter.m | 19 + Signal/src/network/rtp/srtp/SrtpSocket.h | 31 + Signal/src/network/rtp/srtp/SrtpSocket.m | 67 + Signal/src/network/rtp/srtp/SrtpStream.h | 22 + Signal/src/network/rtp/srtp/SrtpStream.m | 73 + Signal/src/network/rtp/zrtp/HashChain.h | 30 + Signal/src/network/rtp/zrtp/HashChain.m | 23 + Signal/src/network/rtp/zrtp/MasterSecret.h | 57 + Signal/src/network/rtp/zrtp/MasterSecret.m | 168 + .../src/network/rtp/zrtp/NegotiationFailed.h | 9 + .../src/network/rtp/zrtp/NegotiationFailed.m | 17 + .../network/rtp/zrtp/RecipientUnavailable.h | 7 + .../network/rtp/zrtp/RecipientUnavailable.m | 13 + .../zrtp/ShortAuthenticationStringGenerator.h | 14 + .../zrtp/ShortAuthenticationStringGenerator.m | 91 + .../network/rtp/zrtp/ZrtpHandshakeResult.h | 17 + .../network/rtp/zrtp/ZrtpHandshakeResult.m | 17 + .../network/rtp/zrtp/ZrtpHandshakeSocket.h | 23 + .../network/rtp/zrtp/ZrtpHandshakeSocket.m | 50 + Signal/src/network/rtp/zrtp/ZrtpInitiator.h | 38 + Signal/src/network/rtp/zrtp/ZrtpInitiator.m | 181 + Signal/src/network/rtp/zrtp/ZrtpManager.h | 56 + Signal/src/network/rtp/zrtp/ZrtpManager.m | 188 + Signal/src/network/rtp/zrtp/ZrtpResponder.h | 35 + Signal/src/network/rtp/zrtp/ZrtpResponder.m | 161 + .../agreement/DH3KKeyAgreementParticipant.h | 23 + .../agreement/DH3KKeyAgreementParticipant.m | 27 + .../zrtp/agreement/DH3KKeyAgreementProtocol.h | 22 + .../zrtp/agreement/DH3KKeyAgreementProtocol.m | 33 + .../agreement/EC25KeyAgreementParticipant.h | 14 + .../agreement/EC25KeyAgreementParticipant.m | 29 + .../zrtp/agreement/EC25KeyAgreementProtocol.h | 13 + .../zrtp/agreement/EC25KeyAgreementProtocol.m | 15 + .../rtp/zrtp/agreement/EvpKeyAgreement.h | 16 + .../rtp/zrtp/agreement/EvpKeyAgreement.m | 330 ++ .../network/rtp/zrtp/packets/CommitPacket.h | 87 + .../network/rtp/zrtp/packets/CommitPacket.m | 180 + .../rtp/zrtp/packets/ConfirmAckPacket.h | 28 + .../rtp/zrtp/packets/ConfirmAckPacket.m | 24 + .../network/rtp/zrtp/packets/ConfirmPacket.h | 78 + .../network/rtp/zrtp/packets/ConfirmPacket.m | 165 + .../src/network/rtp/zrtp/packets/DhPacket.h | 74 + .../src/network/rtp/zrtp/packets/DhPacket.m | 128 + .../zrtp/packets/DhPacketSharedSecretHashes.h | 32 + .../zrtp/packets/DhPacketSharedSecretHashes.m | 38 + .../rtp/zrtp/packets/HandshakePacket.h | 78 + .../rtp/zrtp/packets/HandshakePacket.m | 187 + .../network/rtp/zrtp/packets/HelloAckPacket.h | 27 + .../network/rtp/zrtp/packets/HelloAckPacket.m | 24 + .../network/rtp/zrtp/packets/HelloPacket.h | 90 + .../network/rtp/zrtp/packets/HelloPacket.m | 250 ++ .../zrtp/protocols/KeyAgreementParticipant.h | 18 + .../rtp/zrtp/protocols/KeyAgreementProtocol.h | 17 + .../src/network/rtp/zrtp/protocols/ZrtpRole.h | 46 + Signal/src/network/tcp/LowLatencyCandidate.h | 19 + Signal/src/network/tcp/LowLatencyCandidate.m | 46 + Signal/src/network/tcp/LowLatencyConnector.h | 21 + Signal/src/network/tcp/LowLatencyConnector.m | 52 + Signal/src/network/tcp/StreamPair.h | 15 + Signal/src/network/tcp/StreamPair.m | 20 + Signal/src/network/tcp/tls/Certificate.h | 20 + Signal/src/network/tcp/tls/Certificate.m | 60 + Signal/src/network/tcp/tls/NetworkStream.h | 45 + Signal/src/network/tcp/tls/NetworkStream.m | 240 ++ Signal/src/network/tcp/tls/SecureEndPoint.h | 24 + Signal/src/network/tcp/tls/SecureEndPoint.m | 115 + Signal/src/network/udp/UdpSocket.h | 43 + Signal/src/network/udp/UdpSocket.m | 198 + Signal/src/phone/PhoneManager.h | 39 + Signal/src/phone/PhoneManager.m | 141 + Signal/src/phone/PhoneNumber.h | 39 + Signal/src/phone/PhoneNumber.m | 174 + Signal/src/phone/callstate/CallController.h | 55 + Signal/src/phone/callstate/CallController.m | 155 + .../phone/callstate/CallFailedServerMessage.h | 9 + .../phone/callstate/CallFailedServerMessage.m | 16 + Signal/src/phone/callstate/CallProgress.h | 49 + Signal/src/phone/callstate/CallProgress.m | 31 + Signal/src/phone/callstate/CallState.h | 37 + Signal/src/phone/callstate/CallState.m | 39 + Signal/src/phone/callstate/CallTermination.h | 47 + Signal/src/phone/callstate/CallTermination.m | 38 + .../src/phone/signaling/CallConnectResult.h | 18 + .../src/phone/signaling/CallConnectResult.m | 18 + Signal/src/phone/signaling/CallConnectUtil.h | 29 + Signal/src/phone/signaling/CallConnectUtil.m | 30 + .../signaling/CallConnectUtil_Initiator.h | 24 + .../signaling/CallConnectUtil_Initiator.m | 145 + .../signaling/CallConnectUtil_Responder.h | 26 + .../signaling/CallConnectUtil_Responder.m | 154 + .../phone/signaling/CallConnectUtil_Server.h | 29 + .../phone/signaling/CallConnectUtil_Server.m | 203 + .../src/phone/signaling/InitiateSignal.pb.h | 97 + .../src/phone/signaling/InitiateSignal.pb.m | 350 ++ .../src/phone/signaling/InitiateSignal.proto | 9 + .../signaling/InitiatorSessionDescriptor.h | 24 + .../signaling/InitiatorSessionDescriptor.m | 57 + .../signaling/ResponderSessionDescriptor.h | 28 + .../signaling/ResponderSessionDescriptor.m | 114 + Signal/src/phone/signaling/SignalUtil.h | 39 + Signal/src/phone/signaling/SignalUtil.m | 110 + .../PhoneNumberDirectoryFilter.h | 26 + .../PhoneNumberDirectoryFilter.m | 65 + .../PhoneNumberDirectoryFilterManager.h | 20 + .../PhoneNumberDirectoryFilterManager.m | 112 + Signal/src/profiling/CategorizingLogger.h | 13 + Signal/src/profiling/CategorizingLogger.m | 78 + .../src/profiling/DecayingSampleEstimator.h | 21 + .../src/profiling/DecayingSampleEstimator.m | 44 + Signal/src/profiling/EventWindow.h | 14 + Signal/src/profiling/EventWindow.m | 36 + Signal/src/profiling/LoggingUtil.h | 15 + Signal/src/profiling/LoggingUtil.m | 81 + .../src/profiling/protocols/ConditionLogger.h | 7 + Signal/src/profiling/protocols/Logging.h | 20 + .../profiling/protocols/OccurrenceLogger.h | 5 + Signal/src/profiling/protocols/ValueLogger.h | 7 + .../utilities/AnonymousConditionLogger.h | 12 + .../utilities/AnonymousConditionLogger.m | 28 + .../utilities/AnonymousOccurrenceLogger.h | 10 + .../utilities/AnonymousOccurrenceLogger.m | 17 + .../utilities/AnonymousValueLogger.h | 10 + .../utilities/AnonymousValueLogger.m | 17 + .../protocols/utilities/DiscardingLog.h | 8 + .../protocols/utilities/DiscardingLog.m | 44 + Signal/src/storage/KeychainWrapper.h | 24 + Signal/src/storage/KeychainWrapper.m | 100 + Signal/src/util/ArrayUtil.h | 7 + Signal/src/util/ArrayUtil.m | 38 + Signal/src/util/BloomFilter.h | 25 + Signal/src/util/BloomFilter.m | 66 + Signal/src/util/Conversions.h | 12 + Signal/src/util/Conversions.m | 44 + Signal/src/util/Crc32.h | 7 + Signal/src/util/Crc32.m | 44 + Signal/src/util/DataUtil.h | 77 + Signal/src/util/DataUtil.m | 191 + Signal/src/util/DateUtil.h | 11 + Signal/src/util/DateUtil.m | 39 + Signal/src/util/DictionaryUtil.h | 7 + Signal/src/util/DictionaryUtil.m | 16 + Signal/src/util/FunctionalUtil.h | 32 + Signal/src/util/FunctionalUtil.m | 103 + Signal/src/util/NumberUtil.h | 43 + Signal/src/util/NumberUtil.m | 95 + Signal/src/util/Operation.h | 40 + Signal/src/util/Operation.m | 85 + Signal/src/util/PhoneNumberUtil.h | 9 + Signal/src/util/PhoneNumberUtil.m | 40 + Signal/src/util/SmsInvite.h | 13 + Signal/src/util/SmsInvite.m | 36 + Signal/src/util/StringUtil.h | 21 + Signal/src/util/StringUtil.m | 144 + Signal/src/util/ThreadManager.h | 50 + Signal/src/util/ThreadManager.m | 86 + Signal/src/util/TimeUtil.h | 39 + Signal/src/util/TimeUtil.m | 154 + Signal/src/util/UIUtil.h | 26 + Signal/src/util/UIUtil.m | 73 + Signal/src/util/Util.h | 15 + Signal/src/util/Zid.h | 8 + Signal/src/util/Zid.m | 15 + Signal/src/util/collections/CyclicalBuffer.h | 48 + Signal/src/util/collections/CyclicalBuffer.m | 99 + Signal/src/util/collections/PriorityQueue.h | 15 + Signal/src/util/collections/PriorityQueue.m | 66 + Signal/src/util/collections/Queue.h | 10 + Signal/src/util/collections/Queue.m | 37 + Signal/src/util/constraints/BadArgument.h | 6 + Signal/src/util/constraints/BadArgument.m | 10 + Signal/src/util/constraints/BadState.h | 5 + Signal/src/util/constraints/BadState.m | 7 + Signal/src/util/constraints/Constraints.h | 27 + Signal/src/util/constraints/OperationFailed.h | 6 + Signal/src/util/constraints/OperationFailed.m | 10 + Signal/src/util/constraints/SecurityFailure.h | 7 + Signal/src/util/constraints/SecurityFailure.m | 10 + Signal/src/util/protocols/Terminable.h | 7 + .../protocols/utilities/AnonymousTerminator.h | 11 + .../protocols/utilities/AnonymousTerminator.m | 20 + .../view controllers/CallLogViewController.h | 12 + .../view controllers/CallLogViewController.m | 178 + .../ContactBrowseViewController.h | 22 + .../ContactBrowseViewController.m | 269 ++ .../ContactDetailViewController.h | 28 + .../ContactDetailViewController.m | 163 + .../CountryCodeViewController.h | 23 + .../CountryCodeViewController.m | 74 + .../CountryCodeViewController.xib | 66 + .../view controllers/DialerViewController.h | 34 + .../view controllers/DialerViewController.m | 194 + .../FavouritesViewController.h | 16 + .../FavouritesViewController.m | 180 + .../view controllers/InCallViewController.h | 40 + .../view controllers/InCallViewController.m | 304 ++ .../InboxFeedViewController.h | 27 + .../InboxFeedViewController.m | 423 ++ .../src/view controllers/InviteContactModal.h | 10 + .../src/view controllers/InviteContactModal.m | 44 + .../InviteContactsViewController.h | 15 + .../InviteContactsViewController.m | 265 ++ .../InviteContactsViewController.xib | 158 + .../LeftSideMenuViewController.h | 26 + .../LeftSideMenuViewController.m | 201 + .../NextResponderScrollView.h | 12 + .../NextResponderScrollView.m | 26 + .../PreferenceListViewController.h | 26 + .../PreferenceListViewController.m | 67 + .../PreferenceListViewController.xib | 33 + .../view controllers/RegisterViewController.h | 43 + .../view controllers/RegisterViewController.m | 405 ++ .../view controllers/SettingsViewController.h | 57 + .../view controllers/SettingsViewController.m | 313 ++ .../TabBarParentViewController.h | 37 + .../TabBarParentViewController.m | 211 + .../TabBarParentViewController.xib | 167 + .../xibs/CallAudioManagerDemo.xib | 539 +++ .../xibs/CallLogViewController.xib | 106 + .../xibs/ContactBrowseViewController.xib | 149 + .../xibs/ContactDetailTableViewCell.xib | 42 + .../xibs/ContactDetailViewController.xib | 68 + .../xibs/DialerViewController.xib | 938 ++++ .../xibs/DowngradeCallViewController.xib | 63 + .../xibs/FavouritesViewController.xib | 120 + .../xibs/InCallViewController.xib | 287 ++ .../xibs/InboxFeedViewController.xib | 146 + .../xibs/LeftSideMenuViewController.xib | 57 + .../xibs/RegisterViewController.xib | 305 ++ .../xibs/SettingsViewController.xib | 567 +++ Signal/src/views/CallLogTableViewCell.h | 36 + Signal/src/views/CallLogTableViewCell.m | 98 + Signal/src/views/ContactDetailTableViewCell.h | 21 + Signal/src/views/ContactDetailTableViewCell.m | 49 + Signal/src/views/ContactTableViewCell.h | 17 + Signal/src/views/ContactTableViewCell.m | 58 + Signal/src/views/CountryCodeTableViewCell.h | 10 + Signal/src/views/CountryCodeTableViewCell.m | 21 + Signal/src/views/DialerButtonView.h | 32 + Signal/src/views/DialerButtonView.m | 40 + Signal/src/views/FavouriteTableViewCell.h | 26 + Signal/src/views/FavouriteTableViewCell.m | 51 + Signal/src/views/InboxFeedFooterCell.h | 15 + Signal/src/views/InboxFeedFooterCell.m | 31 + Signal/src/views/InboxFeedFooterCell.xib | 60 + Signal/src/views/InboxFeedTableViewCell.h | 39 + Signal/src/views/InboxFeedTableViewCell.m | 158 + Signal/src/views/InteractiveLabel.h | 8 + Signal/src/views/InteractiveLabel.m | 71 + Signal/src/views/LeftSideMenuCell.h | 7 + Signal/src/views/LeftSideMenuCell.m | 26 + Signal/src/views/LocalizableCustomFontLabel.h | 24 + Signal/src/views/LocalizableCustomFontLabel.m | 34 + .../src/views/PreferenceListTableViewCell.h | 13 + .../src/views/PreferenceListTableViewCell.m | 13 + .../src/views/PreferenceListTableViewCell.xib | 37 + Signal/src/views/SearchBarTitleView.h | 27 + Signal/src/views/SearchBarTitleView.m | 97 + Signal/src/views/SettingsTableHeaderView.h | 15 + Signal/src/views/SettingsTableHeaderView.m | 17 + Signal/src/views/UnseenWhisperUserCell.h | 11 + Signal/src/views/UnseenWhisperUserCell.m | 31 + .../src/views/xibs/CallLogTableViewCell.xib | 103 + .../src/views/xibs/ContactTableViewCell.xib | 35 + .../views/xibs/CountryCodeTableViewCell.xib | 38 + .../src/views/xibs/FavouriteTableViewCell.xib | 48 + .../src/views/xibs/InboxFeedTableViewCell.xib | 133 + Signal/src/views/xibs/LeftSideMenuCell.xib | 30 + .../src/views/xibs/UnseenWhisperUserCell.xib | 40 + .../Supporting Files/SignalTests-Info.plist | 22 + Signal/test/TestUtil.h | 22 + Signal/test/TestUtil.m | 47 + Signal/test/async/AsyncUtilTest.h | 5 + Signal/test/async/AsyncUtilTest.m | 269 ++ Signal/test/async/FutureSourceTest.h | 5 + Signal/test/async/FutureSourceTest.m | 349 ++ Signal/test/async/ObservableTest.h | 5 + Signal/test/async/ObservableTest.m | 132 + Signal/test/audio/AudioFrameTest.h | 5 + Signal/test/audio/AudioFrameTest.m | 14 + Signal/test/audio/AudioRemoteIOTest.h | 5 + Signal/test/audio/AudioRemoteIOTest.m | 53 + Signal/test/audio/AudioStretcherTest.h | 5 + Signal/test/audio/AudioStretcherTest.m | 25 + Signal/test/audio/JitterQueueTest.h | 5 + Signal/test/audio/JitterQueueTest.m | 197 + Signal/test/audio/SpeexCodecTest.h | 5 + Signal/test/audio/SpeexCodecTest.m | 35 + Signal/test/contact/ContactManagerTest.m | 29 + Signal/test/fr.lproj/InfoPlist.strings | 2 + Signal/test/network/IpAddressTest.h | 5 + Signal/test/network/IpAddressTest.m | 101 + Signal/test/network/IpEndPointTest.h | 5 + Signal/test/network/IpEndPointTest.m | 21 + Signal/test/network/dns/DnsManagerTest.h | 5 + Signal/test/network/dns/DnsManagerTest.m | 81 + .../network/http/HttpRequestResponseTest.h | 5 + .../network/http/HttpRequestResponseTest.m | 126 + Signal/test/network/rtp/RtpPacketTests.h | 6 + Signal/test/network/rtp/RtpPacketTests.m | 121 + .../test/network/rtp/srtp/SecureStreamTest.h | 5 + .../test/network/rtp/srtp/SecureStreamTest.m | 61 + .../network/rtp/srtp/SequenceCounterTest.h | 5 + .../network/rtp/srtp/SequenceCounterTest.m | 48 + Signal/test/network/rtp/zrtp/DH3KAgreerTest.h | 13 + Signal/test/network/rtp/zrtp/DH3KAgreerTest.m | 10 + Signal/test/network/rtp/zrtp/EC25AgreerTest.m | 36 + .../network/rtp/zrtp/HandshakePacketTest.h | 5 + .../network/rtp/zrtp/HandshakePacketTest.m | 73 + Signal/test/network/rtp/zrtp/HashChainTest.h | 5 + Signal/test/network/rtp/zrtp/HashChainTest.m | 28 + .../test/network/rtp/zrtp/MasterSecretTest.h | 5 + .../test/network/rtp/zrtp/MasterSecretTest.m | 38 + .../ShortAuthenticationStringGeneratorTest.h | 5 + .../ShortAuthenticationStringGeneratorTest.m | 11 + Signal/test/network/rtp/zrtp/ZrtpTest.h | 17 + Signal/test/network/rtp/zrtp/ZrtpTest.m | 92 + ...generatedKeyAgreementParticipantProtocol.h | 14 + ...generatedKeyAgreementParticipantProtocol.m | 18 + .../network/tcp/LowLatencyConnectorTest.h | 5 + .../network/tcp/LowLatencyConnectorTest.m | 87 + .../test/network/tcp/tls/NetworkStreamTest.h | 5 + .../test/network/tcp/tls/NetworkStreamTest.m | 136 + .../test/network/tcp/tls/SecureEndPointTest.h | 5 + .../test/network/tcp/tls/SecureEndPointTest.m | 19 + Signal/test/network/udp/UdpSocketTest.h | 5 + Signal/test/network/udp/UdpSocketTest.m | 187 + Signal/test/phone/PhoneNumberTest.h | 5 + Signal/test/phone/PhoneNumberTest.m | 14 + .../phone/signaling/SessionDescriptorTest.h | 5 + .../phone/signaling/SessionDescriptorTest.m | 49 + .../profiling/DecayingSampleEstimatorTest.h | 5 + .../profiling/DecayingSampleEstimatorTest.m | 110 + Signal/test/profiling/EventWindowTest.h | 5 + Signal/test/profiling/EventWindowTest.m | 21 + Signal/test/util/BloomFilterTest.h | 5 + Signal/test/util/BloomFilterTest.m | 27 + Signal/test/util/CancelTokenTest.h | 5 + Signal/test/util/CancelTokenTest.m | 81 + Signal/test/util/ConversionsTest.h | 5 + Signal/test/util/ConversionsTest.m | 32 + Signal/test/util/Crc32Test.h | 5 + Signal/test/util/Crc32Test.m | 12 + Signal/test/util/CryptoUtilTest.h | 5 + Signal/test/util/CryptoUtilTest.m | 119 + Signal/test/util/CyclicalBufferTest.h | 5 + Signal/test/util/CyclicalBufferTest.m | 121 + Signal/test/util/ExceptionsTest.h | 5 + Signal/test/util/ExceptionsTest.m | 40 + Signal/test/util/FunctionalUtilTest.h | 5 + Signal/test/util/FunctionalUtilTest.m | 55 + Signal/test/util/PriorityQueueTest.h | 5 + Signal/test/util/PriorityQueueTest.m | 110 + Signal/test/util/QueueTest.h | 5 + Signal/test/util/QueueTest.m | 29 + Signal/test/util/UtilTest.h | 5 + Signal/test/util/UtilTest.m | 398 ++ .../ca-ES.lproj/Localizable.strings | Bin 0 -> 14232 bytes .../cs-CZ.lproj/Localizable.strings | Bin 0 -> 13010 bytes .../translations/de.lproj/Localizable.strings | Bin 0 -> 14172 bytes .../translations/en.lproj/Localizable.strings | 134 + .../translations/fr.lproj/Localizable.strings | Bin 0 -> 14462 bytes .../translations/nl.lproj/Localizable.strings | Bin 0 -> 14032 bytes .../ro-RO.lproj/Localizable.strings | Bin 0 -> 13662 bytes .../sv-SE.lproj/Localizable.strings | Bin 0 -> 13494 bytes Signal/whisperReal.der | Bin 0 -> 1026 bytes 919 files changed, 98519 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Libraries/ProtocolBuffers/AbstractMessage.h create mode 100644 Libraries/ProtocolBuffers/AbstractMessage.m create mode 100644 Libraries/ProtocolBuffers/AbstractMessage_Builder.h create mode 100644 Libraries/ProtocolBuffers/AbstractMessage_Builder.m create mode 100644 Libraries/ProtocolBuffers/Bootstrap.h create mode 100644 Libraries/ProtocolBuffers/CodedInputStream.h create mode 100644 Libraries/ProtocolBuffers/CodedInputStream.m create mode 100644 Libraries/ProtocolBuffers/CodedOutputStream.h create mode 100644 Libraries/ProtocolBuffers/CodedOutputStream.m create mode 100644 Libraries/ProtocolBuffers/ConcreteExtensionField.h create mode 100644 Libraries/ProtocolBuffers/ConcreteExtensionField.m create mode 100644 Libraries/ProtocolBuffers/ExtendableMessage.h create mode 100644 Libraries/ProtocolBuffers/ExtendableMessage.m create mode 100644 Libraries/ProtocolBuffers/ExtendableMessage_Builder.h create mode 100644 Libraries/ProtocolBuffers/ExtendableMessage_Builder.m create mode 100644 Libraries/ProtocolBuffers/ExtensionField.h create mode 100644 Libraries/ProtocolBuffers/ExtensionRegistry.h create mode 100644 Libraries/ProtocolBuffers/ExtensionRegistry.m create mode 100644 Libraries/ProtocolBuffers/Field.h create mode 100644 Libraries/ProtocolBuffers/Field.m create mode 100644 Libraries/ProtocolBuffers/ForwardDeclarations.h create mode 100644 Libraries/ProtocolBuffers/GeneratedMessage.h create mode 100644 Libraries/ProtocolBuffers/GeneratedMessage.m create mode 100644 Libraries/ProtocolBuffers/GeneratedMessage_Builder.h create mode 100644 Libraries/ProtocolBuffers/GeneratedMessage_Builder.m create mode 100644 Libraries/ProtocolBuffers/Message.h create mode 100644 Libraries/ProtocolBuffers/Message_Builder.h create mode 100644 Libraries/ProtocolBuffers/MutableExtensionRegistry.h create mode 100644 Libraries/ProtocolBuffers/MutableExtensionRegistry.m create mode 100644 Libraries/ProtocolBuffers/MutableField.h create mode 100644 Libraries/ProtocolBuffers/MutableField.m create mode 100644 Libraries/ProtocolBuffers/ProtocolBuffers.h create mode 100644 Libraries/ProtocolBuffers/TextFormat.h create mode 100644 Libraries/ProtocolBuffers/TextFormat.m create mode 100644 Libraries/ProtocolBuffers/UnknownFieldSet.h create mode 100644 Libraries/ProtocolBuffers/UnknownFieldSet.m create mode 100644 Libraries/ProtocolBuffers/UnknownFieldSet_Builder.h create mode 100644 Libraries/ProtocolBuffers/UnknownFieldSet_Builder.m create mode 100644 Libraries/ProtocolBuffers/Utilities.h create mode 100644 Libraries/ProtocolBuffers/Utilities.m create mode 100644 Libraries/ProtocolBuffers/WireFormat.h create mode 100644 Libraries/ProtocolBuffers/WireFormat.m create mode 100644 Libraries/spandsp/spandsp/spandsp.xcodeproj/project.pbxproj create mode 100644 Libraries/spandsp/spandsp/spandsp/adsi.h create mode 100644 Libraries/spandsp/spandsp/spandsp/arctan2.h create mode 100644 Libraries/spandsp/spandsp/spandsp/async.h create mode 100644 Libraries/spandsp/spandsp/spandsp/at_interpreter.h create mode 100644 Libraries/spandsp/spandsp/spandsp/awgn.h create mode 100644 Libraries/spandsp/spandsp/spandsp/bell_r2_mf.h create mode 100644 Libraries/spandsp/spandsp/spandsp/bert.h create mode 100644 Libraries/spandsp/spandsp/spandsp/biquad.h create mode 100644 Libraries/spandsp/spandsp/spandsp/bit_operations.h create mode 100644 Libraries/spandsp/spandsp/spandsp/bitstream.h create mode 100644 Libraries/spandsp/spandsp/spandsp/complex.h create mode 100644 Libraries/spandsp/spandsp/spandsp/complex_filters.h create mode 100644 Libraries/spandsp/spandsp/spandsp/complex_vector_float.h create mode 100644 Libraries/spandsp/spandsp/spandsp/complex_vector_int.h create mode 100644 Libraries/spandsp/spandsp/spandsp/crc.h create mode 100644 Libraries/spandsp/spandsp/spandsp/dc_restore.h create mode 100644 Libraries/spandsp/spandsp/spandsp/dds.h create mode 100644 Libraries/spandsp/spandsp/spandsp/dtmf.h create mode 100644 Libraries/spandsp/spandsp/spandsp/echo.h create mode 100644 Libraries/spandsp/spandsp/spandsp/expose.h create mode 100644 Libraries/spandsp/spandsp/spandsp/fast_convert.h create mode 100644 Libraries/spandsp/spandsp/spandsp/fax.h create mode 100644 Libraries/spandsp/spandsp/spandsp/fax_modems.h create mode 100644 Libraries/spandsp/spandsp/spandsp/fir.h create mode 100644 Libraries/spandsp/spandsp/spandsp/fsk.h create mode 100644 Libraries/spandsp/spandsp/spandsp/g168models.h create mode 100644 Libraries/spandsp/spandsp/spandsp/g711.h create mode 100644 Libraries/spandsp/spandsp/spandsp/g722.h create mode 100644 Libraries/spandsp/spandsp/spandsp/g726.h create mode 100644 Libraries/spandsp/spandsp/spandsp/gsm0610.h create mode 100644 Libraries/spandsp/spandsp/spandsp/hdlc.h create mode 100644 Libraries/spandsp/spandsp/spandsp/ima_adpcm.h create mode 100644 Libraries/spandsp/spandsp/spandsp/logging.h create mode 100644 Libraries/spandsp/spandsp/spandsp/lpc10.h create mode 100644 Libraries/spandsp/spandsp/spandsp/modem_connect_tones.h create mode 100644 Libraries/spandsp/spandsp/spandsp/modem_echo.h create mode 100644 Libraries/spandsp/spandsp/spandsp/noise.h create mode 100644 Libraries/spandsp/spandsp/spandsp/oki_adpcm.h create mode 100644 Libraries/spandsp/spandsp/spandsp/playout.h create mode 100644 Libraries/spandsp/spandsp/spandsp/plc.h create mode 100644 Libraries/spandsp/spandsp/spandsp/power_meter.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/README create mode 100644 Libraries/spandsp/spandsp/spandsp/private/adsi.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/async.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/at_interpreter.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/awgn.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/bell_r2_mf.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/bert.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/bitstream.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/dtmf.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/echo.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/fax.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/fax_modems.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/fsk.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/g711.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/g722.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/g726.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/gsm0610.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/hdlc.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/ima_adpcm.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/logging.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/lpc10.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/modem_connect_tones.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/modem_echo.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/noise.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/oki_adpcm.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/queue.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/schedule.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/sig_tone.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/silence_gen.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/super_tone_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/super_tone_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/swept_tone.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t30.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t30_dis_dtc_dcs_bits.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t31.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t38_core.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t38_gateway.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t38_non_ecm_buffer.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t38_terminal.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t4_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/t4_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/time_scale.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/tone_detect.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/tone_generate.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v17rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v17tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v18.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v22bis.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v27ter_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v27ter_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v29rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v29tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v42.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v42bis.h create mode 100644 Libraries/spandsp/spandsp/spandsp/private/v8.h create mode 100644 Libraries/spandsp/spandsp/spandsp/queue.h create mode 100644 Libraries/spandsp/spandsp/spandsp/saturated.h create mode 100644 Libraries/spandsp/spandsp/spandsp/schedule.h create mode 100644 Libraries/spandsp/spandsp/spandsp/sig_tone.h create mode 100644 Libraries/spandsp/spandsp/spandsp/silence_gen.h create mode 100644 Libraries/spandsp/spandsp/spandsp/super_tone_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/super_tone_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/swept_tone.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t30.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t30_api.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t30_fcf.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t30_logging.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t31.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t35.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t38_core.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t38_gateway.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t38_non_ecm_buffer.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t38_terminal.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t4_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/t4_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/telephony.h create mode 100644 Libraries/spandsp/spandsp/spandsp/time_scale.h create mode 100644 Libraries/spandsp/spandsp/spandsp/timing.h create mode 100644 Libraries/spandsp/spandsp/spandsp/tone_detect.h create mode 100644 Libraries/spandsp/spandsp/spandsp/tone_generate.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v17rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v17tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v18.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v22bis.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v27ter_rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v27ter_tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v29rx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v29tx.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v42.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v42bis.h create mode 100644 Libraries/spandsp/spandsp/spandsp/v8.h create mode 100644 Libraries/spandsp/spandsp/spandsp/vector_float.h create mode 100644 Libraries/spandsp/spandsp/spandsp/vector_int.h create mode 100644 Libraries/spandsp/spandsp/spandsp/version.h create mode 100644 Libraries/spandsp/spandsp/spandsp/version.h.in create mode 100644 Libraries/spandsp/spandsp/time_scale.c create mode 100644 Libraries/speex/_kiss_fft_guts.h create mode 100644 Libraries/speex/arch.h create mode 100644 Libraries/speex/bits.c create mode 100644 Libraries/speex/buffer.c create mode 100644 Libraries/speex/cb_search.c create mode 100644 Libraries/speex/cb_search.h create mode 100644 Libraries/speex/cb_search_arm4.h create mode 100644 Libraries/speex/cb_search_bfin.h create mode 100644 Libraries/speex/cb_search_sse.h create mode 100644 Libraries/speex/exc_10_16_table.c create mode 100644 Libraries/speex/exc_10_32_table.c create mode 100644 Libraries/speex/exc_20_32_table.c create mode 100644 Libraries/speex/exc_5_256_table.c create mode 100644 Libraries/speex/exc_5_64_table.c create mode 100644 Libraries/speex/exc_8_128_table.c create mode 100644 Libraries/speex/fftwrap.c create mode 100644 Libraries/speex/fftwrap.h create mode 100644 Libraries/speex/filterbank.c create mode 100644 Libraries/speex/filterbank.h create mode 100644 Libraries/speex/filters.c create mode 100644 Libraries/speex/filters.h create mode 100644 Libraries/speex/filters_arm4.h create mode 100644 Libraries/speex/filters_bfin.h create mode 100644 Libraries/speex/filters_sse.h create mode 100644 Libraries/speex/fixed_arm4.h create mode 100644 Libraries/speex/fixed_arm5e.h create mode 100644 Libraries/speex/fixed_bfin.h create mode 100644 Libraries/speex/fixed_debug.h create mode 100644 Libraries/speex/fixed_generic.h create mode 100644 Libraries/speex/gain_table.c create mode 100644 Libraries/speex/gain_table_lbr.c create mode 100644 Libraries/speex/hexc_10_32_table.c create mode 100644 Libraries/speex/hexc_table.c create mode 100644 Libraries/speex/high_lsp_tables.c create mode 100644 Libraries/speex/include/bitwise.c create mode 100755 Libraries/speex/include/config.h create mode 100644 Libraries/speex/include/framing.c create mode 100644 Libraries/speex/include/ogg/config_types.h create mode 100644 Libraries/speex/include/ogg/ogg.h create mode 100644 Libraries/speex/include/ogg/os_types.h create mode 100644 Libraries/speex/include/speex/Makefile create mode 100644 Libraries/speex/include/speex/Makefile.am create mode 100644 Libraries/speex/include/speex/Makefile.in create mode 100644 Libraries/speex/include/speex/speex.h create mode 100644 Libraries/speex/include/speex/speex_bits.h create mode 100644 Libraries/speex/include/speex/speex_buffer.h create mode 100644 Libraries/speex/include/speex/speex_callbacks.h create mode 100644 Libraries/speex/include/speex/speex_config_types.h create mode 100644 Libraries/speex/include/speex/speex_config_types.h.in create mode 100644 Libraries/speex/include/speex/speex_echo.h create mode 100644 Libraries/speex/include/speex/speex_header.h create mode 100644 Libraries/speex/include/speex/speex_jitter.h create mode 100644 Libraries/speex/include/speex/speex_preprocess.h create mode 100644 Libraries/speex/include/speex/speex_resampler.h create mode 100644 Libraries/speex/include/speex/speex_stereo.h create mode 100644 Libraries/speex/include/speex/speex_types.h create mode 100644 Libraries/speex/jitter.c create mode 100644 Libraries/speex/kiss_fft.c create mode 100644 Libraries/speex/kiss_fft.h create mode 100644 Libraries/speex/kiss_fftr.c create mode 100644 Libraries/speex/kiss_fftr.h create mode 100644 Libraries/speex/lpc.c create mode 100644 Libraries/speex/lpc.h create mode 100644 Libraries/speex/lpc_bfin.h create mode 100644 Libraries/speex/lsp.c create mode 100644 Libraries/speex/lsp.h create mode 100644 Libraries/speex/lsp_bfin.h create mode 100644 Libraries/speex/lsp_tables_nb.c create mode 100644 Libraries/speex/ltp.c create mode 100644 Libraries/speex/ltp.h create mode 100644 Libraries/speex/ltp_arm4.h create mode 100644 Libraries/speex/ltp_bfin.h create mode 100644 Libraries/speex/ltp_sse.h create mode 100644 Libraries/speex/math_approx.h create mode 100644 Libraries/speex/mdf.c create mode 100644 Libraries/speex/misc_bfin.h create mode 100644 Libraries/speex/modes.c create mode 100644 Libraries/speex/modes.h create mode 100644 Libraries/speex/modes_wb.c create mode 100644 Libraries/speex/nb_celp.c create mode 100644 Libraries/speex/nb_celp.h create mode 100644 Libraries/speex/os_support.h create mode 100644 Libraries/speex/preprocess.c create mode 100644 Libraries/speex/pseudofloat.h create mode 100644 Libraries/speex/quant_lsp.c create mode 100644 Libraries/speex/quant_lsp.h create mode 100644 Libraries/speex/quant_lsp_bfin.h create mode 100644 Libraries/speex/resample.c create mode 100644 Libraries/speex/resample_sse.h create mode 100644 Libraries/speex/sb_celp.c create mode 100644 Libraries/speex/sb_celp.h create mode 100644 Libraries/speex/scal.c create mode 100644 Libraries/speex/smallft.c create mode 100644 Libraries/speex/smallft.h create mode 100644 Libraries/speex/speex.c create mode 100644 Libraries/speex/speex.xcodeproj/project.pbxproj create mode 100644 Libraries/speex/speex_callbacks.c create mode 100644 Libraries/speex/speex_header.c create mode 100644 Libraries/speex/stack_alloc.h create mode 100644 Libraries/speex/stereo.c create mode 100644 Libraries/speex/testdenoise.c create mode 100644 Libraries/speex/testecho.c create mode 100644 Libraries/speex/testenc.c create mode 100644 Libraries/speex/testenc_uwb.c create mode 100644 Libraries/speex/testenc_wb.c create mode 100644 Libraries/speex/testjitter.c create mode 100644 Libraries/speex/vbr.c create mode 100644 Libraries/speex/vbr.h create mode 100644 Libraries/speex/vorbis_psy.h create mode 100644 Libraries/speex/vq.c create mode 100644 Libraries/speex/vq.h create mode 100644 Libraries/speex/vq_arm4.h create mode 100644 Libraries/speex/vq_bfin.h create mode 100644 Libraries/speex/vq_sse.h create mode 100644 Libraries/speex/window.c create mode 100644 Podfile create mode 100644 README.md create mode 100644 SettingsBundle/Settings.bundle/Acknowledgements.plist create mode 100644 SettingsBundle/Settings.bundle/Root.plist create mode 100644 SettingsBundle/Settings.bundle/en.lproj/Acknowledgements.strings create mode 100644 SettingsBundle/Settings.bundle/en.lproj/Root.strings create mode 100644 SettingsBundle/licenses/MMDrawerController.license create mode 100644 SettingsBundle/licenses/OpenSSL.license create mode 100644 SettingsBundle/licenses/ProtoBuffers.license create mode 100644 SettingsBundle/licenses/Spandsp.license create mode 100644 SettingsBundle/licenses/Speex.license create mode 100755 SettingsBundle/licenses/compileLicences.pl create mode 100644 SettingsBundle/licenses/libPhoneNumber-iOS.license create mode 100644 Signal.xcodeproj/project.pbxproj create mode 100644 Signal/AudioFiles/171756__nenadsimic__picked-coin-echo-2.wav create mode 100644 Signal/AudioFiles/busy.mp3 create mode 100644 Signal/AudioFiles/completed.mp3 create mode 100644 Signal/AudioFiles/failure.mp3 create mode 100644 Signal/AudioFiles/handshake.mp3 create mode 100644 Signal/AudioFiles/outring.mp3 create mode 100644 Signal/AudioFiles/r.caf create mode 100644 Signal/AudioFiles/sonarping.mp3 create mode 100644 Signal/Default-568h@2x.png create mode 100644 Signal/Default.png create mode 100644 Signal/Default@2x.png create mode 100644 Signal/Fonts/HelveticaNeueLTStd-Bd.otf create mode 100755 Signal/Fonts/HelveticaNeueLTStd-Lt.otf create mode 100755 Signal/Fonts/HelveticaNeueLTStd-Md.otf create mode 100755 Signal/Fonts/HelveticaNeueLTStd-Th.otf create mode 100644 Signal/Icons/AppIcon29x29.jpg create mode 100644 Signal/Icons/AppIcon29x29.png create mode 100644 Signal/Icons/AppIcon29x29@2x.png create mode 100644 Signal/Icons/AppIcon40x40.png create mode 100644 Signal/Icons/AppIcon40x40@2x.png create mode 100644 Signal/Icons/AppIcon60x60.png create mode 100644 Signal/Icons/AppIcon60x60@2x.png create mode 100644 Signal/Icons/AppIcon76x76.png create mode 100644 Signal/Icons/AppIcon76x76@2x.png create mode 100644 Signal/Images/DefaultContactImage.png create mode 100644 Signal/Images/archive_icon.png create mode 100644 Signal/Images/archive_icon@2x.png create mode 100644 Signal/Images/backspace.png create mode 100644 Signal/Images/backspace@2x.png create mode 100644 Signal/Images/checkbox_checkmark.png create mode 100644 Signal/Images/checkbox_checkmark@2x.png create mode 100644 Signal/Images/checkbox_empty.png create mode 100644 Signal/Images/checkbox_empty@2x.png create mode 100644 Signal/Images/contact_default_feed.png create mode 100644 Signal/Images/contacts_arrow.png create mode 100644 Signal/Images/contacts_arrow@2x.png create mode 100644 Signal/Images/dismiss_notification_icon.png create mode 100644 Signal/Images/dismiss_notification_icon@2x.png create mode 100644 Signal/Images/drop_down_arrow_icon.png create mode 100644 Signal/Images/drop_down_arrow_icon@2x.png create mode 100644 Signal/Images/expanded_cell_icon.png create mode 100644 Signal/Images/expanded_cell_icon@2x.png create mode 100644 Signal/Images/favourite_false_icon.png create mode 100644 Signal/Images/favourite_false_icon@2x.png create mode 100644 Signal/Images/favourite_true_icon.png create mode 100644 Signal/Images/favourite_true_icon@2x.png create mode 100644 Signal/Images/forward_button.png create mode 100644 Signal/Images/forward_button@2x.png create mode 100644 Signal/Images/home_icon.png create mode 100644 Signal/Images/icon_contacts.png create mode 100644 Signal/Images/icon_favourites.png create mode 100644 Signal/Images/icon_keypad.png create mode 100644 Signal/Images/icon_recents.png create mode 100644 Signal/Images/in_call_phone_icon.png create mode 100644 Signal/Images/in_call_phone_icon@2x.png create mode 100644 Signal/Images/in_call_phrase_icon.png create mode 100644 Signal/Images/in_call_phrase_icon@2x.png create mode 100644 Signal/Images/incoming_call_icon.png create mode 100644 Signal/Images/incoming_call_icon@2x.png create mode 100644 Signal/Images/menu_icon.png create mode 100644 Signal/Images/menu_icon@2x.png create mode 100644 Signal/Images/message_bubble.png create mode 100644 Signal/Images/message_bubble@2x.png create mode 100644 Signal/Images/message_icon.png create mode 100644 Signal/Images/mute_icon.png create mode 100644 Signal/Images/mute_icon@2x.png create mode 100644 Signal/Images/mute_icon_selected.png create mode 100644 Signal/Images/mute_icon_selected@2x.png create mode 100644 Signal/Images/notification_detail_icon.png create mode 100644 Signal/Images/notification_detail_icon@2x.png create mode 100644 Signal/Images/notification_mini_icon.png create mode 100644 Signal/Images/notification_mini_icon@2x.png create mode 100644 Signal/Images/outgoing_call_icon.png create mode 100755 Signal/Images/outgoing_call_icon@2x.png create mode 100644 Signal/Images/phone_icon.png create mode 100644 Signal/Images/search_cancel.png create mode 100644 Signal/Images/search_cancel@2x.png create mode 100644 Signal/Images/search_icon.png create mode 100644 Signal/Images/search_icon@2x.png create mode 100644 Signal/Images/send_code_icon.png create mode 100644 Signal/Images/send_code_icon@2x.png create mode 100644 Signal/Images/speaker_icon.png create mode 100644 Signal/Images/speaker_icon@2x.png create mode 100644 Signal/Images/speaker_icon_selected.png create mode 100644 Signal/Images/speaker_icon_selected@2x.png create mode 100644 Signal/Images/spinner_connecting.png create mode 100644 Signal/Images/spinner_connecting@2x.png create mode 100644 Signal/Images/spinner_connecting_flash.png create mode 100644 Signal/Images/spinner_connecting_flash@2x.png create mode 100644 Signal/Images/spinner_error.png create mode 100644 Signal/Images/spinner_error@2x.png create mode 100644 Signal/Images/spinner_ringing.png create mode 100644 Signal/Images/spinner_ringing@2x.png create mode 100644 Signal/Images/tab_icon_contacts.png create mode 100644 Signal/Images/tab_icon_contacts@2x.png create mode 100644 Signal/Images/tab_icon_favourites.png create mode 100644 Signal/Images/tab_icon_favourites@2x.png create mode 100644 Signal/Images/tab_icon_inbox.png create mode 100644 Signal/Images/tab_icon_inbox@2x.png create mode 100644 Signal/Images/tab_icon_keypad.png create mode 100644 Signal/Images/tab_icon_keypad@2x.png create mode 100644 Signal/Images/tab_icon_menu.png create mode 100644 Signal/Images/tab_icon_menu@2x.png create mode 100644 Signal/Images/trash_icon.png create mode 100644 Signal/Images/trash_icon@2x.png create mode 100644 Signal/Images/volume_high.png create mode 100644 Signal/Images/volume_high@2x.png create mode 100644 Signal/Images/volume_low.png create mode 100644 Signal/Images/volume_low@2x.png create mode 100644 Signal/Images/whisper_notification_icon.png create mode 100644 Signal/Images/whisper_notification_icon@2x.png create mode 100644 Signal/Signal-Info.plist create mode 100644 Signal/Signal-Prefix.pch create mode 100644 Signal/Signal.entitlements create mode 100644 Signal/iTunesArtwork.png create mode 100644 Signal/iTunesArtwork@2x.png create mode 100644 Signal/main.m create mode 100644 Signal/src/AppDelegate.h create mode 100644 Signal/src/AppDelegate.m create mode 100644 Signal/src/NotificationManifest.h create mode 100644 Signal/src/NotificationTracker.h create mode 100644 Signal/src/NotificationTracker.m create mode 100644 Signal/src/async/AsyncUtil.h create mode 100644 Signal/src/async/AsyncUtil.m create mode 100644 Signal/src/async/AsyncUtilHelperRacingOperation.h create mode 100644 Signal/src/async/AsyncUtilHelperRacingOperation.m create mode 100644 Signal/src/async/CancelTokenSource.h create mode 100644 Signal/src/async/CancelTokenSource.m create mode 100644 Signal/src/async/CancelledToken.h create mode 100644 Signal/src/async/CancelledToken.m create mode 100644 Signal/src/async/Future.h create mode 100644 Signal/src/async/Future.m create mode 100644 Signal/src/async/FutureSource.h create mode 100644 Signal/src/async/FutureSource.m create mode 100644 Signal/src/async/FutureUtil.h create mode 100644 Signal/src/async/FutureUtil.m create mode 100644 Signal/src/async/ObservableValue.h create mode 100644 Signal/src/async/ObservableValue.m create mode 100644 Signal/src/async/TimeoutFailure.h create mode 100644 Signal/src/async/TimeoutFailure.m create mode 100644 Signal/src/async/protocols/CancelToken.h create mode 100644 Signal/src/audio/AppAudioManager.h create mode 100644 Signal/src/audio/AppAudioManager.m create mode 100644 Signal/src/audio/AudioRouter.h create mode 100644 Signal/src/audio/AudioRouter.m create mode 100644 Signal/src/audio/SoundBoard.h create mode 100644 Signal/src/audio/SoundBoard.m create mode 100644 Signal/src/audio/SoundInstance.h create mode 100644 Signal/src/audio/SoundInstance.m create mode 100644 Signal/src/audio/SoundPlayer.h create mode 100644 Signal/src/audio/SoundPlayer.m create mode 100644 Signal/src/audio/incall_audio/AudioPacker.h create mode 100644 Signal/src/audio/incall_audio/AudioPacker.m create mode 100644 Signal/src/audio/incall_audio/AudioSocket.h create mode 100644 Signal/src/audio/incall_audio/AudioSocket.m create mode 100644 Signal/src/audio/incall_audio/CallAudioManager.h create mode 100644 Signal/src/audio/incall_audio/CallAudioManager.m create mode 100644 Signal/src/audio/incall_audio/EncodedAudioFrame.h create mode 100644 Signal/src/audio/incall_audio/EncodedAudioFrame.m create mode 100644 Signal/src/audio/incall_audio/EncodedAudioPacket.h create mode 100644 Signal/src/audio/incall_audio/EncodedAudioPacket.m create mode 100644 Signal/src/audio/incall_audio/RemoteIOAudio.h create mode 100644 Signal/src/audio/incall_audio/RemoteIOAudio.m create mode 100644 Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.h create mode 100644 Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.m create mode 100644 Signal/src/audio/incall_audio/SpeexCodec.h create mode 100644 Signal/src/audio/incall_audio/SpeexCodec.m create mode 100644 Signal/src/audio/incall_audio/processing/AudioProcessor.h create mode 100644 Signal/src/audio/incall_audio/processing/AudioProcessor.m create mode 100644 Signal/src/audio/incall_audio/processing/AudioStretcher.h create mode 100644 Signal/src/audio/incall_audio/processing/AudioStretcher.m create mode 100644 Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.h create mode 100644 Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.m create mode 100644 Signal/src/audio/incall_audio/processing/DropoutTracker.h create mode 100644 Signal/src/audio/incall_audio/processing/DropoutTracker.m create mode 100644 Signal/src/audio/incall_audio/processing/JitterQueue.h create mode 100644 Signal/src/audio/incall_audio/processing/JitterQueue.m create mode 100644 Signal/src/audio/incall_audio/processing/StretchFactorController.h create mode 100644 Signal/src/audio/incall_audio/processing/StretchFactorController.m create mode 100644 Signal/src/audio/incall_audio/protocols/AudioCallbackHandler.h create mode 100644 Signal/src/audio/incall_audio/protocols/BufferDepthMeasure.h create mode 100644 Signal/src/audio/incall_audio/protocols/JitterQueueNotificationReceiver.h create mode 100644 Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.h create mode 100644 Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.m create mode 100644 Signal/src/call/RecentCall.h create mode 100644 Signal/src/call/RecentCall.m create mode 100644 Signal/src/call/RecentCallManager.h create mode 100644 Signal/src/call/RecentCallManager.m create mode 100644 Signal/src/contact/Contact.h create mode 100644 Signal/src/contact/Contact.m create mode 100644 Signal/src/contact/ContactsManager.h create mode 100644 Signal/src/contact/ContactsManager.m create mode 100644 Signal/src/crypto/CryptoTools.h create mode 100644 Signal/src/crypto/CryptoTools.m create mode 100644 Signal/src/crypto/EvpMessageDigest.h create mode 100644 Signal/src/crypto/EvpMessageDigest.m create mode 100644 Signal/src/crypto/EvpSymetricUtil.h create mode 100644 Signal/src/crypto/EvpSymetricUtil.m create mode 100644 Signal/src/crypto/EvpUtil.h create mode 100644 Signal/src/environment/Environment.h create mode 100644 Signal/src/environment/Environment.m create mode 100644 Signal/src/environment/KeyChainStorage.h create mode 100644 Signal/src/environment/KeyChainStorage.m create mode 100644 Signal/src/environment/LocalizableText.h create mode 100644 Signal/src/environment/LocalizableText.m create mode 100644 Signal/src/environment/PreferencesUtil.h create mode 100644 Signal/src/environment/PreferencesUtil.m create mode 100644 Signal/src/environment/PropertyListPreferences.h create mode 100644 Signal/src/environment/PropertyListPreferences.m create mode 100644 Signal/src/environment/Release.h create mode 100644 Signal/src/environment/Release.m create mode 100644 Signal/src/network/IpAddress.h create mode 100644 Signal/src/network/IpAddress.m create mode 100644 Signal/src/network/IpEndPoint.h create mode 100644 Signal/src/network/IpEndPoint.m create mode 100644 Signal/src/network/NetworkEndPoint.h create mode 100644 Signal/src/network/PacketHandler.h create mode 100644 Signal/src/network/PacketHandler.m create mode 100644 Signal/src/network/dns/DnsManager.h create mode 100644 Signal/src/network/dns/DnsManager.m create mode 100644 Signal/src/network/dns/HostNameEndPoint.h create mode 100644 Signal/src/network/dns/HostNameEndPoint.m create mode 100644 Signal/src/network/failures/IgnoredPacketFailure.h create mode 100644 Signal/src/network/failures/IgnoredPacketFailure.m create mode 100644 Signal/src/network/failures/UnrecognizedRequestFailure.h create mode 100644 Signal/src/network/failures/UnrecognizedRequestFailure.m create mode 100644 Signal/src/network/http/HttpManager.h create mode 100644 Signal/src/network/http/HttpManager.m create mode 100644 Signal/src/network/http/HttpRequest.h create mode 100644 Signal/src/network/http/HttpRequest.m create mode 100644 Signal/src/network/http/HttpRequestOrResponse.h create mode 100644 Signal/src/network/http/HttpRequestOrResponse.m create mode 100644 Signal/src/network/http/HttpRequestUtil.h create mode 100644 Signal/src/network/http/HttpRequestUtil.m create mode 100644 Signal/src/network/http/HttpResponse.h create mode 100644 Signal/src/network/http/HttpResponse.m create mode 100644 Signal/src/network/http/HttpSocket.h create mode 100644 Signal/src/network/http/HttpSocket.m create mode 100644 Signal/src/network/rtp/RtpPacket.h create mode 100644 Signal/src/network/rtp/RtpPacket.m create mode 100644 Signal/src/network/rtp/RtpSocket.h create mode 100644 Signal/src/network/rtp/RtpSocket.m create mode 100644 Signal/src/network/rtp/srtp/SequenceCounter.h create mode 100644 Signal/src/network/rtp/srtp/SequenceCounter.m create mode 100644 Signal/src/network/rtp/srtp/SrtpSocket.h create mode 100644 Signal/src/network/rtp/srtp/SrtpSocket.m create mode 100644 Signal/src/network/rtp/srtp/SrtpStream.h create mode 100644 Signal/src/network/rtp/srtp/SrtpStream.m create mode 100644 Signal/src/network/rtp/zrtp/HashChain.h create mode 100644 Signal/src/network/rtp/zrtp/HashChain.m create mode 100644 Signal/src/network/rtp/zrtp/MasterSecret.h create mode 100644 Signal/src/network/rtp/zrtp/MasterSecret.m create mode 100644 Signal/src/network/rtp/zrtp/NegotiationFailed.h create mode 100644 Signal/src/network/rtp/zrtp/NegotiationFailed.m create mode 100644 Signal/src/network/rtp/zrtp/RecipientUnavailable.h create mode 100644 Signal/src/network/rtp/zrtp/RecipientUnavailable.m create mode 100644 Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.h create mode 100644 Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.m create mode 100644 Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.h create mode 100644 Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.m create mode 100644 Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.h create mode 100644 Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.m create mode 100644 Signal/src/network/rtp/zrtp/ZrtpInitiator.h create mode 100644 Signal/src/network/rtp/zrtp/ZrtpInitiator.m create mode 100644 Signal/src/network/rtp/zrtp/ZrtpManager.h create mode 100644 Signal/src/network/rtp/zrtp/ZrtpManager.m create mode 100644 Signal/src/network/rtp/zrtp/ZrtpResponder.h create mode 100644 Signal/src/network/rtp/zrtp/ZrtpResponder.m create mode 100644 Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.h create mode 100644 Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.m create mode 100644 Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.h create mode 100644 Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.m create mode 100644 Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.h create mode 100644 Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.m create mode 100644 Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.h create mode 100644 Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.m create mode 100644 Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.h create mode 100644 Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m create mode 100644 Signal/src/network/rtp/zrtp/packets/CommitPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/CommitPacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/ConfirmPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/ConfirmPacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/DhPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/DhPacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.h create mode 100644 Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.m create mode 100644 Signal/src/network/rtp/zrtp/packets/HandshakePacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/HandshakePacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/HelloAckPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/HelloAckPacket.m create mode 100644 Signal/src/network/rtp/zrtp/packets/HelloPacket.h create mode 100644 Signal/src/network/rtp/zrtp/packets/HelloPacket.m create mode 100644 Signal/src/network/rtp/zrtp/protocols/KeyAgreementParticipant.h create mode 100644 Signal/src/network/rtp/zrtp/protocols/KeyAgreementProtocol.h create mode 100644 Signal/src/network/rtp/zrtp/protocols/ZrtpRole.h create mode 100644 Signal/src/network/tcp/LowLatencyCandidate.h create mode 100644 Signal/src/network/tcp/LowLatencyCandidate.m create mode 100644 Signal/src/network/tcp/LowLatencyConnector.h create mode 100644 Signal/src/network/tcp/LowLatencyConnector.m create mode 100644 Signal/src/network/tcp/StreamPair.h create mode 100644 Signal/src/network/tcp/StreamPair.m create mode 100644 Signal/src/network/tcp/tls/Certificate.h create mode 100644 Signal/src/network/tcp/tls/Certificate.m create mode 100644 Signal/src/network/tcp/tls/NetworkStream.h create mode 100644 Signal/src/network/tcp/tls/NetworkStream.m create mode 100644 Signal/src/network/tcp/tls/SecureEndPoint.h create mode 100644 Signal/src/network/tcp/tls/SecureEndPoint.m create mode 100644 Signal/src/network/udp/UdpSocket.h create mode 100644 Signal/src/network/udp/UdpSocket.m create mode 100644 Signal/src/phone/PhoneManager.h create mode 100644 Signal/src/phone/PhoneManager.m create mode 100644 Signal/src/phone/PhoneNumber.h create mode 100644 Signal/src/phone/PhoneNumber.m create mode 100644 Signal/src/phone/callstate/CallController.h create mode 100644 Signal/src/phone/callstate/CallController.m create mode 100644 Signal/src/phone/callstate/CallFailedServerMessage.h create mode 100644 Signal/src/phone/callstate/CallFailedServerMessage.m create mode 100644 Signal/src/phone/callstate/CallProgress.h create mode 100644 Signal/src/phone/callstate/CallProgress.m create mode 100644 Signal/src/phone/callstate/CallState.h create mode 100644 Signal/src/phone/callstate/CallState.m create mode 100644 Signal/src/phone/callstate/CallTermination.h create mode 100644 Signal/src/phone/callstate/CallTermination.m create mode 100644 Signal/src/phone/signaling/CallConnectResult.h create mode 100644 Signal/src/phone/signaling/CallConnectResult.m create mode 100644 Signal/src/phone/signaling/CallConnectUtil.h create mode 100644 Signal/src/phone/signaling/CallConnectUtil.m create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Initiator.h create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Initiator.m create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Responder.h create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Responder.m create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Server.h create mode 100644 Signal/src/phone/signaling/CallConnectUtil_Server.m create mode 100644 Signal/src/phone/signaling/InitiateSignal.pb.h create mode 100644 Signal/src/phone/signaling/InitiateSignal.pb.m create mode 100644 Signal/src/phone/signaling/InitiateSignal.proto create mode 100644 Signal/src/phone/signaling/InitiatorSessionDescriptor.h create mode 100644 Signal/src/phone/signaling/InitiatorSessionDescriptor.m create mode 100644 Signal/src/phone/signaling/ResponderSessionDescriptor.h create mode 100644 Signal/src/phone/signaling/ResponderSessionDescriptor.m create mode 100644 Signal/src/phone/signaling/SignalUtil.h create mode 100644 Signal/src/phone/signaling/SignalUtil.m create mode 100644 Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.h create mode 100644 Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.m create mode 100644 Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.h create mode 100644 Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.m create mode 100644 Signal/src/profiling/CategorizingLogger.h create mode 100644 Signal/src/profiling/CategorizingLogger.m create mode 100644 Signal/src/profiling/DecayingSampleEstimator.h create mode 100644 Signal/src/profiling/DecayingSampleEstimator.m create mode 100644 Signal/src/profiling/EventWindow.h create mode 100644 Signal/src/profiling/EventWindow.m create mode 100644 Signal/src/profiling/LoggingUtil.h create mode 100644 Signal/src/profiling/LoggingUtil.m create mode 100644 Signal/src/profiling/protocols/ConditionLogger.h create mode 100644 Signal/src/profiling/protocols/Logging.h create mode 100644 Signal/src/profiling/protocols/OccurrenceLogger.h create mode 100644 Signal/src/profiling/protocols/ValueLogger.h create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.h create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.m create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.h create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.m create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousValueLogger.h create mode 100644 Signal/src/profiling/protocols/utilities/AnonymousValueLogger.m create mode 100644 Signal/src/profiling/protocols/utilities/DiscardingLog.h create mode 100644 Signal/src/profiling/protocols/utilities/DiscardingLog.m create mode 100644 Signal/src/storage/KeychainWrapper.h create mode 100644 Signal/src/storage/KeychainWrapper.m create mode 100644 Signal/src/util/ArrayUtil.h create mode 100644 Signal/src/util/ArrayUtil.m create mode 100644 Signal/src/util/BloomFilter.h create mode 100644 Signal/src/util/BloomFilter.m create mode 100644 Signal/src/util/Conversions.h create mode 100644 Signal/src/util/Conversions.m create mode 100644 Signal/src/util/Crc32.h create mode 100644 Signal/src/util/Crc32.m create mode 100644 Signal/src/util/DataUtil.h create mode 100644 Signal/src/util/DataUtil.m create mode 100644 Signal/src/util/DateUtil.h create mode 100644 Signal/src/util/DateUtil.m create mode 100644 Signal/src/util/DictionaryUtil.h create mode 100644 Signal/src/util/DictionaryUtil.m create mode 100644 Signal/src/util/FunctionalUtil.h create mode 100644 Signal/src/util/FunctionalUtil.m create mode 100644 Signal/src/util/NumberUtil.h create mode 100644 Signal/src/util/NumberUtil.m create mode 100644 Signal/src/util/Operation.h create mode 100644 Signal/src/util/Operation.m create mode 100644 Signal/src/util/PhoneNumberUtil.h create mode 100644 Signal/src/util/PhoneNumberUtil.m create mode 100644 Signal/src/util/SmsInvite.h create mode 100644 Signal/src/util/SmsInvite.m create mode 100644 Signal/src/util/StringUtil.h create mode 100644 Signal/src/util/StringUtil.m create mode 100644 Signal/src/util/ThreadManager.h create mode 100644 Signal/src/util/ThreadManager.m create mode 100644 Signal/src/util/TimeUtil.h create mode 100644 Signal/src/util/TimeUtil.m create mode 100644 Signal/src/util/UIUtil.h create mode 100644 Signal/src/util/UIUtil.m create mode 100644 Signal/src/util/Util.h create mode 100644 Signal/src/util/Zid.h create mode 100644 Signal/src/util/Zid.m create mode 100644 Signal/src/util/collections/CyclicalBuffer.h create mode 100644 Signal/src/util/collections/CyclicalBuffer.m create mode 100644 Signal/src/util/collections/PriorityQueue.h create mode 100644 Signal/src/util/collections/PriorityQueue.m create mode 100644 Signal/src/util/collections/Queue.h create mode 100644 Signal/src/util/collections/Queue.m create mode 100644 Signal/src/util/constraints/BadArgument.h create mode 100644 Signal/src/util/constraints/BadArgument.m create mode 100644 Signal/src/util/constraints/BadState.h create mode 100644 Signal/src/util/constraints/BadState.m create mode 100644 Signal/src/util/constraints/Constraints.h create mode 100644 Signal/src/util/constraints/OperationFailed.h create mode 100644 Signal/src/util/constraints/OperationFailed.m create mode 100644 Signal/src/util/constraints/SecurityFailure.h create mode 100644 Signal/src/util/constraints/SecurityFailure.m create mode 100644 Signal/src/util/protocols/Terminable.h create mode 100644 Signal/src/util/protocols/utilities/AnonymousTerminator.h create mode 100644 Signal/src/util/protocols/utilities/AnonymousTerminator.m create mode 100644 Signal/src/view controllers/CallLogViewController.h create mode 100644 Signal/src/view controllers/CallLogViewController.m create mode 100644 Signal/src/view controllers/ContactBrowseViewController.h create mode 100644 Signal/src/view controllers/ContactBrowseViewController.m create mode 100644 Signal/src/view controllers/ContactDetailViewController.h create mode 100644 Signal/src/view controllers/ContactDetailViewController.m create mode 100644 Signal/src/view controllers/CountryCodeViewController.h create mode 100644 Signal/src/view controllers/CountryCodeViewController.m create mode 100644 Signal/src/view controllers/CountryCodeViewController.xib create mode 100644 Signal/src/view controllers/DialerViewController.h create mode 100644 Signal/src/view controllers/DialerViewController.m create mode 100644 Signal/src/view controllers/FavouritesViewController.h create mode 100644 Signal/src/view controllers/FavouritesViewController.m create mode 100644 Signal/src/view controllers/InCallViewController.h create mode 100644 Signal/src/view controllers/InCallViewController.m create mode 100644 Signal/src/view controllers/InboxFeedViewController.h create mode 100644 Signal/src/view controllers/InboxFeedViewController.m create mode 100644 Signal/src/view controllers/InviteContactModal.h create mode 100644 Signal/src/view controllers/InviteContactModal.m create mode 100644 Signal/src/view controllers/InviteContactsViewController.h create mode 100644 Signal/src/view controllers/InviteContactsViewController.m create mode 100644 Signal/src/view controllers/InviteContactsViewController.xib create mode 100644 Signal/src/view controllers/LeftSideMenuViewController.h create mode 100644 Signal/src/view controllers/LeftSideMenuViewController.m create mode 100644 Signal/src/view controllers/NextResponderScrollView.h create mode 100644 Signal/src/view controllers/NextResponderScrollView.m create mode 100644 Signal/src/view controllers/PreferenceListViewController.h create mode 100644 Signal/src/view controllers/PreferenceListViewController.m create mode 100644 Signal/src/view controllers/PreferenceListViewController.xib create mode 100644 Signal/src/view controllers/RegisterViewController.h create mode 100644 Signal/src/view controllers/RegisterViewController.m create mode 100644 Signal/src/view controllers/SettingsViewController.h create mode 100644 Signal/src/view controllers/SettingsViewController.m create mode 100644 Signal/src/view controllers/TabBarParentViewController.h create mode 100644 Signal/src/view controllers/TabBarParentViewController.m create mode 100644 Signal/src/view controllers/TabBarParentViewController.xib create mode 100644 Signal/src/view controllers/xibs/CallAudioManagerDemo.xib create mode 100644 Signal/src/view controllers/xibs/CallLogViewController.xib create mode 100644 Signal/src/view controllers/xibs/ContactBrowseViewController.xib create mode 100644 Signal/src/view controllers/xibs/ContactDetailTableViewCell.xib create mode 100644 Signal/src/view controllers/xibs/ContactDetailViewController.xib create mode 100644 Signal/src/view controllers/xibs/DialerViewController.xib create mode 100644 Signal/src/view controllers/xibs/DowngradeCallViewController.xib create mode 100644 Signal/src/view controllers/xibs/FavouritesViewController.xib create mode 100644 Signal/src/view controllers/xibs/InCallViewController.xib create mode 100644 Signal/src/view controllers/xibs/InboxFeedViewController.xib create mode 100644 Signal/src/view controllers/xibs/LeftSideMenuViewController.xib create mode 100644 Signal/src/view controllers/xibs/RegisterViewController.xib create mode 100644 Signal/src/view controllers/xibs/SettingsViewController.xib create mode 100644 Signal/src/views/CallLogTableViewCell.h create mode 100644 Signal/src/views/CallLogTableViewCell.m create mode 100644 Signal/src/views/ContactDetailTableViewCell.h create mode 100644 Signal/src/views/ContactDetailTableViewCell.m create mode 100644 Signal/src/views/ContactTableViewCell.h create mode 100644 Signal/src/views/ContactTableViewCell.m create mode 100644 Signal/src/views/CountryCodeTableViewCell.h create mode 100644 Signal/src/views/CountryCodeTableViewCell.m create mode 100644 Signal/src/views/DialerButtonView.h create mode 100644 Signal/src/views/DialerButtonView.m create mode 100644 Signal/src/views/FavouriteTableViewCell.h create mode 100644 Signal/src/views/FavouriteTableViewCell.m create mode 100644 Signal/src/views/InboxFeedFooterCell.h create mode 100644 Signal/src/views/InboxFeedFooterCell.m create mode 100644 Signal/src/views/InboxFeedFooterCell.xib create mode 100644 Signal/src/views/InboxFeedTableViewCell.h create mode 100644 Signal/src/views/InboxFeedTableViewCell.m create mode 100644 Signal/src/views/InteractiveLabel.h create mode 100644 Signal/src/views/InteractiveLabel.m create mode 100644 Signal/src/views/LeftSideMenuCell.h create mode 100644 Signal/src/views/LeftSideMenuCell.m create mode 100644 Signal/src/views/LocalizableCustomFontLabel.h create mode 100644 Signal/src/views/LocalizableCustomFontLabel.m create mode 100644 Signal/src/views/PreferenceListTableViewCell.h create mode 100644 Signal/src/views/PreferenceListTableViewCell.m create mode 100644 Signal/src/views/PreferenceListTableViewCell.xib create mode 100644 Signal/src/views/SearchBarTitleView.h create mode 100644 Signal/src/views/SearchBarTitleView.m create mode 100644 Signal/src/views/SettingsTableHeaderView.h create mode 100644 Signal/src/views/SettingsTableHeaderView.m create mode 100644 Signal/src/views/UnseenWhisperUserCell.h create mode 100644 Signal/src/views/UnseenWhisperUserCell.m create mode 100644 Signal/src/views/xibs/CallLogTableViewCell.xib create mode 100644 Signal/src/views/xibs/ContactTableViewCell.xib create mode 100644 Signal/src/views/xibs/CountryCodeTableViewCell.xib create mode 100644 Signal/src/views/xibs/FavouriteTableViewCell.xib create mode 100644 Signal/src/views/xibs/InboxFeedTableViewCell.xib create mode 100644 Signal/src/views/xibs/LeftSideMenuCell.xib create mode 100644 Signal/src/views/xibs/UnseenWhisperUserCell.xib create mode 100644 Signal/test/Supporting Files/SignalTests-Info.plist create mode 100644 Signal/test/TestUtil.h create mode 100644 Signal/test/TestUtil.m create mode 100644 Signal/test/async/AsyncUtilTest.h create mode 100644 Signal/test/async/AsyncUtilTest.m create mode 100644 Signal/test/async/FutureSourceTest.h create mode 100644 Signal/test/async/FutureSourceTest.m create mode 100644 Signal/test/async/ObservableTest.h create mode 100644 Signal/test/async/ObservableTest.m create mode 100644 Signal/test/audio/AudioFrameTest.h create mode 100644 Signal/test/audio/AudioFrameTest.m create mode 100644 Signal/test/audio/AudioRemoteIOTest.h create mode 100644 Signal/test/audio/AudioRemoteIOTest.m create mode 100644 Signal/test/audio/AudioStretcherTest.h create mode 100644 Signal/test/audio/AudioStretcherTest.m create mode 100644 Signal/test/audio/JitterQueueTest.h create mode 100644 Signal/test/audio/JitterQueueTest.m create mode 100644 Signal/test/audio/SpeexCodecTest.h create mode 100644 Signal/test/audio/SpeexCodecTest.m create mode 100644 Signal/test/contact/ContactManagerTest.m create mode 100644 Signal/test/fr.lproj/InfoPlist.strings create mode 100644 Signal/test/network/IpAddressTest.h create mode 100644 Signal/test/network/IpAddressTest.m create mode 100644 Signal/test/network/IpEndPointTest.h create mode 100644 Signal/test/network/IpEndPointTest.m create mode 100644 Signal/test/network/dns/DnsManagerTest.h create mode 100644 Signal/test/network/dns/DnsManagerTest.m create mode 100644 Signal/test/network/http/HttpRequestResponseTest.h create mode 100644 Signal/test/network/http/HttpRequestResponseTest.m create mode 100644 Signal/test/network/rtp/RtpPacketTests.h create mode 100644 Signal/test/network/rtp/RtpPacketTests.m create mode 100644 Signal/test/network/rtp/srtp/SecureStreamTest.h create mode 100644 Signal/test/network/rtp/srtp/SecureStreamTest.m create mode 100644 Signal/test/network/rtp/srtp/SequenceCounterTest.h create mode 100644 Signal/test/network/rtp/srtp/SequenceCounterTest.m create mode 100644 Signal/test/network/rtp/zrtp/DH3KAgreerTest.h create mode 100644 Signal/test/network/rtp/zrtp/DH3KAgreerTest.m create mode 100644 Signal/test/network/rtp/zrtp/EC25AgreerTest.m create mode 100644 Signal/test/network/rtp/zrtp/HandshakePacketTest.h create mode 100644 Signal/test/network/rtp/zrtp/HandshakePacketTest.m create mode 100644 Signal/test/network/rtp/zrtp/HashChainTest.h create mode 100644 Signal/test/network/rtp/zrtp/HashChainTest.m create mode 100644 Signal/test/network/rtp/zrtp/MasterSecretTest.h create mode 100644 Signal/test/network/rtp/zrtp/MasterSecretTest.m create mode 100644 Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.h create mode 100644 Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.m create mode 100644 Signal/test/network/rtp/zrtp/ZrtpTest.h create mode 100644 Signal/test/network/rtp/zrtp/ZrtpTest.m create mode 100644 Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.h create mode 100644 Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.m create mode 100644 Signal/test/network/tcp/LowLatencyConnectorTest.h create mode 100644 Signal/test/network/tcp/LowLatencyConnectorTest.m create mode 100644 Signal/test/network/tcp/tls/NetworkStreamTest.h create mode 100644 Signal/test/network/tcp/tls/NetworkStreamTest.m create mode 100644 Signal/test/network/tcp/tls/SecureEndPointTest.h create mode 100644 Signal/test/network/tcp/tls/SecureEndPointTest.m create mode 100644 Signal/test/network/udp/UdpSocketTest.h create mode 100644 Signal/test/network/udp/UdpSocketTest.m create mode 100644 Signal/test/phone/PhoneNumberTest.h create mode 100644 Signal/test/phone/PhoneNumberTest.m create mode 100644 Signal/test/phone/signaling/SessionDescriptorTest.h create mode 100644 Signal/test/phone/signaling/SessionDescriptorTest.m create mode 100644 Signal/test/profiling/DecayingSampleEstimatorTest.h create mode 100644 Signal/test/profiling/DecayingSampleEstimatorTest.m create mode 100644 Signal/test/profiling/EventWindowTest.h create mode 100644 Signal/test/profiling/EventWindowTest.m create mode 100644 Signal/test/util/BloomFilterTest.h create mode 100644 Signal/test/util/BloomFilterTest.m create mode 100644 Signal/test/util/CancelTokenTest.h create mode 100644 Signal/test/util/CancelTokenTest.m create mode 100644 Signal/test/util/ConversionsTest.h create mode 100644 Signal/test/util/ConversionsTest.m create mode 100644 Signal/test/util/Crc32Test.h create mode 100644 Signal/test/util/Crc32Test.m create mode 100644 Signal/test/util/CryptoUtilTest.h create mode 100644 Signal/test/util/CryptoUtilTest.m create mode 100644 Signal/test/util/CyclicalBufferTest.h create mode 100644 Signal/test/util/CyclicalBufferTest.m create mode 100644 Signal/test/util/ExceptionsTest.h create mode 100644 Signal/test/util/ExceptionsTest.m create mode 100644 Signal/test/util/FunctionalUtilTest.h create mode 100644 Signal/test/util/FunctionalUtilTest.m create mode 100644 Signal/test/util/PriorityQueueTest.h create mode 100644 Signal/test/util/PriorityQueueTest.m create mode 100644 Signal/test/util/QueueTest.h create mode 100644 Signal/test/util/QueueTest.m create mode 100644 Signal/test/util/UtilTest.h create mode 100644 Signal/test/util/UtilTest.m create mode 100644 Signal/translations/ca-ES.lproj/Localizable.strings create mode 100644 Signal/translations/cs-CZ.lproj/Localizable.strings create mode 100644 Signal/translations/de.lproj/Localizable.strings create mode 100644 Signal/translations/en.lproj/Localizable.strings create mode 100644 Signal/translations/fr.lproj/Localizable.strings create mode 100644 Signal/translations/nl.lproj/Localizable.strings create mode 100644 Signal/translations/ro-RO.lproj/Localizable.strings create mode 100644 Signal/translations/sv-SE.lproj/Localizable.strings create mode 100644 Signal/whisperReal.der diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ed686840a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Exclude the build directory +build/* +Pods/* +# Exclude temp nibs and swap files +*~.nib +*.swp +*.lock + +# Exclude OS X folder attributes +.DS_Store + +# Exclude user-specific XCode 3 and 4 files +*.xcworkspace +xcuserdata + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5c17cd8d4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +## Contributor agreement + +Apple requires contributors to iOS projects to relicense their code on submit. We'll have to have individual contributors sign something to enable this. + +Our volunteer legal have put together a form you can sign electronically. So no scanning, faxing, or carrier pigeons involved. How modern: +https://whispersystems.org/cla/ + +Please go ahead and sign, putting your github username in "Address line #2", so that we can accept your pull requests at our heart's delight. + +## Code Conventions + +We are trying to follow the [GitHub code conventions for Objective-C](https://github.com/github/objective-c-conventions) and we appreciate that pull requests do conform with those conventions. + +In addition to that, always add curly braces to your `if` conditionals, even if there is no `else`. Booleans should be declared according to their Objective-C definition, and hence take `YES` or `NO` as values. + +One note, for programmers joining us from Java or similar language communities, note that [exceptions are not commonly used for errors that may occur in normal use](http://stackoverflow.com/questions/324284/throwing-an-exception-in-objective-c-cocoa/324805#324805) so familiarize yourself with **NSError**. + +### UI conventions +We prefer to use [Storyboards](https://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-CocoaApp/Storyboard.html) vs. building UI elements within the code itself. We are not at the stage to provide a .strings localizable file for translating, but the goal is to have translatable strings in a single entry point so that we can reach users in their native language wherever possible. + +## Tabs vs Spaces + +It's the eternal debate. We chose to adopt spaces. Please set your default Xcode configuration to 4 spaces for tabs, and 4 spaces for indentation (it's Xcode's default setting). + +![Tabs vs Spaces](http://cl.ly/TYPZ/Screen%20Shot%202014-01-26%20at%2019.02.28.png) + +If you don't agree with us, you can use the [ClangFormat Xcode plugin](https://github.com/travisjeffery/ClangFormat-Xcode) to code with your favorite indentation style! + +## BitHub + +Open Whisper Systems is currently [experimenting](https://whispersystems.org/blog/bithub/) with the funding privacy Free and Open Source software. For example, this is the current Open WhisperSystems payout per commit, rendered dynamically as an image by the Open WhisperSystems BitHub instance: + +[![Bithub Payment Amount](https://bithub.herokuapp.com/v1/status/payment/commit)](https://whispersystems.org/blog/bithub/) + +## Contributors + +Signal wouldn’t be possible without the many open-source projects we depend on. Big shoutout to the maintainers of all the [pods](https://github.com/WhisperSystems/Signal-iOS/blob/master/Podfile) we use! diff --git a/Libraries/ProtocolBuffers/AbstractMessage.h b/Libraries/ProtocolBuffers/AbstractMessage.h new file mode 100644 index 000000000..4c99f6fd1 --- /dev/null +++ b/Libraries/ProtocolBuffers/AbstractMessage.h @@ -0,0 +1,27 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Message.h" + +/** + * A partial implementation of the {@link Message} interface which implements + * as many methods of that interface as possible in terms of other methods. + * + * @author Cyrus Najmabadi + */ +@interface PBAbstractMessage : NSObject { +@private +} + +@end diff --git a/Libraries/ProtocolBuffers/AbstractMessage.m b/Libraries/ProtocolBuffers/AbstractMessage.m new file mode 100644 index 000000000..27ed6ea32 --- /dev/null +++ b/Libraries/ProtocolBuffers/AbstractMessage.m @@ -0,0 +1,74 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "AbstractMessage.h" + +#import "CodedOutputStream.h" + +@implementation PBAbstractMessage + +- (id) init { + if ((self = [super init])) { + } + + return self; +} + + +- (NSData*) data { + NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)self.serializedSize]; + PBCodedOutputStream* stream = [PBCodedOutputStream streamWithData:data]; + [self writeToCodedOutputStream:stream]; + return data; +} + + +- (BOOL) isInitialized { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (int32_t) serializedSize { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (void) writeToOutputStream:(NSOutputStream*) output { + PBCodedOutputStream* codedOutput = [PBCodedOutputStream streamWithOutputStream:output]; + [self writeToCodedOutputStream:codedOutput]; + [codedOutput flush]; +} + + +- (id) defaultInstance { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (PBUnknownFieldSet*) unknownFields { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) builder { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +@end diff --git a/Libraries/ProtocolBuffers/AbstractMessage_Builder.h b/Libraries/ProtocolBuffers/AbstractMessage_Builder.h new file mode 100644 index 000000000..d5b764b20 --- /dev/null +++ b/Libraries/ProtocolBuffers/AbstractMessage_Builder.h @@ -0,0 +1,25 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Message_Builder.h" + +/** + * A partial implementation of the {@link Message.Builder} interface which + * implements as many methods of that interface as possible in terms of + * other methods. + */ +@interface PBAbstractMessage_Builder : NSObject { +} + +@end diff --git a/Libraries/ProtocolBuffers/AbstractMessage_Builder.m b/Libraries/ProtocolBuffers/AbstractMessage_Builder.m new file mode 100644 index 000000000..0f32ff7c5 --- /dev/null +++ b/Libraries/ProtocolBuffers/AbstractMessage_Builder.m @@ -0,0 +1,119 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "AbstractMessage_Builder.h" + +#import "CodedInputStream.h" +#import "ExtensionRegistry.h" +#import "UnknownFieldSet.h" +#import "UnknownFieldSet_Builder.h" + + +@implementation PBAbstractMessage_Builder + +- (id) clone { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) clear { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} + + +- (id) mergeFromCodedInputStream:(PBCodedInputStream*) input + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) mergeUnknownFields:(PBUnknownFieldSet*) unknownFields { + PBUnknownFieldSet* merged = + [[[PBUnknownFieldSet builderWithUnknownFields:self.unknownFields] + mergeUnknownFields:unknownFields] build]; + + [self setUnknownFields:merged]; + return self; +} + + +- (id) mergeFromData:(NSData*) data { + PBCodedInputStream* input = [PBCodedInputStream streamWithData:data]; + [self mergeFromCodedInputStream:input]; + [input checkLastTagWas:0]; + return self; +} + + +- (id) mergeFromData:(NSData*) data + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBCodedInputStream* input = [PBCodedInputStream streamWithData:data]; + [self mergeFromCodedInputStream:input extensionRegistry:extensionRegistry]; + [input checkLastTagWas:0]; + return self; +} + + +- (id) mergeFromInputStream:(NSInputStream*) input { + PBCodedInputStream* codedInput = [PBCodedInputStream streamWithInputStream:input]; + [self mergeFromCodedInputStream:codedInput]; + [codedInput checkLastTagWas:0]; + return self; +} + + +- (id) mergeFromInputStream:(NSInputStream*) input + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBCodedInputStream* codedInput = [PBCodedInputStream streamWithInputStream:input]; + [self mergeFromCodedInputStream:codedInput extensionRegistry:extensionRegistry]; + [codedInput checkLastTagWas:0]; + return self; +} + + +- (id) build { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) buildPartial { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (BOOL) isInitialized { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) defaultInstance { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (PBUnknownFieldSet*) unknownFields { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (id) setUnknownFields:(PBUnknownFieldSet*) unknownFields { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + +@end diff --git a/Libraries/ProtocolBuffers/Bootstrap.h b/Libraries/ProtocolBuffers/Bootstrap.h new file mode 100644 index 000000000..42aa71a6f --- /dev/null +++ b/Libraries/ProtocolBuffers/Bootstrap.h @@ -0,0 +1,27 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ForwardDeclarations.h" + +#import "CodedInputStream.h" +#import "CodedOutputStream.h" +#import "ExtendableMessage.h" +#import "ExtendableMessage_Builder.h" +#import "ExtensionRegistry.h" +#import "GeneratedMessage.h" +#import "GeneratedMessage_Builder.h" +#import "Message_Builder.h" +#import "UnknownFieldSet.h" +#import "UnknownFieldSet_Builder.h" +#import "Utilities.h" diff --git a/Libraries/ProtocolBuffers/CodedInputStream.h b/Libraries/ProtocolBuffers/CodedInputStream.h new file mode 100644 index 000000000..f992ce380 --- /dev/null +++ b/Libraries/ProtocolBuffers/CodedInputStream.h @@ -0,0 +1,184 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBExtensionRegistry; +@class PBUnknownFieldSet_Builder; +@protocol PBMessage_Builder; + +/** + * Reads and decodes protocol message fields. + * + * This class contains two kinds of methods: methods that read specific + * protocol message constructs and field types (e.g. {@link #readTag()} and + * {@link #readInt32()}) and methods that read low-level values (e.g. + * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading + * encoded protocol messages, you should use the former methods, but if you are + * reading some other format of your own design, use the latter. + * + * @author Cyrus Najmabadi + */ +@interface PBCodedInputStream : NSObject { +@private + NSMutableData* buffer; + int32_t bufferSize; + int32_t bufferSizeAfterLimit; + int32_t bufferPos; + NSInputStream* input; + int32_t lastTag; + + /** + * The total number of bytes read before the current buffer. The total + * bytes read up to the current position can be computed as + * {@code totalBytesRetired + bufferPos}. + */ + int32_t totalBytesRetired; + + /** The absolute position of the end of the current message. */ + int32_t currentLimit; + + /** See setRecursionLimit() */ + int32_t recursionDepth; + int32_t recursionLimit; + + /** See setSizeLimit() */ + int32_t sizeLimit; +} + ++ (PBCodedInputStream*) streamWithData:(NSData*) data; ++ (PBCodedInputStream*) streamWithInputStream:(NSInputStream*) input; + +/** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ +- (int32_t) readTag; +- (BOOL) refillBuffer:(BOOL) mustSucceed; + +- (Float64) readDouble; +- (Float32) readFloat; +- (int64_t) readUInt64; +- (int32_t) readUInt32; +- (int64_t) readInt64; +- (int32_t) readInt32; +- (int64_t) readFixed64; +- (int32_t) readFixed32; +- (int32_t) readEnum; +- (int32_t) readSFixed32; +- (int64_t) readSFixed64; +- (int32_t) readSInt32; +- (int64_t) readSInt64; + +/** + * Read one byte from the input. + * + * @throws InvalidProtocolBuffer The end of the stream or the current + * limit was reached. + */ +- (int8_t) readRawByte; + +/** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ +- (int32_t) readRawVarint32; +- (int64_t) readRawVarint64; +- (int32_t) readRawLittleEndian32; +- (int64_t) readRawLittleEndian64; + +/** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBuffer The end of the stream or the current + * limit was reached. + */ +- (NSData*) readRawData:(int32_t) size; + +/** + * Reads and discards a single field, given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ +- (BOOL) skipField:(int32_t) tag; + + +/** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBuffer The end of the stream or the current + * limit was reached. + */ +- (void) skipRawData:(int32_t) size; + +/** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ +- (void) skipMessage; + +- (BOOL) isAtEnd; +- (int32_t) pushLimit:(int32_t) byteLimit; +- (void) recomputeBufferSizeAfterLimit; +- (void) popLimit:(int32_t) oldLimit; +- (int32_t) bytesUntilLimit; + +/** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int. + * @return A signed 32-bit integer. + */ +int32_t decodeZigZag32(int32_t n); + +/** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int. + * @return A signed 64-bit integer. + */ +int64_t decodeZigZag64(int64_t n); + +/** Read an embedded message field value from the stream. */ +- (void) readMessage:(id) builder extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) readBool; +- (NSString*) readString; +- (NSData*) readData; + +- (void) readGroup:(int32_t) fieldNumber builder:(id) builder extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +/** + * Reads a {@code group} field value from the stream and merges it into the + * given {@link UnknownFieldSet}. + */ +- (void) readUnknownGroup:(int32_t) fieldNumber builder:(PBUnknownFieldSet_Builder*) builder; + +/** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBuffer {@code value} does not match the + * last tag. + */ +- (void) checkLastTagWas:(int32_t) value; + +@end diff --git a/Libraries/ProtocolBuffers/CodedInputStream.m b/Libraries/ProtocolBuffers/CodedInputStream.m new file mode 100644 index 000000000..46ff9c852 --- /dev/null +++ b/Libraries/ProtocolBuffers/CodedInputStream.m @@ -0,0 +1,790 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "CodedInputStream.h" + +#import "Message_Builder.h" +#import "Utilities.h" +#import "WireFormat.h" +#import "UnknownFieldSet_Builder.h" + +@interface PBCodedInputStream () +@property (retain) NSMutableData* buffer; +@property (retain) NSInputStream* input; +@end + + +@implementation PBCodedInputStream + +const int32_t DEFAULT_RECURSION_LIMIT = 64; +const int32_t DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB +const int32_t BUFFER_SIZE = 4096; + +@synthesize buffer; +@synthesize input; + +- (void) dealloc { + [input close]; + self.buffer = nil; + self.input = nil; +} + + +- (void) commonInit { + currentLimit = INT_MAX; + recursionLimit = DEFAULT_RECURSION_LIMIT; + sizeLimit = DEFAULT_SIZE_LIMIT; +} + + +- (id) initWithData:(NSData*) data { + if ((self = [super init])) { + self.buffer = [NSMutableData dataWithData:data]; + bufferSize = (int32_t)buffer.length; + self.input = nil; + [self commonInit]; + } + + return self; +} + + +- (id) initWithInputStream:(NSInputStream*) input_ { + if ((self = [super init])) { + self.buffer = [NSMutableData dataWithLength:BUFFER_SIZE]; + bufferSize = 0; + self.input = input_; + [input open]; + [self commonInit]; + } + + return self; +} + + ++ (PBCodedInputStream*) streamWithData:(NSData*) data { + return [[PBCodedInputStream alloc] initWithData:data]; +} + + ++ (PBCodedInputStream*) streamWithInputStream:(NSInputStream*) input { + return [[PBCodedInputStream alloc] initWithInputStream:input]; +} + + +/** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ +- (int32_t) readTag { + if (self.isAtEnd) { + lastTag = 0; + return 0; + } + + lastTag = [self readRawVarint32]; + if (lastTag == 0) { + // If we actually read zero, that's not a valid tag. + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid Tag" userInfo:nil]; + } + return lastTag; +} + +/** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBufferException {@code value} does not match the + * last tag. + */ +- (void) checkLastTagWas:(int32_t) value { + if (lastTag != value) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid End Tag" userInfo:nil]; + } +} + +/** + * Reads and discards a single field, given its tag value. + * + * @return {@code NO} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code YES}. + */ +- (BOOL) skipField:(int32_t) tag { + switch (PBWireFormatGetTagWireType(tag)) { + case PBWireFormatVarint: + [self readInt32]; + return YES; + case PBWireFormatFixed64: + [self readRawLittleEndian64]; + return YES; + case PBWireFormatLengthDelimited: + [self skipRawData:[self readRawVarint32]]; + return YES; + case PBWireFormatStartGroup: + [self skipMessage]; + [self checkLastTagWas: + PBWireFormatMakeTag(PBWireFormatGetTagFieldNumber(tag), + PBWireFormatEndGroup)]; + return YES; + case PBWireFormatEndGroup: + return NO; + case PBWireFormatFixed32: + [self readRawLittleEndian32]; + return YES; + default: + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid Wire Type" userInfo:nil]; + } +} + + +/** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ +- (void) skipMessage { + while (YES) { + int32_t tag = [self readTag]; + if (tag == 0 || ![self skipField:tag]) { + return; + } + } +} + + +/** Read a {@code double} field value from the stream. */ +- (Float64) readDouble { + return convertInt64ToFloat64([self readRawLittleEndian64]); +} + + +/** Read a {@code float} field value from the stream. */ +- (Float32) readFloat { + return convertInt32ToFloat32([self readRawLittleEndian32]); +} + + +/** Read a {@code uint64} field value from the stream. */ +- (int64_t) readUInt64 { + return [self readRawVarint64]; +} + + +/** Read an {@code int64} field value from the stream. */ +- (int64_t) readInt64 { + return [self readRawVarint64]; +} + + +/** Read an {@code int32} field value from the stream. */ +- (int32_t) readInt32 { + return [self readRawVarint32]; +} + + +/** Read a {@code fixed64} field value from the stream. */ +- (int64_t) readFixed64 { + return [self readRawLittleEndian64]; +} + + +/** Read a {@code fixed32} field value from the stream. */ +- (int32_t) readFixed32 { + return [self readRawLittleEndian32]; +} + + +/** Read a {@code bool} field value from the stream. */ +- (BOOL) readBool { + return [self readRawVarint32] != 0; +} + + +/** Read a {@code string} field value from the stream. */ +- (NSString*) readString { + int32_t size = [self readRawVarint32]; + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + // new String(buffer, bufferPos, size, "UTF-8"); + NSString* result = [[NSString alloc] initWithBytes:(((uint8_t*) buffer.bytes) + bufferPos) + length:(NSUInteger)size + encoding:NSUTF8StringEncoding]; + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + NSData* data = [self readRawData:size]; + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } +} + + +/** Read a {@code group} field value from the stream. */ +- (void) readGroup:(int32_t) fieldNumber + builder:(id) builder + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + if (recursionDepth >= recursionLimit) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil]; + } + ++recursionDepth; + [builder mergeFromCodedInputStream:self extensionRegistry:extensionRegistry]; + [self checkLastTagWas:PBWireFormatMakeTag(fieldNumber, PBWireFormatEndGroup)]; + --recursionDepth; +} + + +/** + * Reads a {@code group} field value from the stream and merges it into the + * given {@link PBUnknownFieldSet}. + */ +- (void) readUnknownGroup:(int32_t) fieldNumber + builder:(PBUnknownFieldSet_Builder*) builder { + if (recursionDepth >= recursionLimit) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil]; + } + ++recursionDepth; + [builder mergeFromCodedInputStream:self]; + [self checkLastTagWas:PBWireFormatMakeTag(fieldNumber, PBWireFormatEndGroup)]; + --recursionDepth; +} + + +/** Read an embedded message field value from the stream. */ +- (void) readMessage:(id) builder + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + int32_t length = [self readRawVarint32]; + if (recursionDepth >= recursionLimit) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil]; + } + int32_t oldLimit = [self pushLimit:length]; + ++recursionDepth; + [builder mergeFromCodedInputStream:self extensionRegistry:extensionRegistry]; + [self checkLastTagWas:0]; + --recursionDepth; + [self popLimit:oldLimit]; +} + + +/** Read a {@code bytes} field value from the stream. */ +- (NSData*) readData { + int32_t size = [self readRawVarint32]; + if (size < bufferSize - bufferPos && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + NSData* result = [NSData dataWithBytes:(((uint8_t*) buffer.bytes) + bufferPos) length:(NSUInteger)size]; + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return [self readRawData:size]; + } +} + + +/** Read a {@code uint32} field value from the stream. */ +- (int32_t) readUInt32 { + return [self readRawVarint32]; +} + + +/** + * Read an enum field value from the stream. Caller is responsible + * for converting the numeric value to an actual enum. + */ +- (int32_t) readEnum { + return [self readRawVarint32]; +} + + +/** Read an {@code sfixed32} field value from the stream. */ +- (int32_t) readSFixed32 { + return [self readRawLittleEndian32]; +} + + +/** Read an {@code sfixed64} field value from the stream. */ +- (int64_t) readSFixed64 { + return [self readRawLittleEndian64]; +} + + +/** Read an {@code sint32} field value from the stream. */ +- (int32_t) readSInt32 { + return decodeZigZag32([self readRawVarint32]); +} + + +/** Read an {@code sint64} field value from the stream. */ +- (int64_t) readSInt64 { + return decodeZigZag64([self readRawVarint64]); +} + + +// ================================================================= + +/** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ +- (int32_t) readRawVarint32 { + int8_t tmp = [self readRawByte]; + if (tmp >= 0) { + return tmp; + } + int32_t result = tmp & 0x7f; + if ((tmp = [self readRawByte]) >= 0) { + result |= tmp << 7; + } else { + result |= (tmp & 0x7f) << 7; + if ((tmp = [self readRawByte]) >= 0) { + result |= tmp << 14; + } else { + result |= (tmp & 0x7f) << 14; + if ((tmp = [self readRawByte]) >= 0) { + result |= tmp << 21; + } else { + result |= (tmp & 0x7f) << 21; + result |= (tmp = [self readRawByte]) << 28; + if (tmp < 0) { + // Discard upper 32 bits. + for (int i = 0; i < 5; i++) { + if ([self readRawByte] >= 0) { + return result; + } + } + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"malformedVarint" userInfo:nil]; + } + } + } + } + return result; +} + + +/** Read a raw Varint from the stream. */ +- (int64_t) readRawVarint64 { + int32_t shift = 0; + int64_t result = 0; + while (shift < 64) { + int8_t b = [self readRawByte]; + result |= (int64_t)(b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + shift += 7; + } + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"malformedVarint" userInfo:nil]; +} + + +/** Read a 32-bit little-endian integer from the stream. */ +- (int32_t) readRawLittleEndian32 { + int8_t b1 = [self readRawByte]; + int8_t b2 = [self readRawByte]; + int8_t b3 = [self readRawByte]; + int8_t b4 = [self readRawByte]; + return + (((int32_t)b1 & 0xff) ) | + (((int32_t)b2 & 0xff) << 8) | + (((int32_t)b3 & 0xff) << 16) | + (((int32_t)b4 & 0xff) << 24); +} + + +/** Read a 64-bit little-endian integer from the stream. */ +- (int64_t) readRawLittleEndian64 { + int8_t b1 = [self readRawByte]; + int8_t b2 = [self readRawByte]; + int8_t b3 = [self readRawByte]; + int8_t b4 = [self readRawByte]; + int8_t b5 = [self readRawByte]; + int8_t b6 = [self readRawByte]; + int8_t b7 = [self readRawByte]; + int8_t b8 = [self readRawByte]; + return + (((int64_t)b1 & 0xff) ) | + (((int64_t)b2 & 0xff) << 8) | + (((int64_t)b3 & 0xff) << 16) | + (((int64_t)b4 & 0xff) << 24) | + (((int64_t)b5 & 0xff) << 32) | + (((int64_t)b6 & 0xff) << 40) | + (((int64_t)b7 & 0xff) << 48) | + (((int64_t)b8 & 0xff) << 56); +} + + +/** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int + * @return A signed 32-bit integer. + */ +int32_t decodeZigZag32(int32_t n) { + return logicalRightShift32(n, 1) ^ -(n & 1); +} + + +/** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int + * @return A signed 64-bit integer. + */ +int64_t decodeZigZag64(int64_t n) { + return logicalRightShift64(n, 1) ^ -(n & 1); +} + + +/** + * Set the maximum message recursion depth. In order to prevent malicious + * messages from causing stack overflows, {@code PBCodedInputStream} limits + * how deeply messages may be nested. The default limit is 64. + * + * @return the old limit. + */ +- (int32_t) setRecursionLimit:(int32_t) limit { + if (limit < 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Recursion limit cannot be negative" userInfo:nil]; + } + int32_t oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; +} + + +/** + * Set the maximum message size. In order to prevent malicious + * messages from exhausting memory or causing integer overflows, + * {@code PBCodedInputStream} limits how large a message may be. + * The default limit is 64MB. You should set this limit as small + * as you can without harming your app's functionality. Note that + * size limits only apply when reading from an {@code InputStream}, not + * when constructed around a raw byte array. + * + * @return the old limit. + */ +- (int32_t) setSizeLimit:(int32_t) limit { + if (limit < 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Size limit cannot be negative:" userInfo:nil]; + } + int32_t oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; +} + + +/** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). + */ +- (void) resetSizeCounter { + totalBytesRetired = 0; +} + + +/** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This + * is called when descending into a length-delimited embedded message. + * + * @return the old limit. + */ +- (int32_t) pushLimit:(int32_t) byteLimit { + if (byteLimit < 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil]; + } + byteLimit += totalBytesRetired + bufferPos; + int32_t oldLimit = currentLimit; + if (byteLimit > oldLimit) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } + currentLimit = byteLimit; + + [self recomputeBufferSizeAfterLimit]; + + return oldLimit; +} + + +- (void) recomputeBufferSizeAfterLimit { + bufferSize += bufferSizeAfterLimit; + int32_t bufferEnd = totalBytesRetired + bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } +} + + +/** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ +- (void) popLimit:(int32_t) oldLimit { + currentLimit = oldLimit; + [self recomputeBufferSizeAfterLimit]; +} + + +/** + * Returns the number of bytes to be read before the current limit. + * If no limit is set, returns -1. + */ +- (int32_t) bytesUntilLimit { + if (currentLimit == INT_MAX) { + return -1; + } + + int32_t currentAbsolutePosition = totalBytesRetired + bufferPos; + return currentLimit - currentAbsolutePosition; +} + +/** + * Returns true if the stream has reached the end of the input. This is the + * case if either the end of the underlying input source has been reached or + * if the stream has reached a limit created using {@link #pushLimit(int)}. + */ +- (BOOL) isAtEnd { + return bufferPos == bufferSize && ![self refillBuffer:NO]; +} + + +/** + * Called with {@code this.buffer} is empty to read more bytes from the + * input. If {@code mustSucceed} is YES, refillBuffer() gurantees that + * either there will be at least one byte in the buffer when it returns + * or it will throw an exception. If {@code mustSucceed} is NO, + * refillBuffer() returns NO if no more bytes were available. + */ +- (BOOL) refillBuffer:(BOOL) mustSucceed { + if (bufferPos < bufferSize) { + @throw [NSException exceptionWithName:@"IllegalState" reason:@"refillBuffer called when buffer wasn't empty." userInfo:nil]; + } + + if (totalBytesRetired + bufferSize == currentLimit) { + // Oops, we hit a limit. + if (mustSucceed) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } else { + return NO; + } + } + + totalBytesRetired += bufferSize; + + // TODO(cyrusn): does NSInputStream behave the same as java.io.InputStream + // when there is no more data? + bufferPos = 0; + bufferSize = 0; + if (input != nil) { + bufferSize = [input read:buffer.mutableBytes maxLength:buffer.length]; + } + + if (bufferSize <= 0) { + bufferSize = 0; + if (mustSucceed) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } else { + return NO; + } + } else { + [self recomputeBufferSizeAfterLimit]; + int32_t totalBytesRead = totalBytesRetired + bufferSize + bufferSizeAfterLimit; + if (totalBytesRead > sizeLimit || totalBytesRead < 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"sizeLimitExceeded" userInfo:nil]; + } + return YES; + } +} + + +/** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ +- (int8_t) readRawByte { + if (bufferPos == bufferSize) { + [self refillBuffer:YES]; + } + int8_t* bytes = (int8_t*)buffer.bytes; + return bytes[bufferPos++]; +} + + +/** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ +- (NSData*) readRawData:(int32_t) size { + if (size < 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil]; + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + [self skipRawData:currentLimit - totalBytesRetired - bufferPos]; + // Then fail. + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + NSData* data = [NSData dataWithBytes:(((int8_t*) buffer.bytes) + bufferPos) length:(NSUInteger)size]; + bufferPos += size; + return data; + } else if (size < BUFFER_SIZE) { + // Reading more bytes than are in the buffer, but not an excessive number + // of bytes. We can safely allocate the resulting array ahead of time. + + // First copy what we have. + NSMutableData* bytes = [NSMutableData dataWithLength:(NSUInteger)size]; + int32_t pos = bufferSize - bufferPos; + memcpy(bytes.mutableBytes, ((int8_t*)buffer.bytes) + bufferPos, pos); + bufferPos = bufferSize; + + // We want to use refillBuffer() and then copy from the buffer into our + // byte array rather than reading directly into our byte array because + // the input may be unbuffered. + [self refillBuffer:YES]; + + while (size - pos > bufferSize) { + memcpy(((int8_t*)bytes.mutableBytes) + pos, buffer.bytes, bufferSize); + pos += bufferSize; + bufferPos = bufferSize; + [self refillBuffer:YES]; + } + + memcpy(((int8_t*)bytes.mutableBytes) + pos, buffer.bytes, size - pos); + bufferPos = size - pos; + + return bytes; + } else { + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actuall* e* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + + // Remember the buffer markers since we'll have to copy the bytes out of + // it later. + int32_t originalBufferPos = bufferPos; + int32_t originalBufferSize = bufferSize; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + bufferPos = 0; + bufferSize = 0; + + // Read all the rest of the bytes we need. + int32_t sizeLeft = size - (originalBufferSize - originalBufferPos); + NSMutableArray* chunks = [NSMutableArray array]; + + while (sizeLeft > 0) { + NSMutableData* chunk = [NSMutableData dataWithLength:(NSUInteger)MIN(sizeLeft, BUFFER_SIZE)]; + + int32_t pos = 0; + while (pos < (int32_t)chunk.length) { + int32_t n = 0; + if (input != nil) { + n = [input read:(((uint8_t*) chunk.mutableBytes) + pos) maxLength:chunk.length - (NSUInteger)pos]; + } + if (n <= 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } + totalBytesRetired += n; + pos += n; + } + sizeLeft -= chunk.length; + [chunks addObject:chunk]; + } + + // OK, got everything. Now concatenate it all into one buffer. + NSMutableData* bytes = [NSMutableData dataWithLength:(NSUInteger)size]; + + // Start by copying the leftover bytes from this.buffer. + int32_t pos = originalBufferSize - originalBufferPos; + memcpy(bytes.mutableBytes, ((int8_t*)buffer.bytes) + originalBufferPos, pos); + + // And now all the chunks. + for (NSData* chunk in chunks) { + memcpy(((int8_t*)bytes.mutableBytes) + pos, chunk.bytes, chunk.length); + pos += chunk.length; + } + + // Done. + return bytes; + } +} + + +/** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ +- (void) skipRawData:(int32_t) size { + if (size < 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil]; + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + [self skipRawData:currentLimit - totalBytesRetired - bufferPos]; + // Then fail. + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } + + if (size <= (bufferSize - bufferPos)) { + // We have all the bytes we need already. + bufferPos += size; + } else { + // Skipping more bytes than are in the buffer. First skip what we have. + int32_t pos = bufferSize - bufferPos; + totalBytesRetired += pos; + bufferPos = 0; + bufferSize = 0; + + // Then skip directly from the InputStream for the rest. + while (pos < size) { + NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)(size - pos)]; + int32_t n = (input == nil) ? -1 : (int32_t)[input read:data.mutableBytes maxLength:(NSUInteger)(size - pos)]; + if (n <= 0) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil]; + } + pos += n; + totalBytesRetired += n; + } + } +} + + + +@end diff --git a/Libraries/ProtocolBuffers/CodedOutputStream.h b/Libraries/ProtocolBuffers/CodedOutputStream.h new file mode 100644 index 000000000..7f056112c --- /dev/null +++ b/Libraries/ProtocolBuffers/CodedOutputStream.h @@ -0,0 +1,216 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBUnknownFieldSet; +@protocol PBMessage; + +/** + * Encodes and writes protocol message fields. + * + *

This class contains two kinds of methods: methods that write specific + * protocol message constructs and field types (e.g. {@link #writeTag} and + * {@link #writeInt32}) and methods that write low-level values (e.g. + * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are + * writing encoded protocol messages, you should use the former methods, but if + * you are writing some other format of your own design, use the latter. + * + *

This class is totally unsynchronized. + * + * @author Cyrus Najmabadi + */ +@interface PBCodedOutputStream : NSObject { + NSMutableData* buffer; + int32_t position; + NSOutputStream* output; +} + ++ (PBCodedOutputStream*) streamWithData:(NSMutableData*) data; ++ (PBCodedOutputStream*) streamWithOutputStream:(NSOutputStream*) output; ++ (PBCodedOutputStream*) streamWithOutputStream:(NSOutputStream*) output bufferSize:(int32_t) bufferSize; + + +/** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int. + */ +int32_t encodeZigZag32(int32_t n); + +/** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int. + */ +int64_t encodeZigZag64(int64_t n); + +int32_t computeDoubleSize(int32_t fieldNumber, Float64 value); +int32_t computeFloatSize(int32_t fieldNumber, Float32 value); +int32_t computeUInt64Size(int32_t fieldNumber, int64_t value); +int32_t computeInt64Size(int32_t fieldNumber, int64_t value); +int32_t computeInt32Size(int32_t fieldNumber, int32_t value); +int32_t computeFixed64Size(int32_t fieldNumber, int64_t value); +int32_t computeFixed32Size(int32_t fieldNumber, int32_t value); +int32_t computeBoolSize(int32_t fieldNumber, BOOL value); +int32_t computeStringSize(int32_t fieldNumber, NSString* value); +int32_t computeGroupSize(int32_t fieldNumber, id value); +int32_t computeUnknownGroupSize(int32_t fieldNumber, PBUnknownFieldSet* value); +int32_t computeMessageSize(int32_t fieldNumber, id value); +int32_t computeDataSize(int32_t fieldNumber, NSData* value); +int32_t computeUInt32Size(int32_t fieldNumber, int32_t value); +int32_t computeSFixed32Size(int32_t fieldNumber, int32_t value); +int32_t computeSFixed64Size(int32_t fieldNumber, int64_t value); +int32_t computeSInt32Size(int32_t fieldNumber, int32_t value); +int32_t computeSInt64Size(int32_t fieldNumber, int64_t value); +int32_t computeTagSize(int32_t fieldNumber); + +int32_t computeDoubleSizeNoTag(Float64 value); +int32_t computeFloatSizeNoTag(Float32 value); +int32_t computeUInt64SizeNoTag(int64_t value); +int32_t computeInt64SizeNoTag(int64_t value); +int32_t computeInt32SizeNoTag(int32_t value); +int32_t computeFixed64SizeNoTag(int64_t value); +int32_t computeFixed32SizeNoTag(int32_t value); +int32_t computeBoolSizeNoTag(BOOL value); +int32_t computeStringSizeNoTag(NSString* value); +int32_t computeGroupSizeNoTag(id value); +int32_t computeUnknownGroupSizeNoTag(PBUnknownFieldSet* value); +int32_t computeMessageSizeNoTag(id value); +int32_t computeDataSizeNoTag(NSData* value); +int32_t computeUInt32SizeNoTag(int32_t value); +int32_t computeEnumSizeNoTag(int32_t value); +int32_t computeSFixed32SizeNoTag(int32_t value); +int32_t computeSFixed64SizeNoTag(int64_t value); +int32_t computeSInt32SizeNoTag(int32_t value); +int32_t computeSInt64SizeNoTag(int64_t value); + +/** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + */ +int32_t computeRawVarint32Size(int32_t value); +int32_t computeRawVarint64Size(int64_t value); + +/** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +int32_t computeMessageSetExtensionSize(int32_t fieldNumber, id value); + +/** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +int32_t computeRawMessageSetExtensionSize(int32_t fieldNumber, NSData* value); + +/** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ +int32_t computeEnumSize(int32_t fieldNumber, int32_t value); + +/** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying NSOutputStream. + */ +- (void) flush; + +- (void) writeRawByte:(uint8_t) value; + +- (void) writeTag:(int32_t) fieldNumber format:(int32_t) format; + +/** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ +- (void) writeRawVarint32:(int32_t) value; +- (void) writeRawVarint64:(int64_t) value; + +- (void) writeRawLittleEndian32:(int32_t) value; +- (void) writeRawLittleEndian64:(int64_t) value; + +- (void) writeRawData:(NSData*) data; +- (void) writeRawData:(NSData*) data offset:(int32_t) offset length:(int32_t) length; + +- (void) writeData:(int32_t) fieldNumber value:(NSData*) value; + +- (void) writeDouble:(int32_t) fieldNumber value:(Float64) value; +- (void) writeFloat:(int32_t) fieldNumber value:(Float32) value; +- (void) writeUInt64:(int32_t) fieldNumber value:(int64_t) value; +- (void) writeInt64:(int32_t) fieldNumber value:(int64_t) value; +- (void) writeInt32:(int32_t) fieldNumber value:(int32_t) value; +- (void) writeFixed64:(int32_t) fieldNumber value:(int64_t) value; +- (void) writeFixed32:(int32_t) fieldNumber value:(int32_t) value; +- (void) writeBool:(int32_t) fieldNumber value:(BOOL) value; +- (void) writeString:(int32_t) fieldNumber value:(NSString*) value; +- (void) writeGroup:(int32_t) fieldNumber value:(id) value; +- (void) writeUnknownGroup:(int32_t) fieldNumber value:(PBUnknownFieldSet*) value; +- (void) writeMessage:(int32_t) fieldNumber value:(id) value; +- (void) writeUInt32:(int32_t) fieldNumber value:(int32_t) value; +- (void) writeSFixed32:(int32_t) fieldNumber value:(int32_t) value; +- (void) writeSFixed64:(int32_t) fieldNumber value:(int64_t) value; +- (void) writeSInt32:(int32_t) fieldNumber value:(int32_t) value; +- (void) writeSInt64:(int32_t) fieldNumber value:(int64_t) value; + +- (void) writeDoubleNoTag:(Float64) value; +- (void) writeFloatNoTag:(Float32) value; +- (void) writeUInt64NoTag:(int64_t) value; +- (void) writeInt64NoTag:(int64_t) value; +- (void) writeInt32NoTag:(int32_t) value; +- (void) writeFixed64NoTag:(int64_t) value; +- (void) writeFixed32NoTag:(int32_t) value; +- (void) writeBoolNoTag:(BOOL) value; +- (void) writeStringNoTag:(NSString*) value; +- (void) writeGroupNoTag:(int32_t) fieldNumber value:(id) value; +- (void) writeUnknownGroupNoTag:(int32_t) fieldNumber value:(PBUnknownFieldSet*) value; +- (void) writeMessageNoTag:(id) value; +- (void) writeDataNoTag:(NSData*) value; +- (void) writeUInt32NoTag:(int32_t) value; +- (void) writeEnumNoTag:(int32_t) value; +- (void) writeSFixed32NoTag:(int32_t) value; +- (void) writeSFixed64NoTag:(int64_t) value; +- (void) writeSInt32NoTag:(int32_t) value; +- (void) writeSInt64NoTag:(int64_t) value; + + +/** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +- (void) writeMessageSetExtension:(int32_t) fieldNumber value:(id) value; + +/** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +- (void) writeRawMessageSetExtension:(int32_t) fieldNumber value:(NSData*) value; + +/** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ +- (void) writeEnum:(int32_t) fieldNumber value:(int32_t) value; + +@end diff --git a/Libraries/ProtocolBuffers/CodedOutputStream.m b/Libraries/ProtocolBuffers/CodedOutputStream.m new file mode 100644 index 000000000..5968dbd4b --- /dev/null +++ b/Libraries/ProtocolBuffers/CodedOutputStream.m @@ -0,0 +1,974 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "CodedOutputStream.h" + +#import "Message.h" +#import "Utilities.h" +#import "WireFormat.h" +#import "UnknownFieldSet.h" + +@interface PBCodedOutputStream () +@property (retain) NSMutableData* buffer; +@property int32_t position; +@property (retain) NSOutputStream* output; +@end + + +@implementation PBCodedOutputStream + +const int32_t DEFAULT_BUFFER_SIZE = 4096; +const int32_t LITTLE_ENDIAN_32_SIZE = 4; +const int32_t LITTLE_ENDIAN_64_SIZE = 8; + +@synthesize output; +@synthesize buffer; +@synthesize position; + +- (void) dealloc { + [output close]; + self.output = nil; + self.buffer = nil; + self.position = 0; +} + + +- (id) initWithOutputStream:(NSOutputStream*) output_ + data:(NSMutableData*) data_ { + if ((self = [super init])) { + self.output = output_; + self.buffer = data_; + self.position = 0; + + [output open]; + } + + return self; +} + + ++ (PBCodedOutputStream*) streamWithOutputStream:(NSOutputStream*) output + bufferSize:(int32_t) bufferSize { + NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)bufferSize]; + return [[PBCodedOutputStream alloc] initWithOutputStream:output + data:data]; +} + + ++ (PBCodedOutputStream*) streamWithOutputStream:(NSOutputStream*) output { + return [PBCodedOutputStream streamWithOutputStream:output bufferSize:DEFAULT_BUFFER_SIZE]; +} + + ++ (PBCodedOutputStream*) streamWithData:(NSMutableData*) data { + return [[PBCodedOutputStream alloc] initWithOutputStream:nil data:data]; +} + + +- (void) writeDoubleNoTag:(Float64) value { + [self writeRawLittleEndian64:convertFloat64ToInt64(value)]; +} + + +/** Write a {@code double} field, including tag, to the stream. */ +- (void) writeDouble:(int32_t) fieldNumber + value:(Float64) value { + [self writeTag:fieldNumber format:PBWireFormatFixed64]; + [self writeDoubleNoTag:value]; +} + + +- (void) writeFloatNoTag:(Float32) value { + [self writeRawLittleEndian32:convertFloat32ToInt32(value)]; +} + + +/** Write a {@code float} field, including tag, to the stream. */ +- (void) writeFloat:(int32_t) fieldNumber + value:(Float32) value { + [self writeTag:fieldNumber format:PBWireFormatFixed32]; + [self writeFloatNoTag:value]; +} + + +- (void) writeUInt64NoTag:(int64_t) value { + [self writeRawVarint64:value]; +} + + +/** Write a {@code uint64} field, including tag, to the stream. */ +- (void) writeUInt64:(int32_t) fieldNumber + value:(int64_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeUInt64NoTag:value]; +} + + +- (void) writeInt64NoTag:(int64_t) value { + [self writeRawVarint64:value]; +} + + +/** Write an {@code int64} field, including tag, to the stream. */ +- (void) writeInt64:(int32_t) fieldNumber + value:(int64_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeInt64NoTag:value]; +} + + +- (void) writeInt32NoTag:(int32_t) value { + if (value >= 0) { + [self writeRawVarint32:value]; + } else { + // Must sign-extend + [self writeRawVarint64:value]; + } +} + + +/** Write an {@code int32} field, including tag, to the stream. */ +- (void) writeInt32:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeInt32NoTag:value]; +} + + +- (void) writeFixed64NoTag:(int64_t) value { + [self writeRawLittleEndian64:value]; +} + + +/** Write a {@code fixed64} field, including tag, to the stream. */ +- (void) writeFixed64:(int32_t) fieldNumber + value:(int64_t) value { + [self writeTag:fieldNumber format:PBWireFormatFixed64]; + [self writeFixed64NoTag:value]; +} + + +- (void) writeFixed32NoTag:(int32_t) value { + [self writeRawLittleEndian32:value]; +} + + +/** Write a {@code fixed32} field, including tag, to the stream. */ +- (void) writeFixed32:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatFixed32]; + [self writeFixed32NoTag:value]; +} + + +- (void) writeBoolNoTag:(BOOL) value { + [self writeRawByte:(value ? 1 : 0)]; +} + + +/** Write a {@code bool} field, including tag, to the stream. */ +- (void) writeBool:(int32_t) fieldNumber + value:(BOOL) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeBoolNoTag:value]; +} + + +- (void) writeStringNoTag:(NSString*) value { + NSData* data = [value dataUsingEncoding:NSUTF8StringEncoding]; + [self writeRawVarint32:(int32_t)data.length]; + [self writeRawData:data]; +} + + +/** Write a {@code string} field, including tag, to the stream. */ +- (void) writeString:(int32_t) fieldNumber + value:(NSString*) value { + // TODO(cyrusn): we could probably use: + // NSString:getBytes:maxLength:usedLength:encoding:options:range:remainingRange: + // to write directly into our buffer. + [self writeTag:fieldNumber format:PBWireFormatLengthDelimited]; + [self writeStringNoTag:value]; +} + + +- (void) writeGroupNoTag:(int32_t) fieldNumber + value:(id) value { + [value writeToCodedOutputStream:self]; + [self writeTag:fieldNumber format:PBWireFormatEndGroup]; +} + + +/** Write a {@code group} field, including tag, to the stream. */ +- (void) writeGroup:(int32_t) fieldNumber + value:(id) value { + [self writeTag:fieldNumber format:PBWireFormatStartGroup]; + [self writeGroupNoTag:fieldNumber value:value]; +} + + +- (void) writeUnknownGroupNoTag:(int32_t) fieldNumber + value:(PBUnknownFieldSet*) value { + [value writeToCodedOutputStream:self]; + [self writeTag:fieldNumber format:PBWireFormatEndGroup]; +} + + +/** Write a group represented by an {@link PBUnknownFieldSet}. */ +- (void) writeUnknownGroup:(int32_t) fieldNumber + value:(PBUnknownFieldSet*) value { + [self writeTag:fieldNumber format:PBWireFormatStartGroup]; + [self writeUnknownGroupNoTag:fieldNumber value:value]; +} + + +- (void) writeMessageNoTag:(id) value { + [self writeRawVarint32:[value serializedSize]]; + [value writeToCodedOutputStream:self]; +} + + +/** Write an embedded message field, including tag, to the stream. */ +- (void) writeMessage:(int32_t) fieldNumber + value:(id) value { + [self writeTag:fieldNumber format:PBWireFormatLengthDelimited]; + [self writeMessageNoTag:value]; +} + + +- (void) writeDataNoTag:(NSData*) value { + [self writeRawVarint32:(int32_t)value.length]; + [self writeRawData:value]; +} + + +/** Write a {@code bytes} field, including tag, to the stream. */ +- (void) writeData:(int32_t) fieldNumber value:(NSData*) value { + [self writeTag:fieldNumber format:PBWireFormatLengthDelimited]; + [self writeDataNoTag:value]; +} + + +- (void) writeUInt32NoTag:(int32_t) value { + [self writeRawVarint32:value]; +} + + +/** Write a {@code uint32} field, including tag, to the stream. */ +- (void) writeUInt32:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeUInt32NoTag:value]; +} + + +- (void) writeEnumNoTag:(int32_t) value { + [self writeRawVarint32:value]; +} + + +/** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ +- (void) writeEnum:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeEnumNoTag:value]; +} + + +- (void) writeSFixed32NoTag:(int32_t) value { + [self writeRawLittleEndian32:value]; +} + + +/** Write an {@code sfixed32} field, including tag, to the stream. */ +- (void) writeSFixed32:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatFixed32]; + [self writeSFixed32NoTag:value]; +} + + +- (void) writeSFixed64NoTag:(int64_t) value { + [self writeRawLittleEndian64:value]; +} + + +/** Write an {@code sfixed64} field, including tag, to the stream. */ +- (void) writeSFixed64:(int32_t) fieldNumber + value:(int64_t) value { + [self writeTag:fieldNumber format:PBWireFormatFixed64]; + [self writeSFixed64NoTag:value]; +} + + +- (void) writeSInt32NoTag:(int32_t) value { + [self writeRawVarint32:encodeZigZag32(value)]; +} + + +/** Write an {@code sint32} field, including tag, to the stream. */ +- (void) writeSInt32:(int32_t) fieldNumber + value:(int32_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeSInt32NoTag:value]; +} + + +- (void) writeSInt64NoTag:(int64_t) value { + [self writeRawVarint64:encodeZigZag64(value)]; +} + + +/** Write an {@code sint64} field, including tag, to the stream. */ +- (void) writeSInt64:(int32_t) fieldNumber + value:(int64_t) value { + [self writeTag:fieldNumber format:PBWireFormatVarint]; + [self writeSInt64NoTag:value]; +} + + +/** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +- (void) writeMessageSetExtension:(int32_t) fieldNumber + value:(id) value { + [self writeTag:PBWireFormatMessageSetItem format:PBWireFormatStartGroup]; + [self writeUInt32:PBWireFormatMessageSetTypeId value:fieldNumber]; + [self writeMessage:PBWireFormatMessageSetMessage value:value]; + [self writeTag:PBWireFormatMessageSetItem format:PBWireFormatEndGroup]; +} + + +/** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +- (void) writeRawMessageSetExtension:(int32_t) fieldNumber + value:(NSData*) value { + [self writeTag:PBWireFormatMessageSetItem format:PBWireFormatStartGroup]; + [self writeUInt32:PBWireFormatMessageSetTypeId value:fieldNumber]; + [self writeData:PBWireFormatMessageSetMessage value:value]; + [self writeTag:PBWireFormatMessageSetItem format:PBWireFormatEndGroup]; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ +int32_t computeDoubleSizeNoTag(Float64 value) { + return LITTLE_ENDIAN_64_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ +int32_t computeFloatSizeNoTag(Float32 value) { + return LITTLE_ENDIAN_32_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ +int32_t computeUInt64SizeNoTag(int64_t value) { + return computeRawVarint64Size(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ +int32_t computeInt64SizeNoTag(int64_t value) { + return computeRawVarint64Size(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ +int32_t computeInt32SizeNoTag(int32_t value) { + if (value >= 0) { + return computeRawVarint32Size(value); + } else { + // Must sign-extend. + return 10; + } +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ +int32_t computeFixed64SizeNoTag(int64_t value) { + return LITTLE_ENDIAN_64_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ +int32_t computeFixed32SizeNoTag(int32_t value) { + return LITTLE_ENDIAN_32_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ +int32_t computeBoolSizeNoTag(BOOL value) { + return 1; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ +int32_t computeStringSizeNoTag(NSString* value) { + NSData* data = [value dataUsingEncoding:NSUTF8StringEncoding]; + return computeRawVarint32Size((int32_t)data.length) + (int32_t)data.length; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + */ +int32_t computeGroupSizeNoTag(id value) { + return [value serializedSize]; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code group} field represented by an {@code PBUnknownFieldSet}, including + * tag. + */ +int32_t computeUnknownGroupSizeNoTag(PBUnknownFieldSet* value) { + return value.serializedSize; +} + + +/** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ +int32_t computeMessageSizeNoTag(id value) { + int32_t size = [value serializedSize]; + return computeRawVarint32Size(size) + size; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ +int32_t computeDataSizeNoTag(NSData* value) { + return computeRawVarint32Size((int32_t)value.length) + (int32_t)value.length; +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ +int32_t computeUInt32SizeNoTag(int32_t value) { + return computeRawVarint32Size(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ +int32_t computeEnumSizeNoTag(int32_t value) { + return computeRawVarint32Size(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ +int32_t computeSFixed32SizeNoTag(int32_t value) { + return LITTLE_ENDIAN_32_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ +int32_t computeSFixed64SizeNoTag(int64_t value) { + return LITTLE_ENDIAN_64_SIZE; +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ +int32_t computeSInt32SizeNoTag(int32_t value) { + return computeRawVarint32Size(encodeZigZag32(value)); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ +int32_t computeSInt64SizeNoTag(int64_t value) { + return computeRawVarint64Size(encodeZigZag64(value)); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ +int32_t computeDoubleSize(int32_t fieldNumber, Float64 value) { + return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ +int32_t computeFloatSize(int32_t fieldNumber, Float32 value) { + return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ +int32_t computeUInt64Size(int32_t fieldNumber, int64_t value) { + return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ +int32_t computeInt64Size(int32_t fieldNumber, int64_t value) { + return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ +int32_t computeInt32Size(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ +int32_t computeFixed64Size(int32_t fieldNumber, int64_t value) { + return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ +int32_t computeFixed32Size(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ +int32_t computeBoolSize(int32_t fieldNumber, BOOL value) { + return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ +int32_t computeStringSize(int32_t fieldNumber, NSString* value) { + return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + */ +int32_t computeGroupSize(int32_t fieldNumber, id value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code group} field represented by an {@code PBUnknownFieldSet}, including + * tag. + */ +int32_t computeUnknownGroupSize(int32_t fieldNumber, + PBUnknownFieldSet* value) { + return computeTagSize(fieldNumber) * 2 + computeUnknownGroupSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ +int32_t computeMessageSize(int32_t fieldNumber, id value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ +int32_t computeDataSize(int32_t fieldNumber, NSData* value) { + return computeTagSize(fieldNumber) + computeDataSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ +int32_t computeUInt32Size(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ +int32_t computeEnumSize(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ +int32_t computeSFixed32Size(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ +int32_t computeSFixed64Size(int32_t fieldNumber, int64_t value) { + return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ +int32_t computeSInt32Size(int32_t fieldNumber, int32_t value) { + return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ +int32_t computeSInt64Size(int32_t fieldNumber, int64_t value) { + return computeTagSize(fieldNumber) + + computeRawVarint64Size(encodeZigZag64(value)); +} + + +/** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +int32_t computeMessageSetExtensionSize(int32_t fieldNumber, id value) { + return computeTagSize(PBWireFormatMessageSetItem) * 2 + + computeUInt32Size(PBWireFormatMessageSetTypeId, fieldNumber) + + computeMessageSize(PBWireFormatMessageSetMessage, value); +} + + +/** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +int32_t computeRawMessageSetExtensionSize(int32_t fieldNumber, NSData* value) { + return computeTagSize(PBWireFormatMessageSetItem) * 2 + + computeUInt32Size(PBWireFormatMessageSetTypeId, fieldNumber) + + computeDataSize(PBWireFormatMessageSetMessage, value); +} + + +/** + * Internal helper that writes the current buffer to the output. The + * buffer position is reset to its initial value when this returns. + */ +- (void) refreshBuffer { + if (output == nil) { + // We're writing to a single buffer. + @throw [NSException exceptionWithName:@"OutOfSpace" reason:@"" userInfo:nil]; + } + + + [output write:buffer.bytes maxLength:(NSUInteger)position]; + position = 0; +} + + +/** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying OutputStream. + */ +- (void) flush { + if (output != nil) { + [self refreshBuffer]; + } +} + + +/** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ +- (int32_t) spaceLeft { + if (output == nil) { + return (int32_t)buffer.length - position; + } else { + @throw [NSException exceptionWithName:@"UnsupportedOperation" + reason:@"spaceLeft() can only be called on CodedOutputStreams that are writing to a flat array." + userInfo:nil]; + } +} + + +/** + * Verifies that {@link #spaceLeft()} returns zero. It's common to create + * a byte array that is exactly big enough to hold a message, then write to + * it with a {@code PBCodedOutputStream}. Calling {@code checkNoSpaceLeft()} + * after writing verifies that the message was actually as big as expected, + * which can help catch bugs. + */ +- (void) checkNoSpaceLeft { + if (self.spaceLeft != 0) { + @throw [NSException exceptionWithName:@"IllegalState" reason:@"Did not write as much data as expected." userInfo:nil]; + } +} + + +/** Write a single byte. */ +- (void) writeRawByte:(uint8_t) value { + if (position == (int32_t)buffer.length) { + [self refreshBuffer]; + } + + ((uint8_t*)buffer.mutableBytes)[position++] = value; +} + + +/** Write an array of bytes. */ +- (void) writeRawData:(NSData*) data { + [self writeRawData:data offset:0 length:(int32_t)data.length]; +} + + +- (void) writeRawData:(NSData*) value offset:(int32_t) offset length:(int32_t) length { + if ((int32_t)buffer.length - position >= length) { + // We have room in the current buffer. + memcpy(((uint8_t*)buffer.mutableBytes) + position, ((uint8_t*)value.bytes) + offset, length); + position += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and flush. + int32_t bytesWritten = (int32_t)buffer.length - position; + memcpy(((uint8_t*)buffer.mutableBytes) + position, ((uint8_t*)value.bytes) + offset, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = (int32_t)buffer.length; + [self refreshBuffer]; + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= (int32_t)buffer.length) { + // Fits in new buffer. + memcpy((uint8_t*)buffer.mutableBytes, ((uint8_t*)value.bytes) + offset, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + [output write:((uint8_t*) value.bytes) + offset maxLength:(NSUInteger)length]; + } + } +} + + +/** Encode and write a tag. */ +- (void) writeTag:(int32_t) fieldNumber + format:(int32_t) format { + [self writeRawVarint32:PBWireFormatMakeTag(fieldNumber, format)]; +} + + +/** Compute the number of bytes that would be needed to encode a tag. */ +int32_t computeTagSize(int32_t fieldNumber) { + return computeRawVarint32Size(PBWireFormatMakeTag(fieldNumber, 0)); +} + + +/** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ +- (void) writeRawVarint32:(int32_t) value { + while (YES) { + if ((value & ~0x7F) == 0) { + [self writeRawByte:(uint8_t)value]; + return; + } else { + [self writeRawByte:((value & 0x7F) | 0x80)]; + value = logicalRightShift32(value, 7); + } + } +} + + +/** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + */ +int32_t computeRawVarint32Size(int32_t value) { + if ((value & (0xffffffffLL << 7)) == 0) return 1; + if ((value & (0xffffffffLL << 14)) == 0) return 2; + if ((value & (0xffffffffLL << 21)) == 0) return 3; + if ((value & (0xffffffffLL << 28)) == 0) return 4; + return 5; +} + + +/** Encode and write a varint. */ +- (void) writeRawVarint64:(int64_t) value{ + while (YES) { + if ((value & ~0x7FL) == 0) { + [self writeRawByte:(uint8_t)((int32_t) value)]; + return; + } else { + [self writeRawByte:(uint8_t)(((int32_t) value & 0x7F) | 0x80)]; + value = logicalRightShift64(value, 7); + } + } +} + + +/** Compute the number of bytes that would be needed to encode a varint. */ +int32_t computeRawVarint64Size(int64_t value) { + if (((uint64_t)value & (0xffffffffffffffffULL << 7)) == 0) return 1; + if (((uint64_t)value & (0xffffffffffffffffULL << 14)) == 0) return 2; + if (((uint64_t)value & (0xffffffffffffffffULL << 21)) == 0) return 3; + if (((uint64_t)value & (0xffffffffffffffffULL << 28)) == 0) return 4; + if (((uint64_t)value & (0xffffffffffffffffULL << 35)) == 0) return 5; + if (((uint64_t)value & (0xffffffffffffffffULL << 42)) == 0) return 6; + if (((uint64_t)value & (0xffffffffffffffffULL << 49)) == 0) return 7; + if (((uint64_t)value & (0xffffffffffffffffULL << 56)) == 0) return 8; + if (((uint64_t)value & (0xffffffffffffffffULL << 63)) == 0) return 9; + return 10; +} + + +/** Write a little-endian 32-bit integer. */ +- (void) writeRawLittleEndian32:(int32_t) value { + [self writeRawByte:((value ) & 0xFF)]; + [self writeRawByte:((value >> 8) & 0xFF)]; + [self writeRawByte:((value >> 16) & 0xFF)]; + [self writeRawByte:((value >> 24) & 0xFF)]; +} + + +/** Write a little-endian 64-bit integer. */ +- (void) writeRawLittleEndian64:(int64_t) value { + [self writeRawByte:((int32_t)(value ) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 8) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 16) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 24) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 32) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 40) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 48) & 0xFF)]; + [self writeRawByte:((int32_t)(value >> 56) & 0xFF)]; +} + + +/** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int + */ +int32_t encodeZigZag32(int32_t n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); +} + + +/** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int + */ +int64_t encodeZigZag64(int64_t n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); +} + +@end diff --git a/Libraries/ProtocolBuffers/ConcreteExtensionField.h b/Libraries/ProtocolBuffers/ConcreteExtensionField.h new file mode 100644 index 000000000..f3e82ec0f --- /dev/null +++ b/Libraries/ProtocolBuffers/ConcreteExtensionField.h @@ -0,0 +1,62 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ExtensionField.h" + +typedef enum { + PBExtensionTypeBool, + PBExtensionTypeFixed32, + PBExtensionTypeSFixed32, + PBExtensionTypeFloat, + PBExtensionTypeFixed64, + PBExtensionTypeSFixed64, + PBExtensionTypeDouble, + PBExtensionTypeInt32, + PBExtensionTypeInt64, + PBExtensionTypeSInt32, + PBExtensionTypeSInt64, + PBExtensionTypeUInt32, + PBExtensionTypeUInt64, + PBExtensionTypeBytes, + PBExtensionTypeString, + PBExtensionTypeMessage, + PBExtensionTypeGroup, + PBExtensionTypeEnum +} PBExtensionType; + +@interface PBConcreteExtensionField : NSObject { +@private + PBExtensionType type; + + Class extendedClass; + int32_t fieldNumber; + id defaultValue; + + Class messageOrGroupClass; + + BOOL isRepeated; + BOOL isPacked; + BOOL isMessageSetWireFormat; +} + ++ (PBConcreteExtensionField*) extensionWithType:(PBExtensionType) type + extendedClass:(Class) extendedClass + fieldNumber:(int32_t) fieldNumber + defaultValue:(id) defaultValue + messageOrGroupClass:(Class) messageOrGroupClass + isRepeated:(BOOL) isRepeated + isPacked:(BOOL) isPacked + isMessageSetWireFormat:(BOOL) isMessageSetWireFormat; + +@end diff --git a/Libraries/ProtocolBuffers/ConcreteExtensionField.m b/Libraries/ProtocolBuffers/ConcreteExtensionField.m new file mode 100644 index 000000000..045c85c65 --- /dev/null +++ b/Libraries/ProtocolBuffers/ConcreteExtensionField.m @@ -0,0 +1,543 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ConcreteExtensionField.h" +#import "CodedOutputStream.h" +#import "CodedInputStream.h" +#import "Message_Builder.h" +#import "Message.h" +#import "ExtendableMessage_Builder.h" + +@interface PBConcreteExtensionField() +@property PBExtensionType type; +@property Class extendedClass; +@property int32_t fieldNumber; +@property (retain) id defaultValue; +@property Class messageOrGroupClass; +@property BOOL isRepeated; +@property BOOL isPacked; +@property BOOL isMessageSetWireFormat; +@end + +@implementation PBConcreteExtensionField + +@synthesize type; +@synthesize extendedClass; +@synthesize fieldNumber; +@synthesize defaultValue; +@synthesize messageOrGroupClass; +@synthesize isRepeated; +@synthesize isPacked; +@synthesize isMessageSetWireFormat; + +- (void) dealloc { + self.type = 0; + self.extendedClass = nil; + self.fieldNumber = 0; + self.defaultValue = nil; + self.messageOrGroupClass = nil; + self.isRepeated = NO; + self.isPacked = NO; + self.isMessageSetWireFormat = NO; +} + + +- (id) initWithType:(PBExtensionType) type_ + extendedClass:(Class) extendedClass_ + fieldNumber:(int32_t) fieldNumber_ + defaultValue:(id) defaultValue_ + messageOrGroupClass:(Class) messageOrGroupClass_ + isRepeated:(BOOL) isRepeated_ + isPacked:(BOOL) isPacked_ + isMessageSetWireFormat:(BOOL) isMessageSetWireFormat_ { + if ((self = [super init])) { + self.type = type_; + self.extendedClass = extendedClass_; + self.fieldNumber = fieldNumber_; + self.defaultValue = defaultValue_; + self.messageOrGroupClass = messageOrGroupClass_; + self.isRepeated = isRepeated_; + self.isPacked = isPacked_; + self.isMessageSetWireFormat = isMessageSetWireFormat_; + } + + return self; +} + + ++ (PBConcreteExtensionField*) extensionWithType:(PBExtensionType) type + extendedClass:(Class) extendedClass + fieldNumber:(int32_t) fieldNumber + defaultValue:(id) defaultValue + messageOrGroupClass:(Class) messageOrGroupClass + isRepeated:(BOOL) isRepeated + isPacked:(BOOL) isPacked + isMessageSetWireFormat:(BOOL) isMessageSetWireFormat { + return [[PBConcreteExtensionField alloc] initWithType:type + extendedClass:extendedClass + fieldNumber:fieldNumber + defaultValue:defaultValue + messageOrGroupClass:messageOrGroupClass + isRepeated:isRepeated + isPacked:isPacked + isMessageSetWireFormat:isMessageSetWireFormat]; +} + + +- (PBWireFormat) wireType { + if (isPacked) { + return PBWireFormatLengthDelimited; + } + + switch (type) { + case PBExtensionTypeBool: return PBWireFormatVarint; + case PBExtensionTypeFixed32: return PBWireFormatFixed32; + case PBExtensionTypeSFixed32: return PBWireFormatFixed32; + case PBExtensionTypeFloat: return PBWireFormatFixed32; + case PBExtensionTypeFixed64: return PBWireFormatFixed64; + case PBExtensionTypeSFixed64: return PBWireFormatFixed64; + case PBExtensionTypeDouble: return PBWireFormatFixed64; + case PBExtensionTypeInt32: return PBWireFormatVarint; + case PBExtensionTypeInt64: return PBWireFormatVarint; + case PBExtensionTypeSInt32: return PBWireFormatVarint; + case PBExtensionTypeSInt64: return PBWireFormatVarint; + case PBExtensionTypeUInt32: return PBWireFormatVarint; + case PBExtensionTypeUInt64: return PBWireFormatVarint; + case PBExtensionTypeBytes: return PBWireFormatLengthDelimited; + case PBExtensionTypeString: return PBWireFormatLengthDelimited; + case PBExtensionTypeMessage: return PBWireFormatLengthDelimited; + case PBExtensionTypeGroup: return PBWireFormatStartGroup; + case PBExtensionTypeEnum: return PBWireFormatVarint; + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +BOOL typeIsFixedSize(PBExtensionType type); +BOOL typeIsFixedSize(PBExtensionType type) { + switch (type) { + case PBExtensionTypeBool: + case PBExtensionTypeFixed32: + case PBExtensionTypeSFixed32: + case PBExtensionTypeFloat: + case PBExtensionTypeFixed64: + case PBExtensionTypeSFixed64: + case PBExtensionTypeDouble: + return YES; + default: + return NO; + } +} + + +int32_t typeSize(PBExtensionType type); +int32_t typeSize(PBExtensionType type) { + switch (type) { + case PBExtensionTypeBool: + return 1; + case PBExtensionTypeFixed32: + case PBExtensionTypeSFixed32: + case PBExtensionTypeFloat: + return 4; + case PBExtensionTypeFixed64: + case PBExtensionTypeSFixed64: + case PBExtensionTypeDouble: + return 8; + default: + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; + } +} + + +- (void) writeSingleValue:(id) value + includingTagToCodedOutputStream:(PBCodedOutputStream*) output { + switch (type) { + case PBExtensionTypeBool: + [output writeBool:fieldNumber value:[value boolValue]]; + return; + case PBExtensionTypeFixed32: + [output writeFixed32:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeSFixed32: + [output writeSFixed32:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeFloat: + [output writeFloat:fieldNumber value:[value floatValue]]; + return; + case PBExtensionTypeFixed64: + [output writeFixed64:fieldNumber value:[value longLongValue]]; + return; + case PBExtensionTypeSFixed64: + [output writeSFixed64:fieldNumber value:[value longLongValue]]; + return; + case PBExtensionTypeDouble: + [output writeDouble:fieldNumber value:[value doubleValue]]; + return; + case PBExtensionTypeInt32: + [output writeInt32:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeInt64: + [output writeInt64:fieldNumber value:[value longLongValue]]; + return; + case PBExtensionTypeSInt32: + [output writeSInt32:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeSInt64: + [output writeSInt64:fieldNumber value:[value longLongValue]]; + return; + case PBExtensionTypeUInt32: + [output writeUInt32:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeUInt64: + [output writeUInt64:fieldNumber value:[value longLongValue]]; + return; + case PBExtensionTypeBytes: + [output writeData:fieldNumber value:value]; + return; + case PBExtensionTypeString: + [output writeString:fieldNumber value:value]; + return; + case PBExtensionTypeGroup: + [output writeGroup:fieldNumber value:value]; + return; + case PBExtensionTypeEnum: + [output writeEnum:fieldNumber value:[value intValue]]; + return; + case PBExtensionTypeMessage: + if (isMessageSetWireFormat) { + [output writeMessageSetExtension:fieldNumber value:value]; + } else { + [output writeMessage:fieldNumber value:value]; + } + return; + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +- (void) writeSingleValue:(id) value + noTagToCodedOutputStream:(PBCodedOutputStream*) output { + switch (type) { + case PBExtensionTypeBool: + [output writeBoolNoTag:[value boolValue]]; + return; + case PBExtensionTypeFixed32: + [output writeFixed32NoTag:[value intValue]]; + return; + case PBExtensionTypeSFixed32: + [output writeSFixed32NoTag:[value intValue]]; + return; + case PBExtensionTypeFloat: + [output writeFloatNoTag:[value floatValue]]; + return; + case PBExtensionTypeFixed64: + [output writeFixed64NoTag:[value longLongValue]]; + return; + case PBExtensionTypeSFixed64: + [output writeSFixed64NoTag:[value longLongValue]]; + return; + case PBExtensionTypeDouble: + [output writeDoubleNoTag:[value doubleValue]]; + return; + case PBExtensionTypeInt32: + [output writeInt32NoTag:[value intValue]]; + return; + case PBExtensionTypeInt64: + [output writeInt64NoTag:[value longLongValue]]; + return; + case PBExtensionTypeSInt32: + [output writeSInt32NoTag:[value intValue]]; + return; + case PBExtensionTypeSInt64: + [output writeSInt64NoTag:[value longLongValue]]; + return; + case PBExtensionTypeUInt32: + [output writeUInt32NoTag:[value intValue]]; + return; + case PBExtensionTypeUInt64: + [output writeUInt64NoTag:[value longLongValue]]; + return; + case PBExtensionTypeBytes: + [output writeDataNoTag:value]; + return; + case PBExtensionTypeString: + [output writeStringNoTag:value]; + return; + case PBExtensionTypeGroup: + [output writeGroupNoTag:fieldNumber value:value]; + return; + case PBExtensionTypeEnum: + [output writeEnumNoTag:[value intValue]]; + return; + case PBExtensionTypeMessage: + [output writeMessageNoTag:value]; + return; + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +- (int32_t) computeSingleSerializedSizeNoTag:(id) value { + switch (type) { + case PBExtensionTypeBool: return computeBoolSizeNoTag([value boolValue]); + case PBExtensionTypeFixed32: return computeFixed32SizeNoTag([value intValue]); + case PBExtensionTypeSFixed32: return computeSFixed32SizeNoTag([value intValue]); + case PBExtensionTypeFloat: return computeFloatSizeNoTag([value floatValue]); + case PBExtensionTypeFixed64: return computeFixed64SizeNoTag([value longLongValue]); + case PBExtensionTypeSFixed64: return computeSFixed64SizeNoTag([value longLongValue]); + case PBExtensionTypeDouble: return computeDoubleSizeNoTag([value doubleValue]); + case PBExtensionTypeInt32: return computeInt32SizeNoTag([value intValue]); + case PBExtensionTypeInt64: return computeInt64SizeNoTag([value longLongValue]); + case PBExtensionTypeSInt32: return computeSInt32SizeNoTag([value intValue]); + case PBExtensionTypeSInt64: return computeSInt64SizeNoTag([value longLongValue]); + case PBExtensionTypeUInt32: return computeUInt32SizeNoTag([value intValue]); + case PBExtensionTypeUInt64: return computeUInt64SizeNoTag([value longLongValue]); + case PBExtensionTypeBytes: return computeDataSizeNoTag(value); + case PBExtensionTypeString: return computeStringSizeNoTag(value); + case PBExtensionTypeGroup: return computeGroupSizeNoTag(value); + case PBExtensionTypeEnum: return computeEnumSizeNoTag([value intValue]); + case PBExtensionTypeMessage: return computeMessageSizeNoTag(value); + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +- (int32_t) computeSingleSerializedSizeIncludingTag:(id) value { + switch (type) { + case PBExtensionTypeBool: return computeBoolSize(fieldNumber, [value boolValue]); + case PBExtensionTypeFixed32: return computeFixed32Size(fieldNumber, [value intValue]); + case PBExtensionTypeSFixed32: return computeSFixed32Size(fieldNumber, [value intValue]); + case PBExtensionTypeFloat: return computeFloatSize(fieldNumber, [value floatValue]); + case PBExtensionTypeFixed64: return computeFixed64Size(fieldNumber, [value longLongValue]); + case PBExtensionTypeSFixed64: return computeSFixed64Size(fieldNumber, [value longLongValue]); + case PBExtensionTypeDouble: return computeDoubleSize(fieldNumber, [value doubleValue]); + case PBExtensionTypeInt32: return computeInt32Size(fieldNumber, [value intValue]); + case PBExtensionTypeInt64: return computeInt64Size(fieldNumber, [value longLongValue]); + case PBExtensionTypeSInt32: return computeSInt32Size(fieldNumber, [value intValue]); + case PBExtensionTypeSInt64: return computeSInt64Size(fieldNumber, [value longLongValue]); + case PBExtensionTypeUInt32: return computeUInt32Size(fieldNumber, [value intValue]); + case PBExtensionTypeUInt64: return computeUInt64Size(fieldNumber, [value longLongValue]); + case PBExtensionTypeBytes: return computeDataSize(fieldNumber, value); + case PBExtensionTypeString: return computeStringSize(fieldNumber, value); + case PBExtensionTypeGroup: return computeGroupSize(fieldNumber, value); + case PBExtensionTypeEnum: return computeEnumSize(fieldNumber, [value intValue]); + case PBExtensionTypeMessage: + if (isMessageSetWireFormat) { + return computeMessageSetExtensionSize(fieldNumber, value); + } else { + return computeMessageSize(fieldNumber, value); + } + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +- (void) writeRepeatedValues:(NSArray*) values + includingTagsToCodedOutputStream:(PBCodedOutputStream*) output { + if (isPacked) { + [output writeTag:fieldNumber format:PBWireFormatLengthDelimited]; + int32_t dataSize = 0; + if (typeIsFixedSize(type)) { + dataSize = (int32_t)values.count * typeSize(type); + } else { + for (id value in values) { + dataSize += [self computeSingleSerializedSizeNoTag:value]; + } + } + [output writeRawVarint32:dataSize]; + for (id value in values) { + [self writeSingleValue:value noTagToCodedOutputStream:output]; + } + } else { + for (id value in values) { + [self writeSingleValue:value includingTagToCodedOutputStream:output]; + } + } +} + + +- (void) writeValue:(id) value includingTagToCodedOutputStream:(PBCodedOutputStream*) output { + if (isRepeated) { + [self writeRepeatedValues:value includingTagsToCodedOutputStream:output]; + } else { + [self writeSingleValue:value includingTagToCodedOutputStream:output]; + } +} + + +- (int32_t) computeRepeatedSerializedSizeIncludingTags:(NSArray*) values { + if (isPacked) { + int32_t size = 0; + if (typeIsFixedSize(type)) { + size = (int32_t)values.count * typeSize(type); + } else { + for (id value in values) { + size += [self computeSingleSerializedSizeNoTag:value]; + } + } + return size + computeTagSize(fieldNumber) + computeRawVarint32Size(size); + } else { + int32_t size = 0; + for (id value in values) { + size += [self computeSingleSerializedSizeIncludingTag:value]; + } + return size; + } +} + + +- (int32_t) computeSerializedSizeIncludingTag:(id) value { + if (isRepeated) { + return [self computeRepeatedSerializedSizeIncludingTags:value]; + } else { + return [self computeSingleSerializedSizeIncludingTag:value]; + } +} + + +- (void) mergeMessageSetExtentionFromCodedInputStream:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields { + @throw [NSException exceptionWithName:@"NYI" reason:@"" userInfo:nil]; + + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + //int typeId = 0; +// ByteString rawBytes = null; +// +// while (true) { +// final int tag = input.readTag(); +// if (tag == 0) { +// break; +// } +// +// if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { +// typeId = input.readUInt32(); +// // Zero is not a valid type ID. +// if (typeId != 0) { +// if (rawBytes != null) { +// unknownFields.mergeField(typeId, +// UnknownFieldSet.Field.newBuilder() +// .addLengthDelimited(rawBytes) +// .build()); +// rawBytes = null; +// } +// } +// } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { +// if (typeId == 0) { +// // We haven't seen a type ID yet, so we have to store the raw bytes +// // for now. +// rawBytes = input.readBytes(); +// } else { +// unknownFields.mergeField(typeId, +// UnknownFieldSet.Field.newBuilder() +// .addLengthDelimited(input.readBytes()) +// .build()); +// } +// } else { +// // Unknown fieldNumber. Skip it. +// if (!input.skipField(tag)) { +// break; // end of group +// } +// } +// } +// +// input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); +} + + +- (id) readSingleValueFromCodedInputStream:(PBCodedInputStream*) input + extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + switch (type) { + case PBExtensionTypeBool: return [NSNumber numberWithBool:[input readBool]]; + case PBExtensionTypeFixed32: return [NSNumber numberWithInt:[input readFixed32]]; + case PBExtensionTypeSFixed32: return [NSNumber numberWithInt:[input readSFixed32]]; + case PBExtensionTypeFloat: return [NSNumber numberWithFloat:[input readFloat]]; + case PBExtensionTypeFixed64: return [NSNumber numberWithLongLong:[input readFixed64]]; + case PBExtensionTypeSFixed64: return [NSNumber numberWithLongLong:[input readSFixed64]]; + case PBExtensionTypeDouble: return [NSNumber numberWithDouble:[input readDouble]]; + case PBExtensionTypeInt32: return [NSNumber numberWithInt:[input readInt32]]; + case PBExtensionTypeInt64: return [NSNumber numberWithLongLong:[input readInt64]]; + case PBExtensionTypeSInt32: return [NSNumber numberWithInt:[input readSInt32]]; + case PBExtensionTypeSInt64: return [NSNumber numberWithLongLong:[input readSInt64]]; + case PBExtensionTypeUInt32: return [NSNumber numberWithInt:[input readUInt32]]; + case PBExtensionTypeUInt64: return [NSNumber numberWithLongLong:[input readUInt64]]; + case PBExtensionTypeBytes: return [input readData]; + case PBExtensionTypeString: return [input readString]; + case PBExtensionTypeEnum: return [NSNumber numberWithInt:[input readEnum]]; + case PBExtensionTypeGroup: + { + id builder = [messageOrGroupClass builder]; + [input readGroup:fieldNumber builder:builder extensionRegistry:extensionRegistry]; + return [builder build]; + } + + case PBExtensionTypeMessage: + { + id builder = [messageOrGroupClass builder]; + [input readMessage:builder extensionRegistry:extensionRegistry]; + return [builder build]; + } + } + + @throw [NSException exceptionWithName:@"InternalError" reason:@"" userInfo:nil]; +} + + +- (void) mergeFromCodedInputStream:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields + extensionRegistry:(PBExtensionRegistry*) extensionRegistry + builder:(PBExtendableMessage_Builder*) builder + tag:(int32_t) tag { + if (isPacked) { + int32_t length = [input readRawVarint32]; + int32_t limit = [input pushLimit:length]; + while ([input bytesUntilLimit] > 0) { + id value = [self readSingleValueFromCodedInputStream:input extensionRegistry:extensionRegistry]; + [builder addExtension:self value:value]; + } + [input popLimit:limit]; + } else if (isMessageSetWireFormat) { + [self mergeMessageSetExtentionFromCodedInputStream:input + unknownFields:unknownFields]; + } else { + id value = [self readSingleValueFromCodedInputStream:input extensionRegistry:extensionRegistry]; + if (isRepeated) { + [builder addExtension:self value:value]; + } else { + [builder setExtension:self value:value]; + } + } +} + + +@end diff --git a/Libraries/ProtocolBuffers/ExtendableMessage.h b/Libraries/ProtocolBuffers/ExtendableMessage.h new file mode 100644 index 000000000..1571479b8 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtendableMessage.h @@ -0,0 +1,77 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GeneratedMessage.h" +@protocol PBExtensionField; + +/** + * Generated message classes for message types that contain extension ranges + * subclass this. + * + *

This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "has", "get", and "getCount" -- but for extensions. The extensions + * are identified using instances of the class {@link GeneratedExtension}; + * the protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + *

For example, imagine you have the {@code .proto} file: + * + *

+ * option java_class = "MyProto";
+ *
+ * message Foo {
+ *   extensions 1000 to max;
+ * }
+ *
+ * extend Foo {
+ *   optional int32 bar;
+ * }
+ * 
+ * + *

Then you might write code like: + * + *

+ * MyProto.Foo foo = getFoo();
+ * int i = foo.getExtension(MyProto.bar);
+ * 
+ * + *

See also {@link ExtendableBuilder}. + */ +@interface PBExtendableMessage : PBGeneratedMessage { +@private + NSMutableDictionary* extensionMap; + NSMutableDictionary* extensionRegistry; +} + +@property (retain) NSMutableDictionary* extensionMap; +@property (retain) NSMutableDictionary* extensionRegistry; + +- (BOOL) hasExtension:(id) extension; +- (id) getExtension:(id) extension; + +//@protected +- (BOOL) extensionsAreInitialized; +- (int32_t) extensionsSerializedSize; +- (void) writeExtensionsToCodedOutputStream:(PBCodedOutputStream*) output + from:(int32_t) startInclusive + to:(int32_t) endExclusive; + + + +/* @internal */ +- (void) ensureExtensionIsRegistered:(id) extension; + +@end diff --git a/Libraries/ProtocolBuffers/ExtendableMessage.m b/Libraries/ProtocolBuffers/ExtendableMessage.m new file mode 100644 index 000000000..e9df70d58 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtendableMessage.m @@ -0,0 +1,106 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ExtendableMessage.h" + +#import "ExtensionField.h" + +@implementation PBExtendableMessage + +@synthesize extensionMap; +@synthesize extensionRegistry; + +- (void) dealloc { + self.extensionMap = nil; + self.extensionRegistry = nil; +} + + +- (BOOL) isInitialized:(id) object { + if ([object isKindOfClass:[NSArray class]]) { + for (id child in object) { + if (![self isInitialized:child]) { + return NO; + } + } + } else if ([object conformsToProtocol:@protocol(PBMessage)]) { + return [object isInitialized]; + } + + return YES; +} + + +- (BOOL) extensionsAreInitialized { + return [self isInitialized:extensionMap.allValues]; +} + + +- (id) getExtension:(id) extension { + [self ensureExtensionIsRegistered:extension]; + id value = [extensionMap objectForKey:[NSNumber numberWithInt:[extension fieldNumber]]]; + if (value != nil) { + return value; + } + + return [extension defaultValue]; +} + + +- (void) ensureExtensionIsRegistered:(id) extension { + if ([extension extendedClass] != [self class]) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Trying to use an extension for another type" userInfo:nil]; + } + + if (extensionRegistry == nil) { + self.extensionRegistry = [NSMutableDictionary dictionary]; + } + [extensionRegistry setObject:extension + forKey:[NSNumber numberWithInt:[extension fieldNumber]]]; +} + + +- (BOOL) hasExtension:(id) extension { + return nil != [extensionMap objectForKey:[NSNumber numberWithInt:[extension fieldNumber]]]; +} + + +- (void) writeExtensionsToCodedOutputStream:(PBCodedOutputStream*) output + from:(int32_t) startInclusive + to:(int32_t) endExclusive { + // man, i really wish Cocoa had a Sorted/TreeMap + NSArray* sortedKeys = [extensionMap.allKeys sortedArrayUsingSelector:@selector(compare:)]; + for (NSNumber* number in sortedKeys) { + int32_t fieldNumber = [number intValue]; + if (fieldNumber >= startInclusive && fieldNumber < endExclusive) { + id extension = [extensionRegistry objectForKey:number]; + id value = [extensionMap objectForKey:number]; + [extension writeValue:value includingTagToCodedOutputStream:output]; + } + } +} + + +- (int32_t) extensionsSerializedSize { + int32_t size = 0; + for (NSNumber* number in extensionMap) { + id extension = [extensionRegistry objectForKey:number]; + id value = [extensionMap objectForKey:number]; + size += [extension computeSerializedSizeIncludingTag:value]; + } + + return size; +} + +@end diff --git a/Libraries/ProtocolBuffers/ExtendableMessage_Builder.h b/Libraries/ProtocolBuffers/ExtendableMessage_Builder.h new file mode 100644 index 000000000..316c91b21 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtendableMessage_Builder.h @@ -0,0 +1,75 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GeneratedMessage_Builder.h" + +@protocol PBExtensionField; +@class PBExtendableMessage_Builder; +@class PBExtendableMessage; + +/** + * Generated message builders for message types that contain extension ranges + * subclass this. + * + *

This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "get", "set", and "add" -- but for extensions. The extensions are + * identified using instances of the class {@link GeneratedExtension}; the + * protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + *

For example, imagine you have the {@code .proto} file: + * + *

+ * option java_class = "MyProto";
+ *
+ * message Foo {
+ *   extensions 1000 to max;
+ * }
+ *
+ * extend Foo {
+ *   optional int32 bar;
+ * }
+ * 
+ * + *

Then you might write code like: + * + *

+ * MyProto.Foo foo =
+ *   MyProto.Foo.newBuilder()
+ *     .setExtension(MyProto.bar, 123)
+ *     .build();
+ * 
+ * + *

See also {@link ExtendableMessage}. + */ +@interface PBExtendableMessage_Builder : PBGeneratedMessage_Builder { +} + +- (id) getExtension:(id) extension; +- (BOOL) hasExtension:(id) extension; +- (PBExtendableMessage_Builder*) setExtension:(id) extension + value:(id) value; +- (PBExtendableMessage_Builder*) addExtension:(id) extension + value:(id) value; +- (PBExtendableMessage_Builder*) setExtension:(id) extension + index:(int32_t) index + value:(id) value; +- (PBExtendableMessage_Builder*) clearExtension:(id) extension; + +/* @protected */ +- (void) mergeExtensionFields:(PBExtendableMessage*) other; + +@end diff --git a/Libraries/ProtocolBuffers/ExtendableMessage_Builder.m b/Libraries/ProtocolBuffers/ExtendableMessage_Builder.m new file mode 100644 index 000000000..c47de3e22 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtendableMessage_Builder.m @@ -0,0 +1,173 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ExtendableMessage_Builder.h" + +#import "ExtendableMessage.h" +#import "ExtensionField.h" +#import "WireFormat.h" +#import "ExtensionRegistry.h" + +@implementation PBExtendableMessage_Builder + +- (PBExtendableMessage*) internalGetResult { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +/** + * Called by subclasses to parse an unknown field or an extension. + * @return {@code YES} unless the tag is an end-group tag. + */ +- (BOOL) parseUnknownField:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields + extensionRegistry:(PBExtensionRegistry*) extensionRegistry + tag:(int32_t) tag { + PBExtendableMessage* message = [self internalGetResult]; + int32_t wireType = PBWireFormatGetTagWireType(tag); + int32_t fieldNumber = PBWireFormatGetTagFieldNumber(tag); + + id extension = [extensionRegistry getExtension:[message class] + fieldNumber:fieldNumber]; + + if (extension != nil) { + if ([extension wireType] == wireType) { + [extension mergeFromCodedInputStream:input + unknownFields:unknownFields + extensionRegistry:extensionRegistry + builder:self + tag:tag]; + return YES; + } + } + + return [super parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]; +} + + +- (id) getExtension:(id) extension { + return [[self internalGetResult] getExtension:extension]; +} + + +- (BOOL) hasExtension:(id) extension { + return [[self internalGetResult] hasExtension:extension]; +} + + +- (PBExtendableMessage_Builder*) setExtension:(id) extension + value:(id) value { + PBExtendableMessage* message = [self internalGetResult]; + [message ensureExtensionIsRegistered:extension]; + + if ([extension isRepeated]) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Must call addExtension() for repeated types." userInfo:nil]; + } + + if (message.extensionMap == nil) { + message.extensionMap = [NSMutableDictionary dictionary]; + } + [message.extensionMap setObject:value forKey:[NSNumber numberWithInt:[extension fieldNumber]]]; + return self; +} + + +- (PBExtendableMessage_Builder*) addExtension:(id) extension + value:(id) value { + PBExtendableMessage* message = [self internalGetResult]; + [message ensureExtensionIsRegistered:extension]; + + if (![extension isRepeated]) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Must call setExtension() for singular types." userInfo:nil]; + } + + if (message.extensionMap == nil) { + message.extensionMap = [NSMutableDictionary dictionary]; + } + NSNumber* fieldNumber = [NSNumber numberWithInt:[extension fieldNumber]]; + NSMutableArray* list = [message.extensionMap objectForKey:fieldNumber]; + if (list == nil) { + list = [NSMutableArray array]; + [message.extensionMap setObject:list forKey:fieldNumber]; + } + + [list addObject:value]; + return self; +} + + +- (PBExtendableMessage_Builder*) setExtension:(id) extension + index:(int32_t) index + value:(id) value { + PBExtendableMessage* message = [self internalGetResult]; + [message ensureExtensionIsRegistered:extension]; + + if (![extension isRepeated]) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Must call setExtension() for singular types." userInfo:nil]; + } + + if (message.extensionMap == nil) { + message.extensionMap = [NSMutableDictionary dictionary]; + } + + NSNumber* fieldNumber = [NSNumber numberWithInt:[extension fieldNumber]]; + NSMutableArray* list = [message.extensionMap objectForKey:fieldNumber]; + + [list replaceObjectAtIndex:(NSUInteger)index withObject:value]; + + return self; +} + + +- (PBExtendableMessage_Builder*) clearExtension:(id) extension { + PBExtendableMessage* message = [self internalGetResult]; + [message ensureExtensionIsRegistered:extension]; + [message.extensionMap removeObjectForKey:[NSNumber numberWithInt:[extension fieldNumber]]]; + + return self; +} + + +- (void) mergeExtensionFields:(PBExtendableMessage*) other { + PBExtendableMessage* thisMessage = [self internalGetResult]; + if ([thisMessage class] != [other class]) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Cannot merge extensions from a different type" userInfo:nil]; + } + + if (other.extensionMap.count > 0) { + if (thisMessage.extensionMap == nil) { + thisMessage.extensionMap = [NSMutableDictionary dictionary]; + } + + NSDictionary* registry = other.extensionRegistry; + for (NSNumber* fieldNumber in other.extensionMap) { + id thisField = [registry objectForKey:fieldNumber]; + id value = [other.extensionMap objectForKey:fieldNumber]; + + if ([thisField isRepeated]) { + NSMutableArray* list = [thisMessage.extensionMap objectForKey:fieldNumber]; + if (list == nil) { + list = [NSMutableArray array]; + [thisMessage.extensionMap setObject:list forKey:fieldNumber]; + } + + [list addObjectsFromArray:value]; + } else { + [thisMessage.extensionMap setObject:value forKey:fieldNumber]; + } + } + } +} + +@end diff --git a/Libraries/ProtocolBuffers/ExtensionField.h b/Libraries/ProtocolBuffers/ExtensionField.h new file mode 100644 index 000000000..34f7c3c76 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtensionField.h @@ -0,0 +1,36 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "WireFormat.h" +@class PBCodedInputStream; +@class PBUnknownFieldSet_Builder; +@class PBExtendableMessage_Builder; +@class PBCodedOutputStream; +@class PBExtensionRegistry; + +@protocol PBExtensionField +- (int32_t) fieldNumber; +- (PBWireFormat) wireType; +- (BOOL) isRepeated; +- (Class) extendedClass; +- (id) defaultValue; + +- (void) mergeFromCodedInputStream:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields + extensionRegistry:(PBExtensionRegistry*) extensionRegistry + builder:(PBExtendableMessage_Builder*) builder + tag:(int32_t) tag; +- (void) writeValue:(id) value includingTagToCodedOutputStream:(PBCodedOutputStream*) output; +- (int32_t) computeSerializedSizeIncludingTag:(id) value; +@end diff --git a/Libraries/ProtocolBuffers/ExtensionRegistry.h b/Libraries/ProtocolBuffers/ExtensionRegistry.h new file mode 100644 index 000000000..444b765a3 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtensionRegistry.h @@ -0,0 +1,83 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * A table of known extensions, searchable by name or field number. When + * parsing a protocol message that might have extensions, you must provide + * an {@code ExtensionRegistry} in which you have registered any extensions + * that you want to be able to parse. Otherwise, those extensions will just + * be treated like unknown fields. + * + *

For example, if you had the {@code .proto} file: + * + *

+ * option java_class = "MyProto";
+ *
+ * message Foo {
+ *   extensions 1000 to max;
+ * }
+ *
+ * extend Foo {
+ *   optional int32 bar;
+ * }
+ * 
+ * + * Then you might write code like: + * + *
+ * ExtensionRegistry registry = ExtensionRegistry.newInstance();
+ * registry.add(MyProto.bar);
+ * MyProto.Foo message = MyProto.Foo.parseFrom(input, registry);
+ * 
+ * + *

Background: + * + *

You might wonder why this is necessary. Two alternatives might come to + * mind. First, you might imagine a system where generated extensions are + * automatically registered when their containing classes are loaded. This + * is a popular technique, but is bad design; among other things, it creates a + * situation where behavior can change depending on what classes happen to be + * loaded. It also introduces a security vulnerability, because an + * unprivileged class could cause its code to be called unexpectedly from a + * privileged class by registering itself as an extension of the right type. + * + *

Another option you might consider is lazy parsing: do not parse an + * extension until it is first requested, at which point the caller must + * provide a type to use. This introduces a different set of problems. First, + * it would require a mutex lock any time an extension was accessed, which + * would be slow. Second, corrupt data would not be detected until first + * access, at which point it would be much harder to deal with it. Third, it + * could violate the expectation that message objects are immutable, since the + * type provided could be any arbitrary message class. An unpriviledged user + * could take advantage of this to inject a mutable object into a message + * belonging to priviledged code and create mischief. + * + * @author Cyrus Najmabadi + */ + +@protocol PBExtensionField; + +@interface PBExtensionRegistry : NSObject { +@protected + NSDictionary* classMap; +} + ++ (PBExtensionRegistry*) emptyRegistry; +- (id) getExtension:(Class) clazz fieldNumber:(NSInteger) fieldNumber; + +/* @protected */ +- (id) initWithClassMap:(NSDictionary*) classMap; +- (id) keyForClass:(Class) clazz; + +@end diff --git a/Libraries/ProtocolBuffers/ExtensionRegistry.m b/Libraries/ProtocolBuffers/ExtensionRegistry.m new file mode 100644 index 000000000..4412b2a59 --- /dev/null +++ b/Libraries/ProtocolBuffers/ExtensionRegistry.m @@ -0,0 +1,62 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ExtensionRegistry.h" + +@interface PBExtensionRegistry() +@property (retain) NSDictionary* classMap; +@end + +@implementation PBExtensionRegistry + +@synthesize classMap; + +- (void) dealloc { + self.classMap = nil; +} + +static PBExtensionRegistry* emptyRegistry = nil; + ++ (void) initialize { + if (self == [PBExtensionRegistry class]) { + emptyRegistry = [[PBExtensionRegistry alloc] initWithClassMap:[NSDictionary dictionary]]; + } +} + + +- (id) initWithClassMap:(NSDictionary*) map_{ + if ((self = [super init])) { + self.classMap = map_; + } + + return self; +} + + +- (id) keyForClass:(Class) clazz { + return NSStringFromClass(clazz); +} + + ++ (PBExtensionRegistry*) emptyRegistry { + return emptyRegistry; +} + + +- (id) getExtension:(Class) clazz fieldNumber:(NSInteger) fieldNumber { + NSDictionary* extensionMap = [classMap objectForKey:[self keyForClass:clazz]]; + return [extensionMap objectForKey:[NSNumber numberWithInteger:fieldNumber]]; +} + +@end diff --git a/Libraries/ProtocolBuffers/Field.h b/Libraries/ProtocolBuffers/Field.h new file mode 100644 index 000000000..851e5eb3e --- /dev/null +++ b/Libraries/ProtocolBuffers/Field.h @@ -0,0 +1,42 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBCodedOutputStream; + +@interface PBField : NSObject { +@protected + NSMutableArray* mutableVarintList; + NSMutableArray* mutableFixed32List; + NSMutableArray* mutableFixed64List; + NSMutableArray* mutableLengthDelimitedList; + NSMutableArray* mutableGroupList; +} + +- (NSArray*) varintList; +- (NSArray*) fixed32List; +- (NSArray*) fixed64List; +- (NSArray*) lengthDelimitedList; +- (NSArray*) groupList; + ++ (PBField*) defaultInstance; + +- (void) writeTo:(int32_t) fieldNumber + output:(PBCodedOutputStream*) output; + +- (int32_t) getSerializedSize:(int32_t) fieldNumber; +- (void) writeAsMessageSetExtensionTo:(int32_t) fieldNumber + output:(PBCodedOutputStream*) output; +- (int32_t) getSerializedSizeAsMessageSetExtension:(int32_t) fieldNumber; + +@end diff --git a/Libraries/ProtocolBuffers/Field.m b/Libraries/ProtocolBuffers/Field.m new file mode 100644 index 000000000..66095f932 --- /dev/null +++ b/Libraries/ProtocolBuffers/Field.m @@ -0,0 +1,143 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Field.h" + +#import "CodedOutputStream.h" +#import "MutableField.h" + +@interface PBField () +@property (retain) NSMutableArray* mutableVarintList; +@property (retain) NSMutableArray* mutableFixed32List; +@property (retain) NSMutableArray* mutableFixed64List; +@property (retain) NSMutableArray* mutableLengthDelimitedList; +@property (retain) NSMutableArray* mutableGroupList; +@end + +@implementation PBField + +static PBField* defaultInstance = nil; + ++ (void) initialize { + if (self == [PBField class]) { + defaultInstance = [[PBField alloc] init]; + } +} + + +@synthesize mutableVarintList; +@synthesize mutableFixed32List; +@synthesize mutableFixed64List; +@synthesize mutableLengthDelimitedList; +@synthesize mutableGroupList; + + +- (void) dealloc { + self.mutableVarintList = nil; + self.mutableFixed32List = nil; + self.mutableFixed64List = nil; + self.mutableLengthDelimitedList = nil; + self.mutableGroupList = nil; +} + + ++ (PBField*) defaultInstance { + return defaultInstance; +} + + +- (NSArray*) varintList { + return mutableVarintList; +} + + +- (NSArray*) fixed32List { + return mutableFixed32List; +} + + +- (NSArray*) fixed64List { + return mutableFixed64List; +} + + +- (NSArray*) lengthDelimitedList { + return mutableLengthDelimitedList; +} + + +- (NSArray*) groupList { + return mutableGroupList; +} + + +- (void) writeTo:(int32_t) fieldNumber + output:(PBCodedOutputStream*) output { + for (NSNumber* value in self.varintList) { + [output writeUInt64:fieldNumber value:value.longLongValue]; + } + for (NSNumber* value in self.fixed32List) { + [output writeFixed32:fieldNumber value:value.intValue]; + } + for (NSNumber* value in self.fixed64List) { + [output writeFixed64:fieldNumber value:value.longLongValue]; + } + for (NSData* value in self.lengthDelimitedList) { + [output writeData:fieldNumber value:value]; + } + for (PBUnknownFieldSet* value in self.groupList) { + [output writeUnknownGroup:fieldNumber value:value]; + } +} + + +- (int32_t) getSerializedSize:(int32_t) fieldNumber { + int32_t result = 0; + for (NSNumber* value in self.varintList) { + result += computeUInt64Size(fieldNumber, value.longLongValue); + } + for (NSNumber* value in self.fixed32List) { + result += computeFixed32Size(fieldNumber, value.intValue); + } + for (NSNumber* value in self.fixed64List) { + result += computeFixed64Size(fieldNumber, value.longLongValue); + } + for (NSData* value in self.lengthDelimitedList) { + result += computeDataSize(fieldNumber, value); + } + for (PBUnknownFieldSet* value in self.groupList) { + result += computeUnknownGroupSize(fieldNumber, value); + } + return result; +} + + +- (void) writeAsMessageSetExtensionTo:(int32_t) fieldNumber + output:(PBCodedOutputStream*) output { + for (NSData* value in self.lengthDelimitedList) { + [output writeRawMessageSetExtension:fieldNumber value:value]; + } +} + + +- (int32_t) getSerializedSizeAsMessageSetExtension:(int32_t) fieldNumber { + int32_t result = 0; + for (NSData* value in self.lengthDelimitedList) { + result += computeRawMessageSetExtensionSize(fieldNumber, value); + } + return result; +} + + +@end diff --git a/Libraries/ProtocolBuffers/ForwardDeclarations.h b/Libraries/ProtocolBuffers/ForwardDeclarations.h new file mode 100644 index 000000000..e121d67a6 --- /dev/null +++ b/Libraries/ProtocolBuffers/ForwardDeclarations.h @@ -0,0 +1,32 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@protocol PBMessage; +@protocol PBMessage_Builder; +@protocol PBExtensionField; + +@class PBAbstractMessage; +@class PBCodedInputStream; +@class PBCodedOutputStream; +@class PBConcreteExtensionField; +@class PBExtendableMessage_Builder; +@class PBExtendableMessage; +@class PBExtensionRegistry; +@class PBField; +@class PBGeneratedMessage; +@class PBGeneratedMessage_Builder; +@class PBMutableExtensionRegistry; +@class PBMutableField; +@class PBUnknownFieldSet; +@class PBUnknownFieldSet_Builder; diff --git a/Libraries/ProtocolBuffers/GeneratedMessage.h b/Libraries/ProtocolBuffers/GeneratedMessage.h new file mode 100644 index 000000000..13c83ea0f --- /dev/null +++ b/Libraries/ProtocolBuffers/GeneratedMessage.h @@ -0,0 +1,33 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "AbstractMessage.h" + +/** + * All generated protocol message classes extend this class. This class + * implements most of the Message and Builder interfaces using Java reflection. + * Users can ignore this class and pretend that generated messages implement + * the Message interface directly. + * + * @author Cyrus Najmabadi + */ +@interface PBGeneratedMessage : PBAbstractMessage { +@private + PBUnknownFieldSet* unknownFields; + +@protected + int32_t memoizedSerializedSize; +} + +@end diff --git a/Libraries/ProtocolBuffers/GeneratedMessage.m b/Libraries/ProtocolBuffers/GeneratedMessage.m new file mode 100644 index 000000000..d8e578d53 --- /dev/null +++ b/Libraries/ProtocolBuffers/GeneratedMessage.m @@ -0,0 +1,42 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GeneratedMessage.h" + +#import "UnknownFieldSet.h" + +@interface PBGeneratedMessage () +@property (retain) PBUnknownFieldSet* unknownFields; +@end + + +@implementation PBGeneratedMessage + +@synthesize unknownFields; + +- (void) dealloc { + self.unknownFields = nil; +} + + +- (id) init { + if ((self = [super init])) { + self.unknownFields = [PBUnknownFieldSet defaultInstance]; + memoizedSerializedSize = -1; + } + + return self; +} + +@end diff --git a/Libraries/ProtocolBuffers/GeneratedMessage_Builder.h b/Libraries/ProtocolBuffers/GeneratedMessage_Builder.h new file mode 100644 index 000000000..acbad6d2b --- /dev/null +++ b/Libraries/ProtocolBuffers/GeneratedMessage_Builder.h @@ -0,0 +1,30 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "AbstractMessage_Builder.h" + +@class PBUnknownFieldSet_Builder; + +@interface PBGeneratedMessage_Builder : PBAbstractMessage_Builder { +} + +/* @protected */ +- (BOOL) parseUnknownField:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields + extensionRegistry:(PBExtensionRegistry*) extensionRegistry + tag:(int32_t) tag; + +- (void) checkInitialized; + +@end diff --git a/Libraries/ProtocolBuffers/GeneratedMessage_Builder.m b/Libraries/ProtocolBuffers/GeneratedMessage_Builder.m new file mode 100644 index 000000000..73f408be6 --- /dev/null +++ b/Libraries/ProtocolBuffers/GeneratedMessage_Builder.m @@ -0,0 +1,92 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GeneratedMessage_Builder.h" + +#import "GeneratedMessage.h" +#import "Message.h" +#import "Message_Builder.h" +#import "UnknownFieldSet.h" +#import "UnknownFieldSet_Builder.h" + + +@interface PBGeneratedMessage () +@property (retain) PBUnknownFieldSet* unknownFields; +@end + + +@implementation PBGeneratedMessage_Builder + +/** + * Get the message being built. We don't just pass this to the + * constructor because it becomes null when build() is called. + */ +- (PBGeneratedMessage*) internalGetResult { + @throw [NSException exceptionWithName:@"ImproperSubclassing" reason:@"" userInfo:nil]; +} + + +- (void) checkInitialized { + PBGeneratedMessage* result = self.internalGetResult; + if (result != nil && !result.isInitialized) { + @throw [NSException exceptionWithName:@"UninitializedMessage" reason:@"" userInfo:nil]; + } +} + + +- (PBUnknownFieldSet*) unknownFields { + return self.internalGetResult.unknownFields; +} + + +- (id) setUnknownFields:(PBUnknownFieldSet*) unknownFields { + self.internalGetResult.unknownFields = unknownFields; + return self; +} + + +- (id) mergeUnknownFields:(PBUnknownFieldSet*) unknownFields { + PBGeneratedMessage* result = self.internalGetResult; + result.unknownFields = + [[[PBUnknownFieldSet builderWithUnknownFields:result.unknownFields] + mergeUnknownFields:unknownFields] build]; + return self; +} + + +- (BOOL) isInitialized { + return self.internalGetResult.isInitialized; +} + + +/** + * Called by subclasses to parse an unknown field. + * @return {@code YES} unless the tag is an end-group tag. + */ +- (BOOL) parseUnknownField:(PBCodedInputStream*) input + unknownFields:(PBUnknownFieldSet_Builder*) unknownFields + extensionRegistry:(PBExtensionRegistry*) extensionRegistry + tag:(int32_t) tag { + return [unknownFields mergeFieldFrom:tag input:input]; +} + + +- (void) checkInitializedParsed { + PBGeneratedMessage* result = self.internalGetResult; + if (result != nil && !result.isInitialized) { + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"" userInfo:nil]; + } +} + +@end diff --git a/Libraries/ProtocolBuffers/Message.h b/Libraries/ProtocolBuffers/Message.h new file mode 100644 index 000000000..6db5d1518 --- /dev/null +++ b/Libraries/ProtocolBuffers/Message.h @@ -0,0 +1,70 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBUnknownFieldSet; +@class PBCodedOutputStream; +@protocol PBMessage_Builder; + +/** + * Abstract interface implemented by Protocol Message objects. + * + * @author Cyrus Najmabadi + */ +@protocol PBMessage +/** + * Get an instance of the type with all fields set to their default values. + * This may or may not be a singleton. This differs from the + * {@code getDefaultInstance()} method of generated message classes in that + * this method is an abstract method of the {@code Message} interface + * whereas {@code getDefaultInstance()} is a static method of a specific + * class. They return the same thing. + */ +- (id) defaultInstance; + +/** + * Get the {@code UnknownFieldSet} + */ +- (PBUnknownFieldSet*) unknownFields; + +/** + * Get the number of bytes required to encode this message. The result + * is only computed on the first call and memoized after that. + */ +- (int32_t) serializedSize; + +/** + * Returns true if all required fields in the message and all embedded + * messages are set, false otherwise. + */ +- (BOOL) isInitialized; + +/** + * Serializes the message and writes it to {@code output}. This does not + * flush or close the stream. + */ +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (void) writeToOutputStream:(NSOutputStream*) output; + +/** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around + * {@link #writeTo(CodedOutputStream)}. + */ +- (NSData*) data; + +/** + * Constructs a new builder for a message of the same type as this message. + */ +- (id) builder; +@end diff --git a/Libraries/ProtocolBuffers/Message_Builder.h b/Libraries/ProtocolBuffers/Message_Builder.h new file mode 100644 index 000000000..8326d8877 --- /dev/null +++ b/Libraries/ProtocolBuffers/Message_Builder.h @@ -0,0 +1,134 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@protocol PBMessage; +@protocol PBMessage_Builder; +@class PBUnknownFieldSet; +@class PBCodedInputStream; +@class PBExtensionRegistry; + +/** + * Abstract interface implemented by Protocol Message builders. + */ +@protocol PBMessage_Builder +/** Resets all fields to their default values. */ +- (id) clear; + +/** + * Construct the final message. Once this is called, the Builder is no + * longer valid, and calling any other method may throw a + * NullPointerException. If you need to continue working with the builder + * after calling {@code build()}, {@code clone()} it first. + * @throws UninitializedMessageException The message is missing one or more + * required fields (i.e. {@link #isInitialized()} returns false). + * Use {@link #buildPartial()} to bypass this check. + */ +- (id) build; + +/** + * Like {@link #build()}, but does not throw an exception if the message + * is missing required fields. Instead, a partial message is returned. + */ +- (id) buildPartial; +- (id) clone; + +/** + * Returns true if all required fields in the message and all embedded + * messages are set, false otherwise. + */ +- (BOOL) isInitialized; + +/** + * Get the message's type's default instance. + * See {@link Message#getDefaultInstanceForType()}. + */ +- (id) defaultInstance; + +- (PBUnknownFieldSet*) unknownFields; +- (id) setUnknownFields:(PBUnknownFieldSet*) unknownFields; + +/** + * Merge some unknown fields into the {@link UnknownFieldSet} for this + * message. + */ +- (id) mergeUnknownFields:(PBUnknownFieldSet*) unknownFields; + +/** + * Parses a message of this type from the input and merges it with this + * message, as if using {@link Builder#mergeFrom(Message)}. + * + *

Warning: This does not verify that all required fields are present in + * the input message. If you call {@link #build()} without setting all + * required fields, it will throw an {@link UninitializedMessageException}, + * which is a {@code RuntimeException} and thus might not be caught. There + * are a few good ways to deal with this: + *

    + *
  • Call {@link #isInitialized()} to verify that all required fields + * are set before building. + *
  • Parse the message separately using one of the static + * {@code parseFrom} methods, then use {@link #mergeFrom(Message)} + * to merge it with this one. {@code parseFrom} will throw an + * {@link InvalidProtocolBufferException} (an {@code IOException}) + * if some required fields are missing. + *
  • Use {@code buildPartial()} to build, which ignores missing + * required fields. + *
+ * + *

Note: The caller should call + * {@link CodedInputStream#checkLastTagWas(int)} after calling this to + * verify that the last tag seen was the appropriate end-group tag, + * or zero for EOF. + */ +- (id) mergeFromCodedInputStream:(PBCodedInputStream*) input; + +/** + * Like {@link Builder#mergeFrom(CodedInputStream)}, but also + * parses extensions. The extensions that you want to be able to parse + * must be registered in {@code extensionRegistry}. Extensions not in + * the registry will be treated as unknown fields. + */ +- (id) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +/** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ +- (id) mergeFromData:(NSData*) data; + +/** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistry)}. + */ +- (id) mergeFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +/** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. Note that this method always + * reads the entire input (unless it throws an exception). If you + * want it to stop earlier, you will need to wrap your input in some + * wrapper stream that limits reading. Despite usually reading the entire + * input, this does not close the stream. + */ +- (id) mergeFromInputStream:(NSInputStream*) input; + +/** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistry)}. + */ +- (id) mergeFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end diff --git a/Libraries/ProtocolBuffers/MutableExtensionRegistry.h b/Libraries/ProtocolBuffers/MutableExtensionRegistry.h new file mode 100644 index 000000000..d2ebf7a5e --- /dev/null +++ b/Libraries/ProtocolBuffers/MutableExtensionRegistry.h @@ -0,0 +1,26 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ExtensionRegistry.h" + +@interface PBMutableExtensionRegistry : PBExtensionRegistry { +@private + NSMutableDictionary* mutableClassMap; +} + ++ (PBMutableExtensionRegistry*) registry; + +- (void) addExtension:(id) extension; + +@end diff --git a/Libraries/ProtocolBuffers/MutableExtensionRegistry.m b/Libraries/ProtocolBuffers/MutableExtensionRegistry.m new file mode 100644 index 000000000..d2409bae6 --- /dev/null +++ b/Libraries/ProtocolBuffers/MutableExtensionRegistry.m @@ -0,0 +1,65 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "MutableExtensionRegistry.h" + +#import "ExtensionField.h" + +@interface PBMutableExtensionRegistry() +@property (retain) NSMutableDictionary* mutableClassMap; +@end + +@implementation PBMutableExtensionRegistry + +@synthesize mutableClassMap; + +- (void) dealloc { + self.mutableClassMap = nil; +} + + +- (id) initWithClassMap:(NSMutableDictionary*) mutableClassMap_ { + if ((self = [super initWithClassMap:mutableClassMap_])) { + self.mutableClassMap = mutableClassMap_; + } + + return self; +} + + ++ (PBMutableExtensionRegistry*) registry { + return [[PBMutableExtensionRegistry alloc] initWithClassMap:[NSMutableDictionary dictionary]]; +} + + +- (void) addExtension:(id) extension { + if (extension == nil) { + return; + } + + Class extendedClass = [extension extendedClass]; + id key = [self keyForClass:extendedClass]; + + NSMutableDictionary* extensionMap = [classMap objectForKey:key]; + if (extensionMap == nil) { + extensionMap = [NSMutableDictionary dictionary]; + [mutableClassMap setObject:extensionMap forKey:key]; + } + + [extensionMap setObject:extension + forKey:[NSNumber numberWithInteger:[extension fieldNumber]]]; +} + + +@end diff --git a/Libraries/ProtocolBuffers/MutableField.h b/Libraries/ProtocolBuffers/MutableField.h new file mode 100644 index 000000000..d45b8dcbd --- /dev/null +++ b/Libraries/ProtocolBuffers/MutableField.h @@ -0,0 +1,33 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Field.h" + +@class PBUnknownFieldSet; + +@interface PBMutableField : PBField { +} + ++ (PBMutableField*) field; + +- (PBMutableField*) mergeFromField:(PBField*) other; + +- (PBMutableField*) clear; +- (PBMutableField*) addVarint:(int64_t) value; +- (PBMutableField*) addFixed32:(int32_t) value; +- (PBMutableField*) addFixed64:(int64_t) value; +- (PBMutableField*) addLengthDelimited:(NSData*) value; +- (PBMutableField*) addGroup:(PBUnknownFieldSet*) value; + +@end diff --git a/Libraries/ProtocolBuffers/MutableField.m b/Libraries/ProtocolBuffers/MutableField.m new file mode 100644 index 000000000..17712bd7f --- /dev/null +++ b/Libraries/ProtocolBuffers/MutableField.m @@ -0,0 +1,138 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "MutableField.h" + +#import "Field.h" + +@interface PBField () +@property (retain) NSMutableArray* mutableVarintList; +@property (retain) NSMutableArray* mutableFixed32List; +@property (retain) NSMutableArray* mutableFixed64List; +@property (retain) NSMutableArray* mutableLengthDelimitedList; +@property (retain) NSMutableArray* mutableGroupList; +@end + + +@implementation PBMutableField + + ++ (PBMutableField*) field { + return [[PBMutableField alloc] init]; +} + + +- (id) init { + if ((self = [super init])) { + } + + return self; +} + + +- (PBMutableField*) clear { + self.mutableVarintList = nil; + self.mutableFixed32List = nil; + self.mutableFixed64List = nil; + self.mutableLengthDelimitedList = nil; + self.mutableGroupList = nil; + return self; +} + + +- (PBMutableField*) mergeFromField:(PBField*) other { + if (other.varintList.count > 0) { + if (mutableVarintList == nil) { + self.mutableVarintList = [NSMutableArray array]; + } + [mutableVarintList addObjectsFromArray:other.varintList]; + } + + if (other.fixed32List.count > 0) { + if (mutableFixed32List == nil) { + self.mutableFixed32List = [NSMutableArray array]; + } + [mutableFixed32List addObjectsFromArray:other.fixed32List]; + } + + if (other.fixed64List.count > 0) { + if (mutableFixed64List == nil) { + self.mutableFixed64List = [NSMutableArray array]; + } + [mutableFixed64List addObjectsFromArray:other.fixed64List]; + } + + if (other.lengthDelimitedList.count > 0) { + if (mutableLengthDelimitedList == nil) { + self.mutableLengthDelimitedList = [NSMutableArray array]; + } + [mutableLengthDelimitedList addObjectsFromArray:other.lengthDelimitedList]; + } + + if (other.groupList.count > 0) { + if (mutableGroupList == nil) { + self.mutableGroupList = [NSMutableArray array]; + } + [mutableGroupList addObjectsFromArray:other.groupList]; + } + + return self; +} + + +- (PBMutableField*) addVarint:(int64_t) value { + if (mutableVarintList == nil) { + self.mutableVarintList = [NSMutableArray array]; + } + [mutableVarintList addObject:[NSNumber numberWithLongLong:value]]; + return self; +} + + +- (PBMutableField*) addFixed32:(int32_t) value { + if (mutableFixed32List == nil) { + self.mutableFixed32List = [NSMutableArray array]; + } + [mutableFixed32List addObject:[NSNumber numberWithInt:value]]; + return self; +} + + +- (PBMutableField*) addFixed64:(int64_t) value { + if (mutableFixed64List == nil) { + self.mutableFixed64List = [NSMutableArray array]; + } + [mutableFixed64List addObject:[NSNumber numberWithLongLong:value]]; + return self; +} + + +- (PBMutableField*) addLengthDelimited:(NSData*) value { + if (mutableLengthDelimitedList == nil) { + self.mutableLengthDelimitedList = [NSMutableArray array]; + } + [mutableLengthDelimitedList addObject:value]; + return self; +} + + +- (PBMutableField*) addGroup:(PBUnknownFieldSet*) value { + if (mutableGroupList == nil) { + self.mutableGroupList = [NSMutableArray array]; + } + [mutableGroupList addObject:value]; + return self; +} + +@end diff --git a/Libraries/ProtocolBuffers/ProtocolBuffers.h b/Libraries/ProtocolBuffers/ProtocolBuffers.h new file mode 100644 index 000000000..5a13609ef --- /dev/null +++ b/Libraries/ProtocolBuffers/ProtocolBuffers.h @@ -0,0 +1,36 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Bootstrap.h" + +#import "AbstractMessage.h" +#import "AbstractMessage_Builder.h" +#import "CodedInputStream.h" +#import "CodedOutputStream.h" +#import "ConcreteExtensionField.h" +#import "ExtendableMessage.h" +#import "ExtendableMessage_Builder.h" +#import "ExtensionField.h" +#import "ExtensionRegistry.h" +#import "Field.h" +#import "GeneratedMessage.h" +#import "GeneratedMessage_Builder.h" +#import "Message.h" +#import "Message_Builder.h" +#import "MutableExtensionRegistry.h" +#import "MutableField.h" +#import "UnknownFieldSet.h" +#import "UnknownFieldSet_Builder.h" +#import "Utilities.h" +#import "WireFormat.h" diff --git a/Libraries/ProtocolBuffers/TextFormat.h b/Libraries/ProtocolBuffers/TextFormat.h new file mode 100644 index 000000000..7153bd8c7 --- /dev/null +++ b/Libraries/ProtocolBuffers/TextFormat.h @@ -0,0 +1,26 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@interface PBTextFormat : NSObject { + +} + ++ (int32_t) parseInt32:(NSString*) text; ++ (int32_t) parseUInt32:(NSString*) text; ++ (int64_t) parseInt64:(NSString*) text; ++ (int64_t) parseUInt64:(NSString*) text; + ++ (NSData*) unescapeBytes:(NSString*) input; + +@end diff --git a/Libraries/ProtocolBuffers/TextFormat.m b/Libraries/ProtocolBuffers/TextFormat.m new file mode 100644 index 000000000..c822ff16e --- /dev/null +++ b/Libraries/ProtocolBuffers/TextFormat.m @@ -0,0 +1,238 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "TextFormat.h" + +#import "Utilities.h" + +@implementation PBTextFormat + + +BOOL allZeroes(NSString* string); +BOOL allZeroes(NSString* string) { + for (int i = 0; i < (int32_t)string.length; i++) { + if ([string characterAtIndex:(NSUInteger)i] != '0') { + return NO; + } + } + + return YES; +} + + +/** Is this an octal digit? */ +BOOL isOctal(unichar c); +BOOL isOctal(unichar c) { + return '0' <= c && c <= '7'; +} + + +/** Is this an octal digit? */ +BOOL isDecimal(unichar c); +BOOL isDecimal(unichar c) { + return '0' <= c && c <= '9'; +} + +/** Is this a hex digit? */ +BOOL isHex(unichar c); +BOOL isHex(unichar c) { + return + isDecimal(c) || + ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'); +} + + ++ (int64_t) parseInteger:(NSString*) text + isSigned:(BOOL) isSigned + isLong:(BOOL) isLong { + if (text.length == 0) { + @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number was blank" userInfo:nil]; + } + + if (isblank([text characterAtIndex:0])) { + @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Invalid character" userInfo:nil]; + } + + if ([text hasPrefix:@"-"]) { + if (!isSigned) { + @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number must be positive" userInfo:nil]; + } + } + + // now call into the appropriate conversion utilities. + int64_t result; + const char* in_string = text.UTF8String; + char* out_string = NULL; + errno = 0; + if (isLong) { + if (isSigned) { + result = strtoll(in_string, &out_string, 0); + } else { + result = convertUInt64ToInt64(strtoull(in_string, &out_string, 0)); + } + } else { + if (isSigned) { + result = strtol(in_string, &out_string, 0); + } else { + result = convertUInt32ToInt32(strtoul(in_string, &out_string, 0)); + } + } + + // from the man pages: + // (Thus, i* tr is not `\0' but **endptr is `\0' on return, the entire + // string was valid.) + if (*in_string == 0 || *out_string != 0) { + @throw [NSException exceptionWithName:@"NumberFormat" reason:@"IllegalNumber" userInfo:nil]; + } + + if (errno == ERANGE) { + @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number out of range" userInfo:nil]; + } + + return result; +} + + +/** + * Parse a 32-bit signed integer from the text. This function recognizes + * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, + * respectively. + */ ++ (int32_t) parseInt32:(NSString*) text { + return (int32_t)[self parseInteger:text isSigned:YES isLong:NO]; +} + + +/** + * Parse a 32-bit unsigned integer from the text. This function recognizes + * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, + * respectively. The result is coerced to a (signed) {@code int} when returned. + */ ++ (int32_t) parseUInt32:(NSString*) text { + return (int32_t)[self parseInteger:text isSigned:NO isLong:NO]; +} + + +/** + * Parse a 64-bit signed integer from the text. This function recognizes + * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, + * respectively. + */ ++ (int64_t) parseInt64:(NSString*) text { + return [self parseInteger:text isSigned:YES isLong:YES]; +} + + +/** + * Parse a 64-bit unsigned integer from the text. This function recognizes + * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, + * respectively. The result is coerced to a (signed) {@code long} when + * returned. + */ ++ (int64_t) parseUInt64:(NSString*) text { + return [self parseInteger:text isSigned:NO isLong:YES]; +} + +/** + * Interpret a character as a digit (in any base up to 36) and return the + * numeric value. This is like {@code Character.digit()} but we don't accept + * non-ASCII digits. + */ +int32_t digitValue(unichar c); +int32_t digitValue(unichar c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('a' <= c && c <= 'z') { + return c - 'a' + 10; + } else { + return c - 'A' + 10; + } +} + + +/** + * Un-escape a byte sequence as escaped using + * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with + * "\x") are also recognized. + */ ++ (NSData*) unescapeBytes:(NSString*) input { + NSMutableData* result = [NSMutableData dataWithLength:input.length]; + + int32_t pos = 0; + for (int32_t i = 0; i < (int32_t)input.length; i++) { + unichar c = [input characterAtIndex:(NSUInteger)i]; + if (c == '\\') { + if (i + 1 < (int32_t)input.length) { + ++i; + c = [input characterAtIndex:(NSUInteger)i]; + if (isOctal(c)) { + // Octal escape. + int32_t code = digitValue(c); + if (i + 1 < (int32_t)input.length && isOctal([input characterAtIndex:(NSUInteger)(i + 1)])) { + ++i; + code = code * 8 + digitValue([input characterAtIndex:(NSUInteger)i]); + } + if (i + 1 < (int32_t)input.length && isOctal([input characterAtIndex:(NSUInteger)(i + 1)])) { + ++i; + code = code * 8 + digitValue([input characterAtIndex:(NSUInteger)i]); + } + ((int8_t*)result.mutableBytes)[pos++] = (int8_t)code; + } else { + switch (c) { + case 'a' : ((int8_t*)result.mutableBytes)[pos++] = 0x07; break; + case 'b' : ((int8_t*)result.mutableBytes)[pos++] = '\b'; break; + case 'f' : ((int8_t*)result.mutableBytes)[pos++] = '\f'; break; + case 'n' : ((int8_t*)result.mutableBytes)[pos++] = '\n'; break; + case 'r' : ((int8_t*)result.mutableBytes)[pos++] = '\r'; break; + case 't' : ((int8_t*)result.mutableBytes)[pos++] = '\t'; break; + case 'v' : ((int8_t*)result.mutableBytes)[pos++] = 0x0b; break; + case '\\': ((int8_t*)result.mutableBytes)[pos++] = '\\'; break; + case '\'': ((int8_t*)result.mutableBytes)[pos++] = '\''; break; + case '"' : ((int8_t*)result.mutableBytes)[pos++] = '\"'; break; + + case 'x': // hex escape + { + int32_t code = 0; + if (i + 1 < (int32_t)input.length && isHex([input characterAtIndex:(NSUInteger)(i + 1)])) { + ++i; + code = digitValue([input characterAtIndex:(NSUInteger)i]); + } else { + @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence: '\\x' with no digits" userInfo:nil]; + } + if (i + 1 < (int32_t)input.length && isHex([input characterAtIndex:(NSUInteger)(i + 1)])) { + ++i; + code = code * 16 + digitValue([input characterAtIndex:(NSUInteger)i]); + } + ((int8_t*)result.mutableBytes)[pos++] = (int8_t)code; + break; + } + + default: + @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence" userInfo:nil]; + } + } + } else { + @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence: '\\' at end of string" userInfo:nil]; + } + } else { + ((int8_t*)result.mutableBytes)[pos++] = (int8_t)c; + } + } + + [result setLength:(NSUInteger)pos]; + return result; +} + +@end diff --git a/Libraries/ProtocolBuffers/UnknownFieldSet.h b/Libraries/ProtocolBuffers/UnknownFieldSet.h new file mode 100644 index 000000000..6fd88be55 --- /dev/null +++ b/Libraries/ProtocolBuffers/UnknownFieldSet.h @@ -0,0 +1,45 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBUnknownFieldSet_Builder; +@class PBUnknownFieldSet; +@class PBField; +@class PBCodedOutputStream; + +@interface PBUnknownFieldSet : NSObject { +@private + NSDictionary* fields; +} + +@property (readonly, retain) NSDictionary* fields; + ++ (PBUnknownFieldSet*) defaultInstance; + ++ (PBUnknownFieldSet*) setWithFields:(NSMutableDictionary*) fields; ++ (PBUnknownFieldSet*) parseFromData:(NSData*) data; + ++ (PBUnknownFieldSet_Builder*) builder; ++ (PBUnknownFieldSet_Builder*) builderWithUnknownFields:(PBUnknownFieldSet*) other; + +- (void) writeAsMessageSetTo:(PBCodedOutputStream*) output; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (NSData*) data; + +- (int32_t) serializedSize; +- (int32_t) serializedSizeAsMessageSet; + +- (BOOL) hasField:(int32_t) number; +- (PBField*) getField:(int32_t) number; + +@end diff --git a/Libraries/ProtocolBuffers/UnknownFieldSet.m b/Libraries/ProtocolBuffers/UnknownFieldSet.m new file mode 100644 index 000000000..1c0bcc135 --- /dev/null +++ b/Libraries/ProtocolBuffers/UnknownFieldSet.m @@ -0,0 +1,161 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "UnknownFieldSet.h" + +#import "CodedInputStream.h" +#import "CodedOutputStream.h" +#import "Field.h" +#import "UnknownFieldSet_Builder.h" + +@interface PBUnknownFieldSet() +@property (retain) NSDictionary* fields; +@end + + +@implementation PBUnknownFieldSet + +static PBUnknownFieldSet* defaultInstance = nil; + ++ (void) initialize { + if (self == [PBUnknownFieldSet class]) { + defaultInstance = [PBUnknownFieldSet setWithFields:[NSMutableDictionary dictionary]]; + } +} + + +@synthesize fields; + +- (void) dealloc { + self.fields = nil; +} + + ++ (PBUnknownFieldSet*) defaultInstance { + return defaultInstance; +} + + +- (id) initWithFields:(NSMutableDictionary*) fields_ { + if ((self = [super init])) { + self.fields = fields_; + } + + return self; +} + + ++ (PBUnknownFieldSet*) setWithFields:(NSMutableDictionary*) fields { + return [[PBUnknownFieldSet alloc] initWithFields:fields]; +} + + +- (BOOL) hasField:(int32_t) number { + return [fields objectForKey:[NSNumber numberWithInt:number]] != nil; +} + + +- (PBField*) getField:(int32_t) number { + PBField* result = [fields objectForKey:[NSNumber numberWithInt:number]]; + return (result == nil) ? [PBField defaultInstance] : result; +} + + +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + NSArray* sortedKeys = [fields.allKeys sortedArrayUsingSelector:@selector(compare:)]; + for (NSNumber* number in sortedKeys) { + PBField* value = [fields objectForKey:number]; + [value writeTo:number.intValue output:output]; + } +} + + +- (void) writeToOutputStream:(NSOutputStream*) output { + PBCodedOutputStream* codedOutput = [PBCodedOutputStream streamWithOutputStream:output]; + [self writeToCodedOutputStream:codedOutput]; + [codedOutput flush]; +} + + ++ (PBUnknownFieldSet*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return [[[PBUnknownFieldSet builder] mergeFromCodedInputStream:input] build]; +} + + ++ (PBUnknownFieldSet*) parseFromData:(NSData*) data { + return [[[PBUnknownFieldSet builder] mergeFromData:data] build]; +} + + ++ (PBUnknownFieldSet*) parseFromInputStream:(NSInputStream*) input { + return [[[PBUnknownFieldSet builder] mergeFromInputStream:input] build]; +} + + ++ (PBUnknownFieldSet_Builder*) builder { + return [[PBUnknownFieldSet_Builder alloc] init]; +} + + ++ (PBUnknownFieldSet_Builder*) builderWithUnknownFields:(PBUnknownFieldSet*) copyFrom { + return [[PBUnknownFieldSet builder] mergeUnknownFields:copyFrom]; +} + + +/** Get the number of bytes required to encode this set. */ +- (int32_t) serializedSize { + int32_t result = 0; + for (NSNumber* number in fields) { + result += [[fields objectForKey:number] getSerializedSize:number.intValue]; + } + return result; +} + +/** + * Serializes the set and writes it to {@code output} using + * {@code MessageSet} wire format. + */ +- (void) writeAsMessageSetTo:(PBCodedOutputStream*) output { + for (NSNumber* number in fields) { + [[fields objectForKey:number] writeAsMessageSetExtensionTo:number.intValue output:output]; + } +} + + +/** + * Get the number of bytes required to encode this set using + * {@code MessageSet} wire format. + */ +- (int32_t) serializedSizeAsMessageSet { + int32_t result = 0; + for (NSNumber* number in fields) { + result += [[fields objectForKey:number] getSerializedSizeAsMessageSetExtension:number.intValue]; + } + return result; +} + + +/** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around {@link #writeTo(PBCodedOutputStream)}. + */ +- (NSData*) data { + NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)self.serializedSize]; + PBCodedOutputStream* output = [PBCodedOutputStream streamWithData:data]; + + [self writeToCodedOutputStream:output]; + return data; +} + +@end diff --git a/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.h b/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.h new file mode 100644 index 000000000..f8ed7899c --- /dev/null +++ b/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.h @@ -0,0 +1,51 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@class PBField; +@class PBMutableField; +@class PBUnknownFieldSet; +@class PBUnknownFieldSet_Builder; +@class PBCodedInputStream; + +@interface PBUnknownFieldSet_Builder : NSObject { +@private + NSMutableDictionary* fields; + + // Optimization: We keep around a builder for the last field that was + // modified so that we can efficiently add to it multiple times in a + // row (important when parsing an unknown repeated field). + int32_t lastFieldNumber; + + PBMutableField* lastField; +} + ++ (PBUnknownFieldSet_Builder*) newBuilder:(PBUnknownFieldSet*) unknownFields; + +- (PBUnknownFieldSet*) build; +- (PBUnknownFieldSet_Builder*) mergeUnknownFields:(PBUnknownFieldSet*) other; + +- (PBUnknownFieldSet_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (PBUnknownFieldSet_Builder*) mergeFromData:(NSData*) data; +- (PBUnknownFieldSet_Builder*) mergeFromInputStream:(NSInputStream*) input; + +- (PBUnknownFieldSet_Builder*) mergeVarintField:(int32_t) number value:(int32_t) value; + +- (BOOL) mergeFieldFrom:(int32_t) tag input:(PBCodedInputStream*) input; + +- (PBUnknownFieldSet_Builder*) addField:(PBField*) field forNumber:(int32_t) number; + +- (PBUnknownFieldSet_Builder*) clear; +- (PBUnknownFieldSet_Builder*) mergeField:(PBField*) field forNumber:(int32_t) number; + +@end diff --git a/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.m b/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.m new file mode 100644 index 000000000..957e6fc2f --- /dev/null +++ b/Libraries/ProtocolBuffers/UnknownFieldSet_Builder.m @@ -0,0 +1,237 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "UnknownFieldSet_Builder.h" + +#import "CodedInputStream.h" +#import "Field.h" +#import "MutableField.h" +#import "UnknownFieldSet.h" +#import "WireFormat.h" + +@interface PBUnknownFieldSet_Builder () +@property (retain) NSMutableDictionary* fields; +@property int32_t lastFieldNumber; +@property (retain) PBMutableField* lastField; +@end + + +@implementation PBUnknownFieldSet_Builder + +@synthesize fields; +@synthesize lastFieldNumber; +@synthesize lastField; + + +- (void) dealloc { + self.fields = nil; + self.lastFieldNumber = 0; + self.lastField = nil; +} + + +- (id) init { + if ((self = [super init])) { + self.fields = [NSMutableDictionary dictionary]; + } + return self; +} + + ++ (PBUnknownFieldSet_Builder*) newBuilder:(PBUnknownFieldSet*) unknownFields { + PBUnknownFieldSet_Builder* builder = [[PBUnknownFieldSet_Builder alloc] init]; + [builder mergeUnknownFields:unknownFields]; + return builder; +} + + +/** + * Add a field to the {@code PBUnknownFieldSet}. If a field with the same + * number already exists, it is removed. + */ +- (PBUnknownFieldSet_Builder*) addField:(PBField*) field forNumber:(int32_t) number { + if (number == 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"" userInfo:nil]; + } + if (lastField != nil && lastFieldNumber == number) { + // Discard this. + self.lastField = nil; + lastFieldNumber = 0; + } + [fields setObject:field forKey:[NSNumber numberWithInt:number]]; + return self; +} + + +/** + * Get a field builder for the given field number which includes any + * values that already exist. + */ +- (PBMutableField*) getFieldBuilder:(int32_t) number { + if (lastField != nil) { + if (number == lastFieldNumber) { + return lastField; + } + // Note: addField() will reset lastField and lastFieldNumber. + [self addField:lastField forNumber:lastFieldNumber]; + } + if (number == 0) { + return nil; + } else { + PBField* existing = [fields objectForKey:[NSNumber numberWithInt:number]]; + lastFieldNumber = number; + self.lastField = [PBMutableField field]; + if (existing != nil) { + [lastField mergeFromField:existing]; + } + return lastField; + } +} + + +- (PBUnknownFieldSet*) build { + [self getFieldBuilder:0]; // Force lastField to be built. + PBUnknownFieldSet* result; + if (fields.count == 0) { + result = [PBUnknownFieldSet defaultInstance]; + } else { + result = [PBUnknownFieldSet setWithFields:fields]; + } + self.fields = nil; + return result; +} + + +/** Check if the given field number is present in the set. */ +- (BOOL) hasField:(int32_t) number { + if (number == 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"" userInfo:nil]; + } + + return number == lastFieldNumber || ([fields objectForKey:[NSNumber numberWithInt:number]] != nil); +} + + +/** + * Add a field to the {@code PBUnknownFieldSet}. If a field with the same + * number already exists, the two are merged. + */ +- (PBUnknownFieldSet_Builder*) mergeField:(PBField*) field forNumber:(int32_t) number { + if (number == 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"" userInfo:nil]; + } + if ([self hasField:number]) { + [[self getFieldBuilder:number] mergeFromField:field]; + } else { + // Optimization: We could call getFieldBuilder(number).mergeFrom(field) + // in this case, but that would create a copy of the PBField object. + // We'd rather reuse the one passed to us, so call addField() instead. + [self addField:field forNumber:number]; + } + + return self; +} + + +- (PBUnknownFieldSet_Builder*) mergeUnknownFields:(PBUnknownFieldSet*) other { + if (other != [PBUnknownFieldSet defaultInstance]) { + for (NSNumber* number in other.fields) { + PBField* field = [other.fields objectForKey:number]; + [self mergeField:field forNumber:[number intValue]]; + } + } + return self; +} + + +- (PBUnknownFieldSet_Builder*) mergeFromData:(NSData*) data { + PBCodedInputStream* input = [PBCodedInputStream streamWithData:data]; + [self mergeFromCodedInputStream:input]; + [input checkLastTagWas:0]; + return self; +} + + +- (PBUnknownFieldSet_Builder*) mergeFromInputStream:(NSInputStream*) input { + @throw [NSException exceptionWithName:@"" reason:@"" userInfo:nil]; +} + + +- (PBUnknownFieldSet_Builder*) mergeVarintField:(int32_t) number value:(int32_t) value { + if (number == 0) { + @throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Zero is not a valid field number." userInfo:nil]; + } + + [[self getFieldBuilder:number] addVarint:value]; + return self; +} + + +/** + * Parse a single field from {@code input} and merge it into this set. + * @param tag The field's tag number, which was already parsed. + * @return {@code NO} if the tag is an engroup tag. + */ +- (BOOL) mergeFieldFrom:(int32_t) tag input:(PBCodedInputStream*) input { + int32_t number = PBWireFormatGetTagFieldNumber(tag); + switch (PBWireFormatGetTagWireType(tag)) { + case PBWireFormatVarint: + [[self getFieldBuilder:number] addVarint:[input readInt64]]; + return YES; + case PBWireFormatFixed64: + [[self getFieldBuilder:number] addFixed64:[input readFixed64]]; + return YES; + case PBWireFormatLengthDelimited: + [[self getFieldBuilder:number] addLengthDelimited:[input readData]]; + return YES; + case PBWireFormatStartGroup: { + PBUnknownFieldSet_Builder* subBuilder = [PBUnknownFieldSet builder]; + [input readUnknownGroup:number builder:subBuilder]; + [[self getFieldBuilder:number] addGroup:[subBuilder build]]; + return YES; + } + case PBWireFormatEndGroup: + return NO; + case PBWireFormatFixed32: + [[self getFieldBuilder:number] addFixed32:[input readFixed32]]; + return YES; + default: + @throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"" userInfo:nil]; + } +} + + +/** + * Parse an entire message from {@code input} and merge its fields into + * this set. + */ +- (PBUnknownFieldSet_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + while (YES) { + int32_t tag = [input readTag]; + if (tag == 0 || ![self mergeFieldFrom:tag input:input]) { + break; + } + } + return self; +} + + +- (PBUnknownFieldSet_Builder*) clear { + self.fields = [NSMutableDictionary dictionary]; + self.lastFieldNumber = 0; + self.lastField = nil; + return self; +} + +@end diff --git a/Libraries/ProtocolBuffers/Utilities.h b/Libraries/ProtocolBuffers/Utilities.h new file mode 100644 index 000000000..288a9a5c3 --- /dev/null +++ b/Libraries/ProtocolBuffers/Utilities.h @@ -0,0 +1,26 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +int64_t convertFloat64ToInt64(Float64 f); +int32_t convertFloat32ToInt32(Float32 f); +Float64 convertInt64ToFloat64(int64_t f); +Float32 convertInt32ToFloat32(int32_t f); + +uint64_t convertInt64ToUInt64(int64_t i); +int64_t convertUInt64ToInt64(uint64_t u); +uint32_t convertInt32ToUInt32(int32_t i); +int64_t convertUInt32ToInt32(uint32_t u); + +int32_t logicalRightShift32(int32_t value, int32_t spaces); +int64_t logicalRightShift64(int64_t value, int32_t spaces); diff --git a/Libraries/ProtocolBuffers/Utilities.m b/Libraries/ProtocolBuffers/Utilities.m new file mode 100644 index 000000000..3a98a7f8f --- /dev/null +++ b/Libraries/ProtocolBuffers/Utilities.m @@ -0,0 +1,79 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Utilities.h" + +int64_t convertFloat64ToInt64(Float64 v) { + union { Float64 f; int64_t i; } u; + u.f = v; + return u.i; +} + + +int32_t convertFloat32ToInt32(Float32 v) { + union { Float32 f; int32_t i; } u; + u.f = v; + return u.i; +} + + +Float64 convertInt64ToFloat64(int64_t v) { + union { Float64 f; int64_t i; } u; + u.i = v; + return u.f; +} + + +Float32 convertInt32ToFloat32(int32_t v) { + union { Float32 f; int32_t i; } u; + u.i = v; + return u.f; +} + + +uint64_t convertInt64ToUInt64(int64_t v) { + union { int64_t i; uint64_t u; } u; + u.i = v; + return u.u; +} + + +int64_t convertUInt64ToInt64(uint64_t v) { + union { int64_t i; uint64_t u; } u; + u.u = v; + return u.i; +} + +uint32_t convertInt32ToUInt32(int32_t v) { + union { int32_t i; uint32_t u; } u; + u.i = v; + return u.u; +} + + +int64_t convertUInt32ToInt32(uint32_t v) { + union { int32_t i; uint32_t u; } u; + u.u = v; + return u.i; +} + + +int32_t logicalRightShift32(int32_t value, int32_t spaces) { + return (int32_t)convertUInt32ToInt32((convertInt32ToUInt32(value) >> spaces)); +} + + +int64_t logicalRightShift64(int64_t value, int32_t spaces) { + return convertUInt64ToInt64((convertInt64ToUInt64(value) >> spaces)); +} diff --git a/Libraries/ProtocolBuffers/WireFormat.h b/Libraries/ProtocolBuffers/WireFormat.h new file mode 100644 index 000000000..d0e0a34f4 --- /dev/null +++ b/Libraries/ProtocolBuffers/WireFormat.h @@ -0,0 +1,38 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +typedef enum { + PBWireFormatVarint = 0, + PBWireFormatFixed64 = 1, + PBWireFormatLengthDelimited = 2, + PBWireFormatStartGroup = 3, + PBWireFormatEndGroup = 4, + PBWireFormatFixed32 = 5, + + PBWireFormatTagTypeBits = 3, + PBWireFormatTagTypeMask = 7 /* = (1 << PBWireFormatTagTypeBits) - 1*/, + + PBWireFormatMessageSetItem = 1, + PBWireFormatMessageSetTypeId = 2, + PBWireFormatMessageSetMessage = 3 +} PBWireFormat; + +int32_t PBWireFormatMakeTag(int32_t fieldNumber, int32_t wireType); +int32_t PBWireFormatGetTagWireType(int32_t tag); +int32_t PBWireFormatGetTagFieldNumber(int32_t tag); + +#define PBWireFormatMessageSetItemTag (PBWireFormatMakeTag(PBWireFormatMessageSetItem, PBWireFormatStartGroup)) +#define PBWireFormatMessageSetItemEndTag (PBWireFormatMakeTag(PBWireFormatMessageSetItem, PBWireFormatEndGroup)) +#define PBWireFormatMessageSetTypeIdTag (PBWireFormatMakeTag(PBWireFormatMessageSetTypeId, PBWireFormatVarint)) +#define PBWireFormatMessageSetMessageTag (PBWireFormatMakeTag(PBWireFormatMessageSetMessage, PBWireFormatLengthDelimited)) diff --git a/Libraries/ProtocolBuffers/WireFormat.m b/Libraries/ProtocolBuffers/WireFormat.m new file mode 100644 index 000000000..65ab84128 --- /dev/null +++ b/Libraries/ProtocolBuffers/WireFormat.m @@ -0,0 +1,31 @@ +// Copyright 2008 Cyrus Najmabadi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "WireFormat.h" + +#import "Utilities.h" + +int32_t PBWireFormatMakeTag(int32_t fieldNumber, int32_t wireType) { + return (fieldNumber << PBWireFormatTagTypeBits) | wireType; +} + + +int32_t PBWireFormatGetTagWireType(int32_t tag) { + return tag & PBWireFormatTagTypeMask; +} + + +int32_t PBWireFormatGetTagFieldNumber(int32_t tag) { + return logicalRightShift32(tag, PBWireFormatTagTypeBits); +} diff --git a/Libraries/spandsp/spandsp/spandsp.xcodeproj/project.pbxproj b/Libraries/spandsp/spandsp/spandsp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..313cb11c7 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp.xcodeproj/project.pbxproj @@ -0,0 +1,777 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A1B989FE1725EC3E00B6E8B5 /* time_scale.c in Sources */ = {isa = PBXBuildFile; fileRef = A1B989FD1725EC3E00B6E8B5 /* time_scale.c */; }; + A1B98A8A1725EC6400B6E8B5 /* adsi.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A011725EC6400B6E8B5 /* adsi.h */; }; + A1B98A8B1725EC6400B6E8B5 /* arctan2.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A021725EC6400B6E8B5 /* arctan2.h */; }; + A1B98A8C1725EC6400B6E8B5 /* async.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A031725EC6400B6E8B5 /* async.h */; }; + A1B98A8D1725EC6400B6E8B5 /* at_interpreter.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A041725EC6400B6E8B5 /* at_interpreter.h */; }; + A1B98A8E1725EC6400B6E8B5 /* awgn.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A051725EC6400B6E8B5 /* awgn.h */; }; + A1B98A8F1725EC6400B6E8B5 /* bell_r2_mf.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A061725EC6400B6E8B5 /* bell_r2_mf.h */; }; + A1B98A901725EC6400B6E8B5 /* bert.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A071725EC6400B6E8B5 /* bert.h */; }; + A1B98A911725EC6400B6E8B5 /* biquad.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A081725EC6400B6E8B5 /* biquad.h */; }; + A1B98A921725EC6400B6E8B5 /* bit_operations.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A091725EC6400B6E8B5 /* bit_operations.h */; }; + A1B98A931725EC6400B6E8B5 /* bitstream.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0A1725EC6400B6E8B5 /* bitstream.h */; }; + A1B98A941725EC6400B6E8B5 /* complex.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0B1725EC6400B6E8B5 /* complex.h */; }; + A1B98A951725EC6400B6E8B5 /* complex_filters.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0C1725EC6400B6E8B5 /* complex_filters.h */; }; + A1B98A961725EC6400B6E8B5 /* complex_vector_float.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0D1725EC6400B6E8B5 /* complex_vector_float.h */; }; + A1B98A971725EC6400B6E8B5 /* complex_vector_int.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0E1725EC6400B6E8B5 /* complex_vector_int.h */; }; + A1B98A981725EC6400B6E8B5 /* crc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A0F1725EC6400B6E8B5 /* crc.h */; }; + A1B98A991725EC6400B6E8B5 /* dc_restore.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A101725EC6400B6E8B5 /* dc_restore.h */; }; + A1B98A9A1725EC6400B6E8B5 /* dds.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A111725EC6400B6E8B5 /* dds.h */; }; + A1B98A9B1725EC6400B6E8B5 /* dtmf.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A121725EC6400B6E8B5 /* dtmf.h */; }; + A1B98A9C1725EC6400B6E8B5 /* echo.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A131725EC6400B6E8B5 /* echo.h */; }; + A1B98A9D1725EC6400B6E8B5 /* expose.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A141725EC6400B6E8B5 /* expose.h */; }; + A1B98A9E1725EC6400B6E8B5 /* fast_convert.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A151725EC6400B6E8B5 /* fast_convert.h */; }; + A1B98A9F1725EC6400B6E8B5 /* fax.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A161725EC6400B6E8B5 /* fax.h */; }; + A1B98AA01725EC6400B6E8B5 /* fax_modems.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A171725EC6400B6E8B5 /* fax_modems.h */; }; + A1B98AA11725EC6400B6E8B5 /* fir.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A181725EC6400B6E8B5 /* fir.h */; }; + A1B98AA21725EC6400B6E8B5 /* fsk.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A191725EC6400B6E8B5 /* fsk.h */; }; + A1B98AA31725EC6400B6E8B5 /* g168models.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1A1725EC6400B6E8B5 /* g168models.h */; }; + A1B98AA41725EC6400B6E8B5 /* g711.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1B1725EC6400B6E8B5 /* g711.h */; }; + A1B98AA51725EC6400B6E8B5 /* g722.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1C1725EC6400B6E8B5 /* g722.h */; }; + A1B98AA61725EC6400B6E8B5 /* g726.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1D1725EC6400B6E8B5 /* g726.h */; }; + A1B98AA71725EC6400B6E8B5 /* gsm0610.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1E1725EC6400B6E8B5 /* gsm0610.h */; }; + A1B98AA81725EC6400B6E8B5 /* hdlc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A1F1725EC6400B6E8B5 /* hdlc.h */; }; + A1B98AA91725EC6400B6E8B5 /* ima_adpcm.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A201725EC6400B6E8B5 /* ima_adpcm.h */; }; + A1B98AAA1725EC6400B6E8B5 /* logging.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A211725EC6400B6E8B5 /* logging.h */; }; + A1B98AAB1725EC6400B6E8B5 /* lpc10.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A221725EC6400B6E8B5 /* lpc10.h */; }; + A1B98AAC1725EC6400B6E8B5 /* modem_connect_tones.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A231725EC6400B6E8B5 /* modem_connect_tones.h */; }; + A1B98AAD1725EC6400B6E8B5 /* modem_echo.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A241725EC6400B6E8B5 /* modem_echo.h */; }; + A1B98AAE1725EC6400B6E8B5 /* noise.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A251725EC6400B6E8B5 /* noise.h */; }; + A1B98AAF1725EC6400B6E8B5 /* oki_adpcm.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A261725EC6400B6E8B5 /* oki_adpcm.h */; }; + A1B98AB01725EC6400B6E8B5 /* playout.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A271725EC6400B6E8B5 /* playout.h */; }; + A1B98AB11725EC6400B6E8B5 /* plc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A281725EC6400B6E8B5 /* plc.h */; }; + A1B98AB21725EC6400B6E8B5 /* power_meter.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A291725EC6400B6E8B5 /* power_meter.h */; }; + A1B98AB31725EC6400B6E8B5 /* adsi.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A2B1725EC6400B6E8B5 /* adsi.h */; }; + A1B98AB41725EC6400B6E8B5 /* async.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A2C1725EC6400B6E8B5 /* async.h */; }; + A1B98AB51725EC6400B6E8B5 /* at_interpreter.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A2D1725EC6400B6E8B5 /* at_interpreter.h */; }; + A1B98AB61725EC6400B6E8B5 /* awgn.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A2E1725EC6400B6E8B5 /* awgn.h */; }; + A1B98AB71725EC6500B6E8B5 /* bell_r2_mf.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A2F1725EC6400B6E8B5 /* bell_r2_mf.h */; }; + A1B98AB81725EC6500B6E8B5 /* bert.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A301725EC6400B6E8B5 /* bert.h */; }; + A1B98AB91725EC6500B6E8B5 /* bitstream.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A311725EC6400B6E8B5 /* bitstream.h */; }; + A1B98ABA1725EC6500B6E8B5 /* dtmf.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A321725EC6400B6E8B5 /* dtmf.h */; }; + A1B98ABB1725EC6500B6E8B5 /* echo.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A331725EC6400B6E8B5 /* echo.h */; }; + A1B98ABC1725EC6500B6E8B5 /* fax.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A341725EC6400B6E8B5 /* fax.h */; }; + A1B98ABD1725EC6500B6E8B5 /* fax_modems.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A351725EC6400B6E8B5 /* fax_modems.h */; }; + A1B98ABE1725EC6500B6E8B5 /* fsk.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A361725EC6400B6E8B5 /* fsk.h */; }; + A1B98ABF1725EC6500B6E8B5 /* g711.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A371725EC6400B6E8B5 /* g711.h */; }; + A1B98AC01725EC6500B6E8B5 /* g722.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A381725EC6400B6E8B5 /* g722.h */; }; + A1B98AC11725EC6500B6E8B5 /* g726.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A391725EC6400B6E8B5 /* g726.h */; }; + A1B98AC21725EC6500B6E8B5 /* gsm0610.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3A1725EC6400B6E8B5 /* gsm0610.h */; }; + A1B98AC31725EC6500B6E8B5 /* hdlc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3B1725EC6400B6E8B5 /* hdlc.h */; }; + A1B98AC41725EC6500B6E8B5 /* ima_adpcm.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3C1725EC6400B6E8B5 /* ima_adpcm.h */; }; + A1B98AC51725EC6500B6E8B5 /* logging.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3D1725EC6400B6E8B5 /* logging.h */; }; + A1B98AC61725EC6500B6E8B5 /* lpc10.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3E1725EC6400B6E8B5 /* lpc10.h */; }; + A1B98AC71725EC6500B6E8B5 /* modem_connect_tones.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A3F1725EC6400B6E8B5 /* modem_connect_tones.h */; }; + A1B98AC81725EC6500B6E8B5 /* modem_echo.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A401725EC6400B6E8B5 /* modem_echo.h */; }; + A1B98AC91725EC6500B6E8B5 /* noise.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A411725EC6400B6E8B5 /* noise.h */; }; + A1B98ACA1725EC6500B6E8B5 /* oki_adpcm.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A421725EC6400B6E8B5 /* oki_adpcm.h */; }; + A1B98ACB1725EC6500B6E8B5 /* queue.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A431725EC6400B6E8B5 /* queue.h */; }; + A1B98ACC1725EC6500B6E8B5 /* schedule.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A451725EC6400B6E8B5 /* schedule.h */; }; + A1B98ACD1725EC6500B6E8B5 /* sig_tone.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A461725EC6400B6E8B5 /* sig_tone.h */; }; + A1B98ACE1725EC6500B6E8B5 /* silence_gen.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A471725EC6400B6E8B5 /* silence_gen.h */; }; + A1B98ACF1725EC6500B6E8B5 /* super_tone_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A481725EC6400B6E8B5 /* super_tone_rx.h */; }; + A1B98AD01725EC6500B6E8B5 /* super_tone_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A491725EC6400B6E8B5 /* super_tone_tx.h */; }; + A1B98AD11725EC6500B6E8B5 /* swept_tone.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4A1725EC6400B6E8B5 /* swept_tone.h */; }; + A1B98AD21725EC6500B6E8B5 /* t30.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4B1725EC6400B6E8B5 /* t30.h */; }; + A1B98AD31725EC6500B6E8B5 /* t30_dis_dtc_dcs_bits.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4C1725EC6400B6E8B5 /* t30_dis_dtc_dcs_bits.h */; }; + A1B98AD41725EC6500B6E8B5 /* t31.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4D1725EC6400B6E8B5 /* t31.h */; }; + A1B98AD51725EC6500B6E8B5 /* t38_core.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4E1725EC6400B6E8B5 /* t38_core.h */; }; + A1B98AD61725EC6500B6E8B5 /* t38_gateway.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A4F1725EC6400B6E8B5 /* t38_gateway.h */; }; + A1B98AD71725EC6500B6E8B5 /* t38_non_ecm_buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A501725EC6400B6E8B5 /* t38_non_ecm_buffer.h */; }; + A1B98AD81725EC6500B6E8B5 /* t38_terminal.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A511725EC6400B6E8B5 /* t38_terminal.h */; }; + A1B98AD91725EC6500B6E8B5 /* t4_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A521725EC6400B6E8B5 /* t4_rx.h */; }; + A1B98ADA1725EC6500B6E8B5 /* t4_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A531725EC6400B6E8B5 /* t4_tx.h */; }; + A1B98ADB1725EC6500B6E8B5 /* time_scale.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A541725EC6400B6E8B5 /* time_scale.h */; }; + A1B98ADC1725EC6500B6E8B5 /* tone_detect.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A551725EC6400B6E8B5 /* tone_detect.h */; }; + A1B98ADD1725EC6500B6E8B5 /* tone_generate.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A561725EC6400B6E8B5 /* tone_generate.h */; }; + A1B98ADE1725EC6500B6E8B5 /* v17rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A571725EC6400B6E8B5 /* v17rx.h */; }; + A1B98ADF1725EC6500B6E8B5 /* v17tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A581725EC6400B6E8B5 /* v17tx.h */; }; + A1B98AE01725EC6500B6E8B5 /* v18.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A591725EC6400B6E8B5 /* v18.h */; }; + A1B98AE11725EC6500B6E8B5 /* v22bis.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5A1725EC6400B6E8B5 /* v22bis.h */; }; + A1B98AE21725EC6500B6E8B5 /* v27ter_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5B1725EC6400B6E8B5 /* v27ter_rx.h */; }; + A1B98AE31725EC6500B6E8B5 /* v27ter_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5C1725EC6400B6E8B5 /* v27ter_tx.h */; }; + A1B98AE41725EC6500B6E8B5 /* v29rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5D1725EC6400B6E8B5 /* v29rx.h */; }; + A1B98AE51725EC6500B6E8B5 /* v29tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5E1725EC6400B6E8B5 /* v29tx.h */; }; + A1B98AE61725EC6500B6E8B5 /* v42.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A5F1725EC6400B6E8B5 /* v42.h */; }; + A1B98AE71725EC6500B6E8B5 /* v42bis.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A601725EC6400B6E8B5 /* v42bis.h */; }; + A1B98AE81725EC6500B6E8B5 /* v8.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A611725EC6400B6E8B5 /* v8.h */; }; + A1B98AE91725EC6500B6E8B5 /* queue.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A621725EC6400B6E8B5 /* queue.h */; }; + A1B98AEA1725EC6500B6E8B5 /* saturated.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A631725EC6400B6E8B5 /* saturated.h */; }; + A1B98AEB1725EC6500B6E8B5 /* schedule.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A641725EC6400B6E8B5 /* schedule.h */; }; + A1B98AEC1725EC6500B6E8B5 /* sig_tone.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A651725EC6400B6E8B5 /* sig_tone.h */; }; + A1B98AED1725EC6500B6E8B5 /* silence_gen.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A661725EC6400B6E8B5 /* silence_gen.h */; }; + A1B98AEE1725EC6500B6E8B5 /* super_tone_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A671725EC6400B6E8B5 /* super_tone_rx.h */; }; + A1B98AEF1725EC6500B6E8B5 /* super_tone_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A681725EC6400B6E8B5 /* super_tone_tx.h */; }; + A1B98AF01725EC6500B6E8B5 /* swept_tone.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A691725EC6400B6E8B5 /* swept_tone.h */; }; + A1B98AF11725EC6500B6E8B5 /* t30.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6A1725EC6400B6E8B5 /* t30.h */; }; + A1B98AF21725EC6500B6E8B5 /* t30_api.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6B1725EC6400B6E8B5 /* t30_api.h */; }; + A1B98AF31725EC6500B6E8B5 /* t30_fcf.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6C1725EC6400B6E8B5 /* t30_fcf.h */; }; + A1B98AF41725EC6500B6E8B5 /* t30_logging.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6D1725EC6400B6E8B5 /* t30_logging.h */; }; + A1B98AF51725EC6500B6E8B5 /* t31.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6E1725EC6400B6E8B5 /* t31.h */; }; + A1B98AF61725EC6500B6E8B5 /* t35.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A6F1725EC6400B6E8B5 /* t35.h */; }; + A1B98AF71725EC6500B6E8B5 /* t38_core.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A701725EC6400B6E8B5 /* t38_core.h */; }; + A1B98AF81725EC6500B6E8B5 /* t38_gateway.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A711725EC6400B6E8B5 /* t38_gateway.h */; }; + A1B98AF91725EC6500B6E8B5 /* t38_non_ecm_buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A721725EC6400B6E8B5 /* t38_non_ecm_buffer.h */; }; + A1B98AFA1725EC6500B6E8B5 /* t38_terminal.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A731725EC6400B6E8B5 /* t38_terminal.h */; }; + A1B98AFB1725EC6500B6E8B5 /* t4_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A741725EC6400B6E8B5 /* t4_rx.h */; }; + A1B98AFC1725EC6500B6E8B5 /* t4_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A751725EC6400B6E8B5 /* t4_tx.h */; }; + A1B98AFD1725EC6500B6E8B5 /* telephony.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A761725EC6400B6E8B5 /* telephony.h */; }; + A1B98AFE1725EC6500B6E8B5 /* time_scale.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A771725EC6400B6E8B5 /* time_scale.h */; }; + A1B98AFF1725EC6500B6E8B5 /* timing.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A781725EC6400B6E8B5 /* timing.h */; }; + A1B98B001725EC6500B6E8B5 /* tone_detect.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A791725EC6400B6E8B5 /* tone_detect.h */; }; + A1B98B011725EC6500B6E8B5 /* tone_generate.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7A1725EC6400B6E8B5 /* tone_generate.h */; }; + A1B98B021725EC6500B6E8B5 /* v17rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7B1725EC6400B6E8B5 /* v17rx.h */; }; + A1B98B031725EC6500B6E8B5 /* v17tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7C1725EC6400B6E8B5 /* v17tx.h */; }; + A1B98B041725EC6500B6E8B5 /* v18.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7D1725EC6400B6E8B5 /* v18.h */; }; + A1B98B051725EC6500B6E8B5 /* v22bis.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7E1725EC6400B6E8B5 /* v22bis.h */; }; + A1B98B061725EC6500B6E8B5 /* v27ter_rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A7F1725EC6400B6E8B5 /* v27ter_rx.h */; }; + A1B98B071725EC6500B6E8B5 /* v27ter_tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A801725EC6400B6E8B5 /* v27ter_tx.h */; }; + A1B98B081725EC6500B6E8B5 /* v29rx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A811725EC6400B6E8B5 /* v29rx.h */; }; + A1B98B091725EC6500B6E8B5 /* v29tx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A821725EC6400B6E8B5 /* v29tx.h */; }; + A1B98B0A1725EC6500B6E8B5 /* v42.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A831725EC6400B6E8B5 /* v42.h */; }; + A1B98B0B1725EC6500B6E8B5 /* v42bis.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A841725EC6400B6E8B5 /* v42bis.h */; }; + A1B98B0C1725EC6500B6E8B5 /* v8.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A851725EC6400B6E8B5 /* v8.h */; }; + A1B98B0D1725EC6500B6E8B5 /* vector_float.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A861725EC6400B6E8B5 /* vector_float.h */; }; + A1B98B0E1725EC6500B6E8B5 /* vector_int.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A871725EC6400B6E8B5 /* vector_int.h */; }; + A1B98B0F1725EC6500B6E8B5 /* version.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B98A881725EC6400B6E8B5 /* version.h */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A1B989641725EC1300B6E8B5 /* libspandsp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libspandsp.a; sourceTree = BUILT_PRODUCTS_DIR; }; + A1B989FD1725EC3E00B6E8B5 /* time_scale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = time_scale.c; sourceTree = ""; }; + A1B98A011725EC6400B6E8B5 /* adsi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = adsi.h; sourceTree = ""; }; + A1B98A021725EC6400B6E8B5 /* arctan2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arctan2.h; sourceTree = ""; }; + A1B98A031725EC6400B6E8B5 /* async.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = async.h; sourceTree = ""; }; + A1B98A041725EC6400B6E8B5 /* at_interpreter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = at_interpreter.h; sourceTree = ""; }; + A1B98A051725EC6400B6E8B5 /* awgn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = awgn.h; sourceTree = ""; }; + A1B98A061725EC6400B6E8B5 /* bell_r2_mf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bell_r2_mf.h; sourceTree = ""; }; + A1B98A071725EC6400B6E8B5 /* bert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bert.h; sourceTree = ""; }; + A1B98A081725EC6400B6E8B5 /* biquad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = biquad.h; sourceTree = ""; }; + A1B98A091725EC6400B6E8B5 /* bit_operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bit_operations.h; sourceTree = ""; }; + A1B98A0A1725EC6400B6E8B5 /* bitstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitstream.h; sourceTree = ""; }; + A1B98A0B1725EC6400B6E8B5 /* complex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = complex.h; sourceTree = ""; }; + A1B98A0C1725EC6400B6E8B5 /* complex_filters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = complex_filters.h; sourceTree = ""; }; + A1B98A0D1725EC6400B6E8B5 /* complex_vector_float.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = complex_vector_float.h; sourceTree = ""; }; + A1B98A0E1725EC6400B6E8B5 /* complex_vector_int.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = complex_vector_int.h; sourceTree = ""; }; + A1B98A0F1725EC6400B6E8B5 /* crc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crc.h; sourceTree = ""; }; + A1B98A101725EC6400B6E8B5 /* dc_restore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dc_restore.h; sourceTree = ""; }; + A1B98A111725EC6400B6E8B5 /* dds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dds.h; sourceTree = ""; }; + A1B98A121725EC6400B6E8B5 /* dtmf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dtmf.h; sourceTree = ""; }; + A1B98A131725EC6400B6E8B5 /* echo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = echo.h; sourceTree = ""; }; + A1B98A141725EC6400B6E8B5 /* expose.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = expose.h; sourceTree = ""; }; + A1B98A151725EC6400B6E8B5 /* fast_convert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fast_convert.h; sourceTree = ""; }; + A1B98A161725EC6400B6E8B5 /* fax.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fax.h; sourceTree = ""; }; + A1B98A171725EC6400B6E8B5 /* fax_modems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fax_modems.h; sourceTree = ""; }; + A1B98A181725EC6400B6E8B5 /* fir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fir.h; sourceTree = ""; }; + A1B98A191725EC6400B6E8B5 /* fsk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fsk.h; sourceTree = ""; }; + A1B98A1A1725EC6400B6E8B5 /* g168models.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g168models.h; sourceTree = ""; }; + A1B98A1B1725EC6400B6E8B5 /* g711.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g711.h; sourceTree = ""; }; + A1B98A1C1725EC6400B6E8B5 /* g722.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g722.h; sourceTree = ""; }; + A1B98A1D1725EC6400B6E8B5 /* g726.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g726.h; sourceTree = ""; }; + A1B98A1E1725EC6400B6E8B5 /* gsm0610.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gsm0610.h; sourceTree = ""; }; + A1B98A1F1725EC6400B6E8B5 /* hdlc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hdlc.h; sourceTree = ""; }; + A1B98A201725EC6400B6E8B5 /* ima_adpcm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ima_adpcm.h; sourceTree = ""; }; + A1B98A211725EC6400B6E8B5 /* logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = logging.h; sourceTree = ""; }; + A1B98A221725EC6400B6E8B5 /* lpc10.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc10.h; sourceTree = ""; }; + A1B98A231725EC6400B6E8B5 /* modem_connect_tones.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem_connect_tones.h; sourceTree = ""; }; + A1B98A241725EC6400B6E8B5 /* modem_echo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem_echo.h; sourceTree = ""; }; + A1B98A251725EC6400B6E8B5 /* noise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = noise.h; sourceTree = ""; }; + A1B98A261725EC6400B6E8B5 /* oki_adpcm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = oki_adpcm.h; sourceTree = ""; }; + A1B98A271725EC6400B6E8B5 /* playout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = playout.h; sourceTree = ""; }; + A1B98A281725EC6400B6E8B5 /* plc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = plc.h; sourceTree = ""; }; + A1B98A291725EC6400B6E8B5 /* power_meter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = power_meter.h; sourceTree = ""; }; + A1B98A2B1725EC6400B6E8B5 /* adsi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = adsi.h; sourceTree = ""; }; + A1B98A2C1725EC6400B6E8B5 /* async.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = async.h; sourceTree = ""; }; + A1B98A2D1725EC6400B6E8B5 /* at_interpreter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = at_interpreter.h; sourceTree = ""; }; + A1B98A2E1725EC6400B6E8B5 /* awgn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = awgn.h; sourceTree = ""; }; + A1B98A2F1725EC6400B6E8B5 /* bell_r2_mf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bell_r2_mf.h; sourceTree = ""; }; + A1B98A301725EC6400B6E8B5 /* bert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bert.h; sourceTree = ""; }; + A1B98A311725EC6400B6E8B5 /* bitstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitstream.h; sourceTree = ""; }; + A1B98A321725EC6400B6E8B5 /* dtmf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dtmf.h; sourceTree = ""; }; + A1B98A331725EC6400B6E8B5 /* echo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = echo.h; sourceTree = ""; }; + A1B98A341725EC6400B6E8B5 /* fax.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fax.h; sourceTree = ""; }; + A1B98A351725EC6400B6E8B5 /* fax_modems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fax_modems.h; sourceTree = ""; }; + A1B98A361725EC6400B6E8B5 /* fsk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fsk.h; sourceTree = ""; }; + A1B98A371725EC6400B6E8B5 /* g711.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g711.h; sourceTree = ""; }; + A1B98A381725EC6400B6E8B5 /* g722.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g722.h; sourceTree = ""; }; + A1B98A391725EC6400B6E8B5 /* g726.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = g726.h; sourceTree = ""; }; + A1B98A3A1725EC6400B6E8B5 /* gsm0610.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gsm0610.h; sourceTree = ""; }; + A1B98A3B1725EC6400B6E8B5 /* hdlc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hdlc.h; sourceTree = ""; }; + A1B98A3C1725EC6400B6E8B5 /* ima_adpcm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ima_adpcm.h; sourceTree = ""; }; + A1B98A3D1725EC6400B6E8B5 /* logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = logging.h; sourceTree = ""; }; + A1B98A3E1725EC6400B6E8B5 /* lpc10.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc10.h; sourceTree = ""; }; + A1B98A3F1725EC6400B6E8B5 /* modem_connect_tones.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem_connect_tones.h; sourceTree = ""; }; + A1B98A401725EC6400B6E8B5 /* modem_echo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem_echo.h; sourceTree = ""; }; + A1B98A411725EC6400B6E8B5 /* noise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = noise.h; sourceTree = ""; }; + A1B98A421725EC6400B6E8B5 /* oki_adpcm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = oki_adpcm.h; sourceTree = ""; }; + A1B98A431725EC6400B6E8B5 /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = ""; }; + A1B98A441725EC6400B6E8B5 /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; + A1B98A451725EC6400B6E8B5 /* schedule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = schedule.h; sourceTree = ""; }; + A1B98A461725EC6400B6E8B5 /* sig_tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sig_tone.h; sourceTree = ""; }; + A1B98A471725EC6400B6E8B5 /* silence_gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = silence_gen.h; sourceTree = ""; }; + A1B98A481725EC6400B6E8B5 /* super_tone_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = super_tone_rx.h; sourceTree = ""; }; + A1B98A491725EC6400B6E8B5 /* super_tone_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = super_tone_tx.h; sourceTree = ""; }; + A1B98A4A1725EC6400B6E8B5 /* swept_tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = swept_tone.h; sourceTree = ""; }; + A1B98A4B1725EC6400B6E8B5 /* t30.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30.h; sourceTree = ""; }; + A1B98A4C1725EC6400B6E8B5 /* t30_dis_dtc_dcs_bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30_dis_dtc_dcs_bits.h; sourceTree = ""; }; + A1B98A4D1725EC6400B6E8B5 /* t31.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t31.h; sourceTree = ""; }; + A1B98A4E1725EC6400B6E8B5 /* t38_core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_core.h; sourceTree = ""; }; + A1B98A4F1725EC6400B6E8B5 /* t38_gateway.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_gateway.h; sourceTree = ""; }; + A1B98A501725EC6400B6E8B5 /* t38_non_ecm_buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_non_ecm_buffer.h; sourceTree = ""; }; + A1B98A511725EC6400B6E8B5 /* t38_terminal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_terminal.h; sourceTree = ""; }; + A1B98A521725EC6400B6E8B5 /* t4_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t4_rx.h; sourceTree = ""; }; + A1B98A531725EC6400B6E8B5 /* t4_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t4_tx.h; sourceTree = ""; }; + A1B98A541725EC6400B6E8B5 /* time_scale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time_scale.h; sourceTree = ""; }; + A1B98A551725EC6400B6E8B5 /* tone_detect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tone_detect.h; sourceTree = ""; }; + A1B98A561725EC6400B6E8B5 /* tone_generate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tone_generate.h; sourceTree = ""; }; + A1B98A571725EC6400B6E8B5 /* v17rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v17rx.h; sourceTree = ""; }; + A1B98A581725EC6400B6E8B5 /* v17tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v17tx.h; sourceTree = ""; }; + A1B98A591725EC6400B6E8B5 /* v18.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v18.h; sourceTree = ""; }; + A1B98A5A1725EC6400B6E8B5 /* v22bis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v22bis.h; sourceTree = ""; }; + A1B98A5B1725EC6400B6E8B5 /* v27ter_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v27ter_rx.h; sourceTree = ""; }; + A1B98A5C1725EC6400B6E8B5 /* v27ter_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v27ter_tx.h; sourceTree = ""; }; + A1B98A5D1725EC6400B6E8B5 /* v29rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v29rx.h; sourceTree = ""; }; + A1B98A5E1725EC6400B6E8B5 /* v29tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v29tx.h; sourceTree = ""; }; + A1B98A5F1725EC6400B6E8B5 /* v42.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v42.h; sourceTree = ""; }; + A1B98A601725EC6400B6E8B5 /* v42bis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v42bis.h; sourceTree = ""; }; + A1B98A611725EC6400B6E8B5 /* v8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v8.h; sourceTree = ""; }; + A1B98A621725EC6400B6E8B5 /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = ""; }; + A1B98A631725EC6400B6E8B5 /* saturated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = saturated.h; sourceTree = ""; }; + A1B98A641725EC6400B6E8B5 /* schedule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = schedule.h; sourceTree = ""; }; + A1B98A651725EC6400B6E8B5 /* sig_tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sig_tone.h; sourceTree = ""; }; + A1B98A661725EC6400B6E8B5 /* silence_gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = silence_gen.h; sourceTree = ""; }; + A1B98A671725EC6400B6E8B5 /* super_tone_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = super_tone_rx.h; sourceTree = ""; }; + A1B98A681725EC6400B6E8B5 /* super_tone_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = super_tone_tx.h; sourceTree = ""; }; + A1B98A691725EC6400B6E8B5 /* swept_tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = swept_tone.h; sourceTree = ""; }; + A1B98A6A1725EC6400B6E8B5 /* t30.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30.h; sourceTree = ""; }; + A1B98A6B1725EC6400B6E8B5 /* t30_api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30_api.h; sourceTree = ""; }; + A1B98A6C1725EC6400B6E8B5 /* t30_fcf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30_fcf.h; sourceTree = ""; }; + A1B98A6D1725EC6400B6E8B5 /* t30_logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t30_logging.h; sourceTree = ""; }; + A1B98A6E1725EC6400B6E8B5 /* t31.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t31.h; sourceTree = ""; }; + A1B98A6F1725EC6400B6E8B5 /* t35.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t35.h; sourceTree = ""; }; + A1B98A701725EC6400B6E8B5 /* t38_core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_core.h; sourceTree = ""; }; + A1B98A711725EC6400B6E8B5 /* t38_gateway.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_gateway.h; sourceTree = ""; }; + A1B98A721725EC6400B6E8B5 /* t38_non_ecm_buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_non_ecm_buffer.h; sourceTree = ""; }; + A1B98A731725EC6400B6E8B5 /* t38_terminal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t38_terminal.h; sourceTree = ""; }; + A1B98A741725EC6400B6E8B5 /* t4_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t4_rx.h; sourceTree = ""; }; + A1B98A751725EC6400B6E8B5 /* t4_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = t4_tx.h; sourceTree = ""; }; + A1B98A761725EC6400B6E8B5 /* telephony.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = telephony.h; sourceTree = ""; }; + A1B98A771725EC6400B6E8B5 /* time_scale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time_scale.h; sourceTree = ""; }; + A1B98A781725EC6400B6E8B5 /* timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timing.h; sourceTree = ""; }; + A1B98A791725EC6400B6E8B5 /* tone_detect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tone_detect.h; sourceTree = ""; }; + A1B98A7A1725EC6400B6E8B5 /* tone_generate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tone_generate.h; sourceTree = ""; }; + A1B98A7B1725EC6400B6E8B5 /* v17rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v17rx.h; sourceTree = ""; }; + A1B98A7C1725EC6400B6E8B5 /* v17tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v17tx.h; sourceTree = ""; }; + A1B98A7D1725EC6400B6E8B5 /* v18.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v18.h; sourceTree = ""; }; + A1B98A7E1725EC6400B6E8B5 /* v22bis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v22bis.h; sourceTree = ""; }; + A1B98A7F1725EC6400B6E8B5 /* v27ter_rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v27ter_rx.h; sourceTree = ""; }; + A1B98A801725EC6400B6E8B5 /* v27ter_tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v27ter_tx.h; sourceTree = ""; }; + A1B98A811725EC6400B6E8B5 /* v29rx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v29rx.h; sourceTree = ""; }; + A1B98A821725EC6400B6E8B5 /* v29tx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v29tx.h; sourceTree = ""; }; + A1B98A831725EC6400B6E8B5 /* v42.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v42.h; sourceTree = ""; }; + A1B98A841725EC6400B6E8B5 /* v42bis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v42bis.h; sourceTree = ""; }; + A1B98A851725EC6400B6E8B5 /* v8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v8.h; sourceTree = ""; }; + A1B98A861725EC6400B6E8B5 /* vector_float.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector_float.h; sourceTree = ""; }; + A1B98A871725EC6400B6E8B5 /* vector_int.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector_int.h; sourceTree = ""; }; + A1B98A881725EC6400B6E8B5 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + A1B98A891725EC6400B6E8B5 /* version.h.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = version.h.in; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A1B989611725EC1300B6E8B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A1B9895B1725EC1300B6E8B5 = { + isa = PBXGroup; + children = ( + A1B989711725EC1A00B6E8B5 /* src */, + A1B989651725EC1300B6E8B5 /* Products */, + ); + sourceTree = ""; + }; + A1B989651725EC1300B6E8B5 /* Products */ = { + isa = PBXGroup; + children = ( + A1B989641725EC1300B6E8B5 /* libspandsp.a */, + ); + name = Products; + sourceTree = ""; + }; + A1B989711725EC1A00B6E8B5 /* src */ = { + isa = PBXGroup; + children = ( + A1B989FD1725EC3E00B6E8B5 /* time_scale.c */, + A1B98A001725EC6400B6E8B5 /* spandsp */, + ); + name = src; + sourceTree = ""; + }; + A1B98A001725EC6400B6E8B5 /* spandsp */ = { + isa = PBXGroup; + children = ( + A1B98A011725EC6400B6E8B5 /* adsi.h */, + A1B98A021725EC6400B6E8B5 /* arctan2.h */, + A1B98A031725EC6400B6E8B5 /* async.h */, + A1B98A041725EC6400B6E8B5 /* at_interpreter.h */, + A1B98A051725EC6400B6E8B5 /* awgn.h */, + A1B98A061725EC6400B6E8B5 /* bell_r2_mf.h */, + A1B98A071725EC6400B6E8B5 /* bert.h */, + A1B98A081725EC6400B6E8B5 /* biquad.h */, + A1B98A091725EC6400B6E8B5 /* bit_operations.h */, + A1B98A0A1725EC6400B6E8B5 /* bitstream.h */, + A1B98A0B1725EC6400B6E8B5 /* complex.h */, + A1B98A0C1725EC6400B6E8B5 /* complex_filters.h */, + A1B98A0D1725EC6400B6E8B5 /* complex_vector_float.h */, + A1B98A0E1725EC6400B6E8B5 /* complex_vector_int.h */, + A1B98A0F1725EC6400B6E8B5 /* crc.h */, + A1B98A101725EC6400B6E8B5 /* dc_restore.h */, + A1B98A111725EC6400B6E8B5 /* dds.h */, + A1B98A121725EC6400B6E8B5 /* dtmf.h */, + A1B98A131725EC6400B6E8B5 /* echo.h */, + A1B98A141725EC6400B6E8B5 /* expose.h */, + A1B98A151725EC6400B6E8B5 /* fast_convert.h */, + A1B98A161725EC6400B6E8B5 /* fax.h */, + A1B98A171725EC6400B6E8B5 /* fax_modems.h */, + A1B98A181725EC6400B6E8B5 /* fir.h */, + A1B98A191725EC6400B6E8B5 /* fsk.h */, + A1B98A1A1725EC6400B6E8B5 /* g168models.h */, + A1B98A1B1725EC6400B6E8B5 /* g711.h */, + A1B98A1C1725EC6400B6E8B5 /* g722.h */, + A1B98A1D1725EC6400B6E8B5 /* g726.h */, + A1B98A1E1725EC6400B6E8B5 /* gsm0610.h */, + A1B98A1F1725EC6400B6E8B5 /* hdlc.h */, + A1B98A201725EC6400B6E8B5 /* ima_adpcm.h */, + A1B98A211725EC6400B6E8B5 /* logging.h */, + A1B98A221725EC6400B6E8B5 /* lpc10.h */, + A1B98A231725EC6400B6E8B5 /* modem_connect_tones.h */, + A1B98A241725EC6400B6E8B5 /* modem_echo.h */, + A1B98A251725EC6400B6E8B5 /* noise.h */, + A1B98A261725EC6400B6E8B5 /* oki_adpcm.h */, + A1B98A271725EC6400B6E8B5 /* playout.h */, + A1B98A281725EC6400B6E8B5 /* plc.h */, + A1B98A291725EC6400B6E8B5 /* power_meter.h */, + A1B98A2A1725EC6400B6E8B5 /* private */, + A1B98A621725EC6400B6E8B5 /* queue.h */, + A1B98A631725EC6400B6E8B5 /* saturated.h */, + A1B98A641725EC6400B6E8B5 /* schedule.h */, + A1B98A651725EC6400B6E8B5 /* sig_tone.h */, + A1B98A661725EC6400B6E8B5 /* silence_gen.h */, + A1B98A671725EC6400B6E8B5 /* super_tone_rx.h */, + A1B98A681725EC6400B6E8B5 /* super_tone_tx.h */, + A1B98A691725EC6400B6E8B5 /* swept_tone.h */, + A1B98A6A1725EC6400B6E8B5 /* t30.h */, + A1B98A6B1725EC6400B6E8B5 /* t30_api.h */, + A1B98A6C1725EC6400B6E8B5 /* t30_fcf.h */, + A1B98A6D1725EC6400B6E8B5 /* t30_logging.h */, + A1B98A6E1725EC6400B6E8B5 /* t31.h */, + A1B98A6F1725EC6400B6E8B5 /* t35.h */, + A1B98A701725EC6400B6E8B5 /* t38_core.h */, + A1B98A711725EC6400B6E8B5 /* t38_gateway.h */, + A1B98A721725EC6400B6E8B5 /* t38_non_ecm_buffer.h */, + A1B98A731725EC6400B6E8B5 /* t38_terminal.h */, + A1B98A741725EC6400B6E8B5 /* t4_rx.h */, + A1B98A751725EC6400B6E8B5 /* t4_tx.h */, + A1B98A761725EC6400B6E8B5 /* telephony.h */, + A1B98A771725EC6400B6E8B5 /* time_scale.h */, + A1B98A781725EC6400B6E8B5 /* timing.h */, + A1B98A791725EC6400B6E8B5 /* tone_detect.h */, + A1B98A7A1725EC6400B6E8B5 /* tone_generate.h */, + A1B98A7B1725EC6400B6E8B5 /* v17rx.h */, + A1B98A7C1725EC6400B6E8B5 /* v17tx.h */, + A1B98A7D1725EC6400B6E8B5 /* v18.h */, + A1B98A7E1725EC6400B6E8B5 /* v22bis.h */, + A1B98A7F1725EC6400B6E8B5 /* v27ter_rx.h */, + A1B98A801725EC6400B6E8B5 /* v27ter_tx.h */, + A1B98A811725EC6400B6E8B5 /* v29rx.h */, + A1B98A821725EC6400B6E8B5 /* v29tx.h */, + A1B98A831725EC6400B6E8B5 /* v42.h */, + A1B98A841725EC6400B6E8B5 /* v42bis.h */, + A1B98A851725EC6400B6E8B5 /* v8.h */, + A1B98A861725EC6400B6E8B5 /* vector_float.h */, + A1B98A871725EC6400B6E8B5 /* vector_int.h */, + A1B98A881725EC6400B6E8B5 /* version.h */, + A1B98A891725EC6400B6E8B5 /* version.h.in */, + ); + path = spandsp; + sourceTree = ""; + }; + A1B98A2A1725EC6400B6E8B5 /* private */ = { + isa = PBXGroup; + children = ( + A1B98A2B1725EC6400B6E8B5 /* adsi.h */, + A1B98A2C1725EC6400B6E8B5 /* async.h */, + A1B98A2D1725EC6400B6E8B5 /* at_interpreter.h */, + A1B98A2E1725EC6400B6E8B5 /* awgn.h */, + A1B98A2F1725EC6400B6E8B5 /* bell_r2_mf.h */, + A1B98A301725EC6400B6E8B5 /* bert.h */, + A1B98A311725EC6400B6E8B5 /* bitstream.h */, + A1B98A321725EC6400B6E8B5 /* dtmf.h */, + A1B98A331725EC6400B6E8B5 /* echo.h */, + A1B98A341725EC6400B6E8B5 /* fax.h */, + A1B98A351725EC6400B6E8B5 /* fax_modems.h */, + A1B98A361725EC6400B6E8B5 /* fsk.h */, + A1B98A371725EC6400B6E8B5 /* g711.h */, + A1B98A381725EC6400B6E8B5 /* g722.h */, + A1B98A391725EC6400B6E8B5 /* g726.h */, + A1B98A3A1725EC6400B6E8B5 /* gsm0610.h */, + A1B98A3B1725EC6400B6E8B5 /* hdlc.h */, + A1B98A3C1725EC6400B6E8B5 /* ima_adpcm.h */, + A1B98A3D1725EC6400B6E8B5 /* logging.h */, + A1B98A3E1725EC6400B6E8B5 /* lpc10.h */, + A1B98A3F1725EC6400B6E8B5 /* modem_connect_tones.h */, + A1B98A401725EC6400B6E8B5 /* modem_echo.h */, + A1B98A411725EC6400B6E8B5 /* noise.h */, + A1B98A421725EC6400B6E8B5 /* oki_adpcm.h */, + A1B98A431725EC6400B6E8B5 /* queue.h */, + A1B98A441725EC6400B6E8B5 /* README */, + A1B98A451725EC6400B6E8B5 /* schedule.h */, + A1B98A461725EC6400B6E8B5 /* sig_tone.h */, + A1B98A471725EC6400B6E8B5 /* silence_gen.h */, + A1B98A481725EC6400B6E8B5 /* super_tone_rx.h */, + A1B98A491725EC6400B6E8B5 /* super_tone_tx.h */, + A1B98A4A1725EC6400B6E8B5 /* swept_tone.h */, + A1B98A4B1725EC6400B6E8B5 /* t30.h */, + A1B98A4C1725EC6400B6E8B5 /* t30_dis_dtc_dcs_bits.h */, + A1B98A4D1725EC6400B6E8B5 /* t31.h */, + A1B98A4E1725EC6400B6E8B5 /* t38_core.h */, + A1B98A4F1725EC6400B6E8B5 /* t38_gateway.h */, + A1B98A501725EC6400B6E8B5 /* t38_non_ecm_buffer.h */, + A1B98A511725EC6400B6E8B5 /* t38_terminal.h */, + A1B98A521725EC6400B6E8B5 /* t4_rx.h */, + A1B98A531725EC6400B6E8B5 /* t4_tx.h */, + A1B98A541725EC6400B6E8B5 /* time_scale.h */, + A1B98A551725EC6400B6E8B5 /* tone_detect.h */, + A1B98A561725EC6400B6E8B5 /* tone_generate.h */, + A1B98A571725EC6400B6E8B5 /* v17rx.h */, + A1B98A581725EC6400B6E8B5 /* v17tx.h */, + A1B98A591725EC6400B6E8B5 /* v18.h */, + A1B98A5A1725EC6400B6E8B5 /* v22bis.h */, + A1B98A5B1725EC6400B6E8B5 /* v27ter_rx.h */, + A1B98A5C1725EC6400B6E8B5 /* v27ter_tx.h */, + A1B98A5D1725EC6400B6E8B5 /* v29rx.h */, + A1B98A5E1725EC6400B6E8B5 /* v29tx.h */, + A1B98A5F1725EC6400B6E8B5 /* v42.h */, + A1B98A601725EC6400B6E8B5 /* v42bis.h */, + A1B98A611725EC6400B6E8B5 /* v8.h */, + ); + path = private; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A1B989621725EC1300B6E8B5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A1B98A8A1725EC6400B6E8B5 /* adsi.h in Headers */, + A1B98A8B1725EC6400B6E8B5 /* arctan2.h in Headers */, + A1B98A8C1725EC6400B6E8B5 /* async.h in Headers */, + A1B98A8D1725EC6400B6E8B5 /* at_interpreter.h in Headers */, + A1B98A8E1725EC6400B6E8B5 /* awgn.h in Headers */, + A1B98A8F1725EC6400B6E8B5 /* bell_r2_mf.h in Headers */, + A1B98A901725EC6400B6E8B5 /* bert.h in Headers */, + A1B98A911725EC6400B6E8B5 /* biquad.h in Headers */, + A1B98A921725EC6400B6E8B5 /* bit_operations.h in Headers */, + A1B98A931725EC6400B6E8B5 /* bitstream.h in Headers */, + A1B98A941725EC6400B6E8B5 /* complex.h in Headers */, + A1B98A951725EC6400B6E8B5 /* complex_filters.h in Headers */, + A1B98A961725EC6400B6E8B5 /* complex_vector_float.h in Headers */, + A1B98A971725EC6400B6E8B5 /* complex_vector_int.h in Headers */, + A1B98A981725EC6400B6E8B5 /* crc.h in Headers */, + A1B98A991725EC6400B6E8B5 /* dc_restore.h in Headers */, + A1B98A9A1725EC6400B6E8B5 /* dds.h in Headers */, + A1B98A9B1725EC6400B6E8B5 /* dtmf.h in Headers */, + A1B98A9C1725EC6400B6E8B5 /* echo.h in Headers */, + A1B98A9D1725EC6400B6E8B5 /* expose.h in Headers */, + A1B98A9E1725EC6400B6E8B5 /* fast_convert.h in Headers */, + A1B98A9F1725EC6400B6E8B5 /* fax.h in Headers */, + A1B98AA01725EC6400B6E8B5 /* fax_modems.h in Headers */, + A1B98AA11725EC6400B6E8B5 /* fir.h in Headers */, + A1B98AA21725EC6400B6E8B5 /* fsk.h in Headers */, + A1B98AA31725EC6400B6E8B5 /* g168models.h in Headers */, + A1B98AA41725EC6400B6E8B5 /* g711.h in Headers */, + A1B98AA51725EC6400B6E8B5 /* g722.h in Headers */, + A1B98AA61725EC6400B6E8B5 /* g726.h in Headers */, + A1B98AA71725EC6400B6E8B5 /* gsm0610.h in Headers */, + A1B98AA81725EC6400B6E8B5 /* hdlc.h in Headers */, + A1B98AA91725EC6400B6E8B5 /* ima_adpcm.h in Headers */, + A1B98AAA1725EC6400B6E8B5 /* logging.h in Headers */, + A1B98AAB1725EC6400B6E8B5 /* lpc10.h in Headers */, + A1B98AAC1725EC6400B6E8B5 /* modem_connect_tones.h in Headers */, + A1B98AAD1725EC6400B6E8B5 /* modem_echo.h in Headers */, + A1B98AAE1725EC6400B6E8B5 /* noise.h in Headers */, + A1B98AAF1725EC6400B6E8B5 /* oki_adpcm.h in Headers */, + A1B98AB01725EC6400B6E8B5 /* playout.h in Headers */, + A1B98AB11725EC6400B6E8B5 /* plc.h in Headers */, + A1B98AB21725EC6400B6E8B5 /* power_meter.h in Headers */, + A1B98AB31725EC6400B6E8B5 /* adsi.h in Headers */, + A1B98AB41725EC6400B6E8B5 /* async.h in Headers */, + A1B98AB51725EC6400B6E8B5 /* at_interpreter.h in Headers */, + A1B98AB61725EC6400B6E8B5 /* awgn.h in Headers */, + A1B98AB71725EC6500B6E8B5 /* bell_r2_mf.h in Headers */, + A1B98AB81725EC6500B6E8B5 /* bert.h in Headers */, + A1B98AB91725EC6500B6E8B5 /* bitstream.h in Headers */, + A1B98ABA1725EC6500B6E8B5 /* dtmf.h in Headers */, + A1B98ABB1725EC6500B6E8B5 /* echo.h in Headers */, + A1B98ABC1725EC6500B6E8B5 /* fax.h in Headers */, + A1B98ABD1725EC6500B6E8B5 /* fax_modems.h in Headers */, + A1B98ABE1725EC6500B6E8B5 /* fsk.h in Headers */, + A1B98ABF1725EC6500B6E8B5 /* g711.h in Headers */, + A1B98AC01725EC6500B6E8B5 /* g722.h in Headers */, + A1B98AC11725EC6500B6E8B5 /* g726.h in Headers */, + A1B98AC21725EC6500B6E8B5 /* gsm0610.h in Headers */, + A1B98AC31725EC6500B6E8B5 /* hdlc.h in Headers */, + A1B98AC41725EC6500B6E8B5 /* ima_adpcm.h in Headers */, + A1B98AC51725EC6500B6E8B5 /* logging.h in Headers */, + A1B98AC61725EC6500B6E8B5 /* lpc10.h in Headers */, + A1B98AC71725EC6500B6E8B5 /* modem_connect_tones.h in Headers */, + A1B98AC81725EC6500B6E8B5 /* modem_echo.h in Headers */, + A1B98AC91725EC6500B6E8B5 /* noise.h in Headers */, + A1B98ACA1725EC6500B6E8B5 /* oki_adpcm.h in Headers */, + A1B98ACB1725EC6500B6E8B5 /* queue.h in Headers */, + A1B98ACC1725EC6500B6E8B5 /* schedule.h in Headers */, + A1B98ACD1725EC6500B6E8B5 /* sig_tone.h in Headers */, + A1B98ACE1725EC6500B6E8B5 /* silence_gen.h in Headers */, + A1B98ACF1725EC6500B6E8B5 /* super_tone_rx.h in Headers */, + A1B98AD01725EC6500B6E8B5 /* super_tone_tx.h in Headers */, + A1B98AD11725EC6500B6E8B5 /* swept_tone.h in Headers */, + A1B98AD21725EC6500B6E8B5 /* t30.h in Headers */, + A1B98AD31725EC6500B6E8B5 /* t30_dis_dtc_dcs_bits.h in Headers */, + A1B98AD41725EC6500B6E8B5 /* t31.h in Headers */, + A1B98AD51725EC6500B6E8B5 /* t38_core.h in Headers */, + A1B98AD61725EC6500B6E8B5 /* t38_gateway.h in Headers */, + A1B98AD71725EC6500B6E8B5 /* t38_non_ecm_buffer.h in Headers */, + A1B98AD81725EC6500B6E8B5 /* t38_terminal.h in Headers */, + A1B98AD91725EC6500B6E8B5 /* t4_rx.h in Headers */, + A1B98ADA1725EC6500B6E8B5 /* t4_tx.h in Headers */, + A1B98ADB1725EC6500B6E8B5 /* time_scale.h in Headers */, + A1B98ADC1725EC6500B6E8B5 /* tone_detect.h in Headers */, + A1B98ADD1725EC6500B6E8B5 /* tone_generate.h in Headers */, + A1B98ADE1725EC6500B6E8B5 /* v17rx.h in Headers */, + A1B98ADF1725EC6500B6E8B5 /* v17tx.h in Headers */, + A1B98AE01725EC6500B6E8B5 /* v18.h in Headers */, + A1B98AE11725EC6500B6E8B5 /* v22bis.h in Headers */, + A1B98AE21725EC6500B6E8B5 /* v27ter_rx.h in Headers */, + A1B98AE31725EC6500B6E8B5 /* v27ter_tx.h in Headers */, + A1B98AE41725EC6500B6E8B5 /* v29rx.h in Headers */, + A1B98AE51725EC6500B6E8B5 /* v29tx.h in Headers */, + A1B98AE61725EC6500B6E8B5 /* v42.h in Headers */, + A1B98AE71725EC6500B6E8B5 /* v42bis.h in Headers */, + A1B98AE81725EC6500B6E8B5 /* v8.h in Headers */, + A1B98AE91725EC6500B6E8B5 /* queue.h in Headers */, + A1B98AEA1725EC6500B6E8B5 /* saturated.h in Headers */, + A1B98AEB1725EC6500B6E8B5 /* schedule.h in Headers */, + A1B98AEC1725EC6500B6E8B5 /* sig_tone.h in Headers */, + A1B98AED1725EC6500B6E8B5 /* silence_gen.h in Headers */, + A1B98AEE1725EC6500B6E8B5 /* super_tone_rx.h in Headers */, + A1B98AEF1725EC6500B6E8B5 /* super_tone_tx.h in Headers */, + A1B98AF01725EC6500B6E8B5 /* swept_tone.h in Headers */, + A1B98AF11725EC6500B6E8B5 /* t30.h in Headers */, + A1B98AF21725EC6500B6E8B5 /* t30_api.h in Headers */, + A1B98AF31725EC6500B6E8B5 /* t30_fcf.h in Headers */, + A1B98AF41725EC6500B6E8B5 /* t30_logging.h in Headers */, + A1B98AF51725EC6500B6E8B5 /* t31.h in Headers */, + A1B98AF61725EC6500B6E8B5 /* t35.h in Headers */, + A1B98AF71725EC6500B6E8B5 /* t38_core.h in Headers */, + A1B98AF81725EC6500B6E8B5 /* t38_gateway.h in Headers */, + A1B98AF91725EC6500B6E8B5 /* t38_non_ecm_buffer.h in Headers */, + A1B98AFA1725EC6500B6E8B5 /* t38_terminal.h in Headers */, + A1B98AFB1725EC6500B6E8B5 /* t4_rx.h in Headers */, + A1B98AFC1725EC6500B6E8B5 /* t4_tx.h in Headers */, + A1B98AFD1725EC6500B6E8B5 /* telephony.h in Headers */, + A1B98AFE1725EC6500B6E8B5 /* time_scale.h in Headers */, + A1B98AFF1725EC6500B6E8B5 /* timing.h in Headers */, + A1B98B001725EC6500B6E8B5 /* tone_detect.h in Headers */, + A1B98B011725EC6500B6E8B5 /* tone_generate.h in Headers */, + A1B98B021725EC6500B6E8B5 /* v17rx.h in Headers */, + A1B98B031725EC6500B6E8B5 /* v17tx.h in Headers */, + A1B98B041725EC6500B6E8B5 /* v18.h in Headers */, + A1B98B051725EC6500B6E8B5 /* v22bis.h in Headers */, + A1B98B061725EC6500B6E8B5 /* v27ter_rx.h in Headers */, + A1B98B071725EC6500B6E8B5 /* v27ter_tx.h in Headers */, + A1B98B081725EC6500B6E8B5 /* v29rx.h in Headers */, + A1B98B091725EC6500B6E8B5 /* v29tx.h in Headers */, + A1B98B0A1725EC6500B6E8B5 /* v42.h in Headers */, + A1B98B0B1725EC6500B6E8B5 /* v42bis.h in Headers */, + A1B98B0C1725EC6500B6E8B5 /* v8.h in Headers */, + A1B98B0D1725EC6500B6E8B5 /* vector_float.h in Headers */, + A1B98B0E1725EC6500B6E8B5 /* vector_int.h in Headers */, + A1B98B0F1725EC6500B6E8B5 /* version.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A1B989631725EC1300B6E8B5 /* spandsp */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1B989681725EC1300B6E8B5 /* Build configuration list for PBXNativeTarget "spandsp" */; + buildPhases = ( + A1B989601725EC1300B6E8B5 /* Sources */, + A1B989611725EC1300B6E8B5 /* Frameworks */, + A1B989621725EC1300B6E8B5 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = spandsp; + productName = spandsp; + productReference = A1B989641725EC1300B6E8B5 /* libspandsp.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A1B9895C1725EC1300B6E8B5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Twisted Oak Studios"; + }; + buildConfigurationList = A1B9895F1725EC1300B6E8B5 /* Build configuration list for PBXProject "spandsp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A1B9895B1725EC1300B6E8B5; + productRefGroup = A1B989651725EC1300B6E8B5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A1B989631725EC1300B6E8B5 /* spandsp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + A1B989601725EC1300B6E8B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1B989FE1725EC3E00B6E8B5 /* time_scale.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A1B989661725EC1300B6E8B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + A1B989671725EC1300B6E8B5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = iphoneos; + }; + name = Release; + }; + A1B989691725EC1300B6E8B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + A1B9896A1725EC1300B6E8B5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A1B9895F1725EC1300B6E8B5 /* Build configuration list for PBXProject "spandsp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1B989661725EC1300B6E8B5 /* Debug */, + A1B989671725EC1300B6E8B5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A1B989681725EC1300B6E8B5 /* Build configuration list for PBXNativeTarget "spandsp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1B989691725EC1300B6E8B5 /* Debug */, + A1B9896A1725EC1300B6E8B5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A1B9895C1725EC1300B6E8B5 /* Project object */; +} diff --git a/Libraries/spandsp/spandsp/spandsp/adsi.h b/Libraries/spandsp/spandsp/spandsp/adsi.h new file mode 100644 index 000000000..d8c829435 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/adsi.h @@ -0,0 +1,516 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * adsi.h - Analogue display services interface and other call ID related handling. + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: adsi.h,v 1.40 2009/05/22 16:39:01 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_ADSI_H_) +#define _SPANDSP_ADSI_H_ + +/*! \page adsi_page ADSI transmission and reception +\section adsi_page_sec_1 What does it do? +Although ADSI has a specific meaning in some places, the term is used here to indicate +any form of Analogue Display Service Interface, which includes caller ID, SMS, and others. + +The ADSI module provides for the transmission and reception of ADSI messages +in various formats. Currently, the supported formats are: + + - Bellcore/Telcordia GR-30 CORE CLASS (Custom Local Area Signaling Services) standard + (North America, Australia, China, Taiwan, and Hong Kong). + + - ETSI ETS 300 648, ETS 300 659-1 CLIP (Calling Line Identity Presentation) FSK standard + (France, Germany, Norway, Italy, Spain, South Africa, Turkey, and the UK). + + - ETSI Caller-ID support for the UK, British Telecom SIN227 and SIN242. + + - ETSI ETS 300 648, ETS 300 659-1 CLIP (Calling Line Identity Presentation) DTMF standard + variant 1 (Belgium, Brazil, Denmark, Finland, Iceland, India, Netherlands, Saudi Arabia, + Sweden and Uruguay). + + - ETSI ETS 300 648, ETS 300 659-1 CLIP (Calling Line Identity Presentation) DTMF standard + variant 2 (Denmark and Holland). + + - ETSI ETS 300 648, ETS 300 659-1 CLIP (Calling Line Identity Presentation) DTMF standard + variant 3. + + - ETSI ETS 300 648, ETS 300 659-1 CLIP (Calling Line Identity Presentation) DTMF standard + variant 4. (Taiwan and Kuwait). + + - ETSI Caller-ID support in UK (British Telecom), British Telecomm SIN227, SIN242. + + - Nippon Telegraph & Telephone Corporation JCLIP (Japanese Calling Line Identity + Presentation) standard. + + - Telecommunications Authority of Singapore ACLIP (Analog Calling Line Identity + Presentation) standard. + + - TDD (Telecommunications Device for the Deaf). + +\section adsi_page_sec_2 How does it work? + +\section adsi_page_sec_2a The Bellcore CLASS specification +Most FSK based CLI formats are similar to the US CLASS one, which is as follows: + +The alert tone for CLI during a call is at least 100ms of silence, then +2130Hz + 2750Hz for 88ms to 110ms. When CLI is presented at ringing time, +this tone is not sent. In the US, CLI is usually sent between the first +two rings. This silence period is long in the US, so the message fits easily. +In other places, where the standard ring tone has much smaller silences, +a line voltage reversal is used to wake up a power saving receiver, then the +message is sent, then the phone begins to ring. + +The message is sent using a Bell 202 FSK modem. The data rate is 1200 bits +per second. The message protocol uses 8-bit data words (bytes), each bounded +by a start bit and a stop bit. + +Channel Carrier Message Message Data Checksum +Seizure Signal Type Length Word(s) Word +Signal Word Word + +\section adsi_page_sec_2a1 CHANNEL SEIZURE SIGNAL +The channel seizure is 30 continuous bytes of 55h (01010101), including +the start and stop bits (i.e. 300 bits of alternations in total). +This provides a detectable alternating function to the CPE (i.e. the +modem data pump). + +\section adsi_page_sec_2a2 CARRIER SIGNAL +The carrier signal consists of 180 bits of 1s. This may be reduced to 80 +bits of 1s for caller ID on call waiting. + +\section adsi_page_sec_2a3 MESSAGE TYPE WORD +Various message types are defined. The commonest ones for the US CLASS +standard are: + + - Type 0x04 (SDMF) - single data message. Simple caller ID (CND) + - Type 0x80 (MDMF) - multiple data message. A more flexible caller ID, + with extra information. + +Other messages support message waiting, for voice mail, and other display features. + +\section adsi_page_sec_2a4 MESSAGE LENGTH WORD +The message length word specifies the total number of data words +to follow. + +\section adsi_page_sec_2a5 DATA WORDS +The data words contain the actual message. + +\section adsi_page_sec_2a6 CHECKSUM WORD +The Checksum Word contains the twos complement of the modulo 256 +sum of the other words in the data message (i.e., message type, +message length, and data words). The receiving equipment may +calculate the modulo 256 sum of the received words and add this +sum to the received checksum word. A result of zero generally +indicates that the message was correctly received. Message +retransmission is not supported. The sumcheck word should be followed +by a minimum of two stop bits. + +\section adsi_page_sec_2b The ETSI CLIP specification +The ETSI CLIP specification uses similar messages to the Bellcore specification. +They are not, however, identical. First, ETSI use the V.23 modem standard, rather +than Bell 202. Second, different fields, and different message types are available. + +The wake up indication generally differs from the Bellcore specification, to +accomodate differences in European ring cadences. + +\section adsi_page_sec_2c The ETSI caller ID by DTMF specification +CLI by DTMF is usually sent in a very simple way. The exchange does not give +any prior warning (no reversal, or ring) to wake up the receiver. It just +sends a string of DTMF digits. Around the world several variants of this +basic scheme are used. + +One variant of the digit string is used in Belgium, Brazil, Denmark, Finland, Iceland, +India, Netherlands, Saudi Arabia, Sweden and Uruguay: + + - ADBC + +Each of these fields may be omitted. The following special information codes are defined + + - "00" indicates the calling party number is not available. + - "10" indicates that the presentation of the calling party number is restricted. + +A second variant of the digit string is one of the following: + + - A# + - D1# Number not available because the caller has restricted it. + - D2# Number not available because the call is international. + - D3# Number not available due to technical reasons. + +A third variant of the digit string is used in Taiwan and Kuwait: + + - DC + +A forth variant of the digit string is used in Denmark and Holland: + + - # + +There is no distinctive start marker in this format. + +\section adsi_page_sec_2d The Japanese specification from NTT + +The Japanese caller ID specification is considerably different from any of the others. It +uses V.23 modem signals, but the message structure is uniqeue. Also, the message is delivered +while off hook. This results in a sequence + + - The phone line rings + - CPE answers and waits for the caller ID message + - CPE hangs up on receipt of the caller ID message + - The phone line rings a second time + - The CPE answers a second time, connecting the called party with the caller. + +Timeouts are, obviously, required to ensure this system behaves well when the caller ID message +or the second ring are missing. +*/ + +enum +{ + ADSI_STANDARD_NONE = 0, + ADSI_STANDARD_CLASS = 1, + ADSI_STANDARD_CLIP = 2, + ADSI_STANDARD_ACLIP = 3, + ADSI_STANDARD_JCLIP = 4, + ADSI_STANDARD_CLIP_DTMF = 5, + ADSI_STANDARD_TDD = 6 +}; + +/* In some of the messages code characters are used, as follows: + 'C' for public callbox + 'L' for long distance + 'O' for overseas + 'P' for private + 'S' for service conflict + + Taiwan and Kuwait change this pattern to: + 'C' for coin/public callbox + 'I' for international call + 'O' for out of area call + 'P' for private + */ + +/*! Definitions for CLASS (Custom Local Area Signaling Services) */ +enum +{ + /*! Single data message caller ID */ + CLASS_SDMF_CALLERID = 0x04, + /*! Multiple data message caller ID */ + CLASS_MDMF_CALLERID = 0x80, + /*! Single data message message waiting */ + CLASS_SDMF_MSG_WAITING = 0x06, + /*! Multiple data message message waiting */ + CLASS_MDMF_MSG_WAITING = 0x82 +}; + +/*! CLASS MDMF message IDs */ +enum +{ + /*! Date and time (MMDDHHMM) */ + MCLASS_DATETIME = 0x01, + /*! Caller number */ + MCLASS_CALLER_NUMBER = 0x02, + /*! Dialed number */ + MCLASS_DIALED_NUMBER = 0x03, + /*! Caller number absent: 'O' or 'P' */ + MCLASS_ABSENCE1 = 0x04, + /*! Call forward: universal ('0'), on busy ('1'), or on unanswered ('2') */ + MCLASS_REDIRECT = 0x05, + /*! Long distance: 'L' */ + MCLASS_QUALIFIER = 0x06, + /*! Caller's name */ + MCLASS_CALLER_NAME = 0x07, + /*! Caller's name absent: 'O' or 'P' */ + MCLASS_ABSENCE2 = 0x08, + /*! Alternate route */ + MCLASS_ALT_ROUTE = 0x09 +}; + +/*! CLASS MDMF message waiting message IDs */ +/*! Message waiting/not waiting */ +#define MCLASS_VISUAL_INDICATOR 0x0B + +/*! Definitions for CLIP (Calling Line Identity Presentation) (from ETS 300 659-1) */ +enum +{ + /*! Multiple data message caller ID */ + CLIP_MDMF_CALLERID = 0x80, + /*! Multiple data message message waiting */ + CLIP_MDMF_MSG_WAITING = 0x82, + /*! Multiple data message charge information */ + CLIP_MDMF_CHARGE_INFO = 0x86, + /*! Multiple data message SMS */ + CLIP_MDMF_SMS = 0x89 +}; + +/*! CLIP message IDs (from ETS 300 659-1) */ +enum +{ + /*! Date and time (MMDDHHMM) */ + CLIP_DATETIME = 0x01, + /*! Caller number (AKA calling line identity) */ + CLIP_CALLER_NUMBER = 0x02, + /*! Dialed number (AKA called line identity) */ + CLIP_DIALED_NUMBER = 0x03, + /*! Caller number absent: 'O' or 'P' (AKA reason for absence of calling line identity) */ + CLIP_ABSENCE1 = 0x04, + /*! Caller's name (AKA calling party name) */ + CLIP_CALLER_NAME = 0x07, + /*! Caller's name absent: 'O' or 'P' (AKA reason for absence of calling party name) */ + CLIP_ABSENCE2 = 0x08, + /*! Visual indicator */ + CLIP_VISUAL_INDICATOR = 0x0B, + /*! Message ID */ + CLIP_MESSAGE_ID = 0x0D, + /*! Complementary calling line identity */ + CLIP_COMPLEMENTARY_CALLER_NUMBER = 0x10, + /*! Call type - voice call (1), ring-back-when-free call (2), calling name delivery (3) or msg waiting call(0x81) */ + CLIP_CALLTYPE = 0x11, + /*! Number of messages */ + CLIP_NUM_MSG = 0x13, + /*! Type of forwarded call */ + CLIP_TYPE_OF_FORWARDED_CALL = 0x15, + /*! Type of calling user */ + CLIP_TYPE_OF_CALLING_USER = 0x16, + /*! Redirecting number */ + CLIP_REDIR_NUMBER = 0x1A, + /*! Charge */ + CLIP_CHARGE = 0x20, + /*! Duration of the call */ + CLIP_DURATION = 0x23, + /*! Additional charge */ + CLIP_ADD_CHARGE = 0x21, + /*! Display information */ + CLIP_DISPLAY_INFO = 0x50, + /*! Service information */ + CLIP_SERVICE_INFO = 0x55 +}; + +/*! Definitions for A-CLIP (Analog Calling Line Identity Presentation) */ +enum +{ + /*! Single data message caller ID frame */ + ACLIP_SDMF_CALLERID = 0x04, + /*! Multiple data message caller ID frame */ + ACLIP_MDMF_CALLERID = 0x80 +}; + +/*! A-CLIP MDM message IDs */ +enum +{ + /*! Date and time (MMDDHHMM) */ + ACLIP_DATETIME = 0x01, + /*! Caller number */ + ACLIP_CALLER_NUMBER = 0x02, + /*! Dialed number */ + ACLIP_DIALED_NUMBER = 0x03, + /*! Caller number absent: 'O' or 'P' */ + ACLIP_NUMBER_ABSENCE = 0x04, + /*! Call forward: universal, on busy, or on unanswered */ + ACLIP_REDIRECT = 0x05, + /*! Long distance call: 'L' */ + ACLIP_QUALIFIER = 0x06, + /*! Caller's name */ + ACLIP_CALLER_NAME = 0x07, + /*! Caller's name absent: 'O' or 'P' */ + ACLIP_NAME_ABSENCE = 0x08 +}; + +/*! Definitions for J-CLIP (Japan Calling Line Identity Presentation) */ +/*! Multiple data message caller ID frame */ +#define JCLIP_MDMF_CALLERID 0x40 + +/*! J-CLIP MDM message IDs */ +enum +{ + /*! Caller number */ + JCLIP_CALLER_NUMBER = 0x02, + /*! Caller number data extension signal */ + JCLIP_CALLER_NUM_DES = 0x21, + /*! Dialed number */ + JCLIP_DIALED_NUMBER = 0x09, + /*! Dialed number data extension signal */ + JCLIP_DIALED_NUM_DES = 0x22, + /*! Caller number absent: 'C', 'O', 'P' or 'S' */ + JCLIP_ABSENCE = 0x04 +}; + +/* Definitions for CLIP-DTMF and its variants */ + +/*! Caller number is '#' terminated DTMF. */ +#define CLIP_DTMF_HASH_TERMINATED '#' +/*! Caller number is 'C' terminated DTMF. */ +#define CLIP_DTMF_C_TERMINATED 'C' + +/*! Caller number */ +#define CLIP_DTMF_HASH_CALLER_NUMBER 'A' +/*! Caller number absent: private (1), overseas (2) or not available (3) */ +#define CLIP_DTMF_HASH_ABSENCE 'D' +/*! Caller ID field with no explicit field type */ +#define CLIP_DTMF_HASH_UNSPECIFIED 0 + +/*! Caller number */ +#define CLIP_DTMF_C_CALLER_NUMBER 'A' +/*! Diverting number */ +#define CLIP_DTMF_C_REDIRECT_NUMBER 'D' +/*! Caller number absent: private/restricted (00) or not available (10) */ +#define CLIP_DTMF_C_ABSENCE 'B' + +/*! + ADSI transmitter descriptor. This contains all the state information for an ADSI + (caller ID, CLASS, CLIP, ACLIP) transmit channel. + */ +typedef struct adsi_tx_state_s adsi_tx_state_t; + +/*! + ADSI receiver descriptor. This contains all the state information for an ADSI + (caller ID, CLASS, CLIP, ACLIP, JCLIP) receive channel. + */ +typedef struct adsi_rx_state_s adsi_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Initialise an ADSI receive context. + \param s The ADSI receive context. + \param standard The code for the ADSI standard to be used. + \param put_msg A callback routine called to deliver the received messages + to the application. + \param user_data An opaque pointer for the callback routine. + \return A pointer to the initialised context, or NULL if there was a problem. +*/ +SPAN_DECLARE(adsi_rx_state_t *) adsi_rx_init(adsi_rx_state_t *s, + int standard, + put_msg_func_t put_msg, + void *user_data); + +/*! \brief Release an ADSI receive context. + \param s The ADSI receive context. + \return 0 for OK. +*/ +SPAN_DECLARE(int) adsi_rx_release(adsi_rx_state_t *s); + +/*! \brief Free the resources of an ADSI receive context. + \param s The ADSI receive context. + \return 0 for OK. +*/ +SPAN_DECLARE(int) adsi_rx_free(adsi_rx_state_t *s); + +/*! \brief Receive a chunk of ADSI audio. + \param s The ADSI receive context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE(int) adsi_rx(adsi_rx_state_t *s, const int16_t amp[], int len); + +/*! \brief Initialise an ADSI transmit context. + \param s The ADSI transmit context. + \param standard The code for the ADSI standard to be used. + \return A pointer to the initialised context, or NULL if there was a problem. +*/ +SPAN_DECLARE(adsi_tx_state_t *) adsi_tx_init(adsi_tx_state_t *s, int standard); + +/*! \brief Release an ADSI transmit context. + \param s The ADSI transmit context. + \return 0 for OK. +*/ +SPAN_DECLARE(int) adsi_tx_release(adsi_tx_state_t *s); + +/*! \brief Free the resources of an ADSI transmit context. + \param s The ADSI transmit context. + \return 0 for OK. +*/ +SPAN_DECLARE(int) adsi_tx_free(adsi_tx_state_t *s); + +/*! \brief Adjust the preamble associated with an ADSI transmit context. + \param s The ADSI transmit context. + \param preamble_len The number of bits of preamble. + \param preamble_ones_len The number of bits of continuous one before a message. + \param postamble_ones_len The number of bits of continuous one after a message. + \param stop_bits The number of stop bits per character. +*/ +SPAN_DECLARE(void) adsi_tx_set_preamble(adsi_tx_state_t *s, + int preamble_len, + int preamble_ones_len, + int postamble_ones_len, + int stop_bits); + +/*! \brief Generate a block of ADSI audio samples. + \param s The ADSI transmit context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE(int) adsi_tx(adsi_tx_state_t *s, int16_t amp[], int max_len); + +/*! \brief Request generation of an ADSI alert tone. + \param s The ADSI transmit context. +*/ +SPAN_DECLARE(void) adsi_tx_send_alert_tone(adsi_tx_state_t *s); + +/*! \brief Put a message into the input buffer of an ADSI transmit context. + \param s The ADSI transmit context. + \param msg The message. + \param len The length of the message. + \return The length actually added. If a message is already in progress + in the transmitter, this function will return zero, as it will + not successfully add the message to the buffer. If the message is + invalid (e.g. it is too long), this function will return -1. +*/ +SPAN_DECLARE(int) adsi_tx_put_message(adsi_tx_state_t *s, const uint8_t *msg, int len); + +/*! \brief Get a field from an ADSI message. + \param s The ADSI receive context. + \param msg The message buffer. + \param msg_len The length of the message. + \param pos Current position within the message. Set to -1 when starting a message. + \param field_type The type code for the field. + \param field_body Pointer to the body of the field. + \param field_len The length of the field, or -1 for no more fields, or -2 for message structure corrupt. +*/ +SPAN_DECLARE(int) adsi_next_field(adsi_rx_state_t *s, const uint8_t *msg, int msg_len, int pos, uint8_t *field_type, uint8_t const **field_body, int *field_len); + +/*! \brief Insert the header or a field into an ADSI message. + \param s The ADSI transmit context. + \param msg The message buffer. + \param len The current length of the message. + \param field_type The type code for the new field. + \param field_body Pointer to the body of the new field. + \param field_len The length of the new field. +*/ +SPAN_DECLARE(int) adsi_add_field(adsi_tx_state_t *s, uint8_t *msg, int len, uint8_t field_type, uint8_t const *field_body, int field_len); + +/*! \brief Return a short name for an ADSI standard + \param standard The code for the standard. + \return A pointer to the name. +*/ +SPAN_DECLARE(const char *) adsi_standard_to_str(int standard); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/arctan2.h b/Libraries/spandsp/spandsp/spandsp/arctan2.h new file mode 100644 index 000000000..e732601ee --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/arctan2.h @@ -0,0 +1,108 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * arctan2.h - A quick rough approximate arc tan + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: arctan2.h,v 1.13 2008/05/29 13:04:19 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_ARCTAN2_H_) +#define _SPANDSP_ARCTAN2_H_ + +/*! \page arctan2_page Fast approximate four quadrant arc-tangent +\section arctan2_page_sec_1 What does it do? +This module provides a fast approximate 4-quadrant arc tangent function, +based on something at dspguru.com. The worst case error is about 4.07 degrees. +This is fine for many "where am I" type evaluations in comms. work. + +\section arctan2_page_sec_2 How does it work? +???. +*/ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* This returns its answer as a signed 32 bit integer phase value. */ +static __inline__ int32_t arctan2(float y, float x) +{ + float abs_y; + float angle; + + if (x == 0.0f || y == 0.0f) + return 0; + + abs_y = fabsf(y); + + /* If we are in quadrant II or III, flip things around */ + if (x < 0.0f) + angle = 3.0f - (x + abs_y)/(abs_y - x); + else + angle = 1.0f - (x - abs_y)/(abs_y + x); + angle *= 536870912.0f; + + /* If we are in quadrant III or IV, negate to return an + answer in the range +-pi */ + if (y < 0.0f) + angle = -angle; + return (int32_t) angle; +} +/*- End of function --------------------------------------------------------*/ + +#if 0 +/* This returns its answer in radians, in the range +-pi. */ +static __inline__ float arctan2f(float y, float x) +{ + float angle; + float fx; + float fy; + + if (x == 0.0f || y == 0.0f) + return 0; + fx = fabsf(x); + fy = fabsf(y); + /* Deal with the octants */ + /* N.B. 0.28125 == (1/4 + 1/32) */ + if (fy > fx) + angle = 3.1415926f/2.0f - fx*fy/(y*y + 0.28125f*x*x); + else + angle = fy*fx/(x*x + 0.28125f*y*y); + + /* Deal with the quadrants, to bring the final answer to the range +-pi */ + if (x < 0.0f) + angle = 3.1415926f - angle; + if (y < 0.0f) + angle = -angle; + return angle; +} +/*- End of function --------------------------------------------------------*/ +#endif + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/async.h b/Libraries/spandsp/spandsp/spandsp/async.h new file mode 100644 index 000000000..50eb6e445 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/async.h @@ -0,0 +1,213 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * async.h - Asynchronous serial bit stream encoding and decoding + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: async.h,v 1.25 2009/04/23 14:12:34 steveu Exp $ + */ + +/*! \file */ + +/*! \page async_page Asynchronous bit stream processing +\section async_page_sec_1 What does it do? +The asynchronous serial bit stream processing module provides +generation and decoding facilities for most asynchronous data +formats. It supports: + - 1 or 2 stop bits. + - Odd, even or no parity. + - 5, 6, 7, or 8 bit characters. + - V.14 rate adaption. +The input to this module is a bit stream. This means any symbol synchronisation +and decoding must occur before data is fed to this module. + +\section async_page_sec_2 The transmitter +???. + +\section async_page_sec_3 The receiver +???. +*/ + +#if !defined(_SPANDSP_ASYNC_H_) +#define _SPANDSP_ASYNC_H_ + +/*! Special "bit" values for the bitstream put and get functions, and the signal status functions. */ +enum +{ + /*! \brief The carrier signal has dropped. */ + SIG_STATUS_CARRIER_DOWN = -1, + /*! \brief The carrier signal is up. This merely indicates that carrier + energy has been seen. It is not an indication that the carrier is either + valid, or of the expected type. */ + SIG_STATUS_CARRIER_UP = -2, + /*! \brief The modem is training. This is an early indication that the + signal seems to be of the right type. This may be needed in time critical + applications, like T.38, to forward an early indication of what is happening + on the wire. */ + SIG_STATUS_TRAINING_IN_PROGRESS = -3, + /*! \brief The modem has trained, and is ready for data exchange. */ + SIG_STATUS_TRAINING_SUCCEEDED = -4, + /*! \brief The modem has failed to train. */ + SIG_STATUS_TRAINING_FAILED = -5, + /*! \brief Packet framing (e.g. HDLC framing) is OK. */ + SIG_STATUS_FRAMING_OK = -6, + /*! \brief The data stream has ended. */ + SIG_STATUS_END_OF_DATA = -7, + /*! \brief An abort signal (e.g. an HDLC abort) has been received. */ + SIG_STATUS_ABORT = -8, + /*! \brief A break signal (e.g. an async break) has been received. */ + SIG_STATUS_BREAK = -9, + /*! \brief A modem has completed its task, and shut down. */ + SIG_STATUS_SHUTDOWN_COMPLETE = -10, + /*! \brief Regular octet report for things like HDLC to the MTP standards. */ + SIG_STATUS_OCTET_REPORT = -11, + /*! \brief Notification that a modem has detected signal quality degradation. */ + SIG_STATUS_POOR_SIGNAL_QUALITY = -12, + /*! \brief Notification that a modem retrain has occurred. */ + SIG_STATUS_MODEM_RETRAIN_OCCURRED = -13 +}; + +/*! Message put function for data pumps */ +typedef void (*put_msg_func_t)(void *user_data, const uint8_t *msg, int len); + +/*! Message get function for data pumps */ +typedef int (*get_msg_func_t)(void *user_data, uint8_t *msg, int max_len); + +/*! Byte put function for data pumps */ +typedef void (*put_byte_func_t)(void *user_data, int byte); + +/*! Byte get function for data pumps */ +typedef int (*get_byte_func_t)(void *user_data); + +/*! Bit put function for data pumps */ +typedef void (*put_bit_func_t)(void *user_data, int bit); + +/*! Bit get function for data pumps */ +typedef int (*get_bit_func_t)(void *user_data); + +/*! Completion callback function for tx data pumps */ +typedef void (*modem_tx_status_func_t)(void *user_data, int status); + +/*! Completion callback function for rx data pumps */ +typedef void (*modem_rx_status_func_t)(void *user_data, int status); + +enum +{ + /*! No parity bit should be used */ + ASYNC_PARITY_NONE = 0, + /*! An even parity bit will exist, after the data bits */ + ASYNC_PARITY_EVEN, + /*! An odd parity bit will exist, after the data bits */ + ASYNC_PARITY_ODD +}; + +/*! + Asynchronous data transmit descriptor. This defines the state of a single + working instance of a byte to asynchronous serial converter, for use + in FSK modems. +*/ +typedef struct async_tx_state_s async_tx_state_t; + +/*! + Asynchronous data receive descriptor. This defines the state of a single + working instance of an asynchronous serial to byte converter, for use + in FSK modems. +*/ +typedef struct async_rx_state_s async_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Convert a signal status to a short text description. + \brief Convert a signal status to a short text description. + \param status The modem signal status. + \return A pointer to the description. */ +SPAN_DECLARE(const char *) signal_status_to_str(int status); + +/*! Initialise an asynchronous data transmit context. + \brief Initialise an asynchronous data transmit context. + \param s The transmitter context. + \param data_bits The number of data bit. + \param parity_bits The type of parity. + \param stop_bits The number of stop bits. + \param use_v14 TRUE if V.14 rate adaption processing should be used. + \param get_byte The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. + \return A pointer to the initialised context, or NULL if there was a problem. */ +SPAN_DECLARE(async_tx_state_t *) async_tx_init(async_tx_state_t *s, + int data_bits, + int parity_bits, + int stop_bits, + int use_v14, + get_byte_func_t get_byte, + void *user_data); + +SPAN_DECLARE(int) async_tx_release(async_tx_state_t *s); + +SPAN_DECLARE(int) async_tx_free(async_tx_state_t *s); + +/*! Get the next bit of a transmitted serial bit stream. + \brief Get the next bit of a transmitted serial bit stream. + \param user_data An opaque point which must point to a transmitter context. + \return the next bit, or PUTBIT_END_OF_DATA to indicate the data stream has ended. */ +SPAN_DECLARE_NONSTD(int) async_tx_get_bit(void *user_data); + +/*! Initialise an asynchronous data receiver context. + \brief Initialise an asynchronous data receiver context. + \param s The receiver context. + \param data_bits The number of data bits. + \param parity_bits The type of parity. + \param stop_bits The number of stop bits. + \param use_v14 TRUE if V.14 rate adaption processing should be used. + \param put_byte The callback routine used to put the received data. + \param user_data An opaque pointer. + \return A pointer to the initialised context, or NULL if there was a problem. */ +SPAN_DECLARE(async_rx_state_t *) async_rx_init(async_rx_state_t *s, + int data_bits, + int parity_bits, + int stop_bits, + int use_v14, + put_byte_func_t put_byte, + void *user_data); + +SPAN_DECLARE(int) async_rx_release(async_rx_state_t *s); + +SPAN_DECLARE(int) async_rx_free(async_rx_state_t *s); + +/*! Accept a bit from a received serial bit stream + \brief Accept a bit from a received serial bit stream + \param user_data An opaque point which must point to a receiver context. + \param bit The new bit. Some special values are supported for this field. + - SIG_STATUS_CARRIER_UP + - SIG_STATUS_CARRIER_DOWN + - SIG_STATUS_TRAINING_SUCCEEDED + - SIG_STATUS_TRAINING_FAILED + - SIG_STATUS_END_OF_DATA */ +SPAN_DECLARE_NONSTD(void) async_rx_put_bit(void *user_data, int bit); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/at_interpreter.h b/Libraries/spandsp/spandsp/spandsp/at_interpreter.h new file mode 100644 index 000000000..90cf06169 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/at_interpreter.h @@ -0,0 +1,199 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * at_interpreter.h - AT command interpreter to V.251, V.252, V.253, T.31 and the 3GPP specs. + * + * Written by Steve Underwood + * + * Copyright (C) 2004, 2005, 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: at_interpreter.h,v 1.23 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_AT_INTERPRETER_H_) +#define _SPANDSP_AT_INTERPRETER_H_ + +/*! \page at_page AT command interpreter +\section at_page_sec_1 What does it do? +The AT interpreter module implements V.251, V.252, V.253, T.31 and various 3GPP +modem control commands. + +\section at_page_sec_2 How does it work? +*/ + +typedef struct at_state_s at_state_t; + +typedef int (at_modem_control_handler_t)(at_state_t *s, void *user_data, int op, const char *num); +typedef int (at_tx_handler_t)(at_state_t *s, void *user_data, const uint8_t *buf, size_t len); +typedef int (at_class1_handler_t)(at_state_t *s, void *user_data, int direction, int operation, int val); + +enum at_rx_mode_e +{ + AT_MODE_ONHOOK_COMMAND, + AT_MODE_OFFHOOK_COMMAND, + AT_MODE_CONNECTED, + AT_MODE_DELIVERY, + AT_MODE_HDLC, + AT_MODE_STUFFED +}; + +enum at_call_event_e +{ + AT_CALL_EVENT_ALERTING = 1, + AT_CALL_EVENT_CONNECTED, + AT_CALL_EVENT_ANSWERED, + AT_CALL_EVENT_BUSY, + AT_CALL_EVENT_NO_DIALTONE, + AT_CALL_EVENT_NO_ANSWER, + AT_CALL_EVENT_HANGUP +}; + +enum at_modem_control_operation_e +{ + /*! Start an outgoing call. */ + AT_MODEM_CONTROL_CALL, + /*! Answer an incoming call. */ + AT_MODEM_CONTROL_ANSWER, + /*! Hangup a call. */ + AT_MODEM_CONTROL_HANGUP, + /*! Take the line off hook. */ + AT_MODEM_CONTROL_OFFHOOK, + /*! Put the line on hook. */ + AT_MODEM_CONTROL_ONHOOK, + /*! Control V.24 Circuit 108, "data terminal ready". */ + AT_MODEM_CONTROL_DTR, + /*! Control V.24 Circuit 105, "request to send". */ + AT_MODEM_CONTROL_RTS, + /*! Control V.24 Circuit 106, "clear to send". */ + AT_MODEM_CONTROL_CTS, + /*! Control V.24 Circuit 109, "receive line signal detector" (i.e. carrier detect). */ + AT_MODEM_CONTROL_CAR, + /*! Control V.24 Circuit 125, "ring indicator". */ + AT_MODEM_CONTROL_RNG, + /*! Control V.24 Circuit 107, "data set ready". */ + AT_MODEM_CONTROL_DSR, + /*! Set the caller ID for outgoing calls. */ + AT_MODEM_CONTROL_SETID, + /* The remainder of the control functions should not get past the modem, to the + application. */ + AT_MODEM_CONTROL_RESTART, + AT_MODEM_CONTROL_DTE_TIMEOUT +}; + +enum +{ + AT_RESPONSE_CODE_OK = 0, + AT_RESPONSE_CODE_CONNECT, + AT_RESPONSE_CODE_RING, + AT_RESPONSE_CODE_NO_CARRIER, + AT_RESPONSE_CODE_ERROR, + AT_RESPONSE_CODE_XXX, + AT_RESPONSE_CODE_NO_DIALTONE, + AT_RESPONSE_CODE_BUSY, + AT_RESPONSE_CODE_NO_ANSWER, + AT_RESPONSE_CODE_FCERROR, + AT_RESPONSE_CODE_FRH3 +}; + +/*! + AT profile. +*/ +typedef struct +{ + /*! TRUE if character echo is enabled */ + int echo; + /*! TRUE if verbose reporting is enabled */ + int verbose; + /*! TRUE if result codes are verbose */ + int result_code_format; + /*! TRUE if pulse dialling is the default */ + int pulse_dial; + /*! ??? */ + int double_escape; + /*! ??? */ + int adaptive_receive; + /*! The state of all possible S registers */ + uint8_t s_regs[100]; +} at_profile_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(void) at_set_at_rx_mode(at_state_t *s, int new_mode); + +SPAN_DECLARE(void) at_put_response(at_state_t *s, const char *t); + +SPAN_DECLARE(void) at_put_numeric_response(at_state_t *s, int val); + +SPAN_DECLARE(void) at_put_response_code(at_state_t *s, int code); + +SPAN_DECLARE(void) at_reset_call_info(at_state_t *s); + +/*! Set the call information for an AT interpreter. + \brief Set the call information for an AT interpreter. + \param s The AT interpreter context. + \param id . + \param value . */ +SPAN_DECLARE(void) at_set_call_info(at_state_t *s, char const *id, char const *value); + +SPAN_DECLARE(void) at_display_call_info(at_state_t *s); + +SPAN_DECLARE(int) at_modem_control(at_state_t *s, int op, const char *num); + +SPAN_DECLARE(void) at_call_event(at_state_t *s, int event); + +SPAN_DECLARE(void) at_interpreter(at_state_t *s, const char *cmd, int len); + +SPAN_DECLARE(void) at_set_class1_handler(at_state_t *s, at_class1_handler_t handler, void *user_data); + +/*! Initialise an AT interpreter context. + \brief Initialise an AT interpreter context. + \param s The AT context. + \param at_tx_handler x. + \param at_tx_user_data x. + \param modem_control_handler x. + \param modem_control_user_data x. + \return A pointer to the AT context, or NULL if there was a problem. */ +SPAN_DECLARE(at_state_t *) at_init(at_state_t *s, + at_tx_handler_t *at_tx_handler, + void *at_tx_user_data, + at_modem_control_handler_t *modem_control_handler, + void *modem_control_user_data); + +/*! Release an AT interpreter context. + \brief Release an AT interpreter context. + \param s The AT context. + \return 0 for OK */ +SPAN_DECLARE(int) at_release(at_state_t *s); + +/*! Free an AT interpreter context. + \brief Free an AT interpreter context. + \param s The AT context. + \return 0 for OK */ +SPAN_DECLARE(int) at_free(at_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/awgn.h b/Libraries/spandsp/spandsp/spandsp/awgn.h new file mode 100644 index 000000000..ef4ff09d1 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/awgn.h @@ -0,0 +1,96 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * awgn.h - An additive Gaussian white noise generator + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: awgn.h,v 1.18 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +/* This code is based on some demonstration code in a research + paper somewhere. I can't track down where I got the original from, + so that due recognition can be given. The original had no explicit + copyright notice, and I hope nobody objects to its use here. + + Having a reasonable Gaussian noise generator is pretty important for + telephony testing (in fact, pretty much any DSP testing), and this + one seems to have served me OK. Since the generation of Gaussian + noise is only for test purposes, and not a core system component, + I don't intend to worry excessively about copyright issues, unless + someone worries me. + + The non-core nature of this code also explains why it is unlikely + to ever be optimised. */ + +#if !defined(_SPANDSP_AWGN_H_) +#define _SPANDSP_AWGN_H_ + +/*! \page awgn_page Additive white gaussian noise (AWGN) generation + +\section awgn_page_sec_1 What does it do? +Adding noise is not the most useful thing in most DSP applications, but it is +awfully useful for test suites. + +\section awgn_page_sec_2 How does it work? + +This code is based on some demonstration code in a research paper somewhere. I +can't track down where I got the original from, so that due recognition can be +given. The original had no explicit copyright notice, and I hope nobody objects +to its use here. + +Having a reasonable Gaussian noise generator is pretty important for telephony +testing (in fact, pretty much any DSP testing), and this one seems to have +served me OK. Since the generation of Gaussian noise is only for test purposes, +and not a core system component, I don't intend to worry excessively about +copyright issues, unless someone worries me. + +The non-core nature of this code also explains why it is unlikely to ever be +optimised. +*/ + +/*! + AWGN generator descriptor. This contains all the state information for an AWGN generator. + */ +typedef struct awgn_state_s awgn_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(awgn_state_t *) awgn_init_dbm0(awgn_state_t *s, int idum, float level); + +SPAN_DECLARE(awgn_state_t *) awgn_init_dbov(awgn_state_t *s, int idum, float level); + +SPAN_DECLARE(int) awgn_release(awgn_state_t *s); + +SPAN_DECLARE(int) awgn_free(awgn_state_t *s); + +SPAN_DECLARE(int16_t) awgn(awgn_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/bell_r2_mf.h b/Libraries/spandsp/spandsp/spandsp/bell_r2_mf.h new file mode 100644 index 000000000..398a9c583 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/bell_r2_mf.h @@ -0,0 +1,275 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bell_r2_mf.h - Bell MF and MFC/R2 tone generation and detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bell_r2_mf.h,v 1.24 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_BELL_R2_MF_H_) +#define _SPANDSP_BELL_R2_MF_H_ + +/*! \page mfc_r2_tone_generation_page MFC/R2 tone generation +\section mfc_r2_tone_generation_page_sec_1 What does it do? +The MFC/R2 tone generation module provides for the generation of the +repertoire of 15 dual tones needs for the digital MFC/R2 signalling protocol. + +\section mfc_r2_tone_generation_page_sec_2 How does it work? +*/ + +/*! \page bell_mf_tone_generation_page Bell MF tone generation +\section bell_mf_tone_generation_page_sec_1 What does it do? +The Bell MF tone generation module provides for the generation of the +repertoire of 15 dual tones needs for various Bell MF signalling protocols. + +\section bell_mf_tone_generation_page_sec_2 How does it work? +Basic Bell MF tone generation specs: + - Tone on time = KP: 100+-7ms. All other signals: 68+-7ms + - Tone off time (between digits) = 68+-7ms + - Frequency tolerance +- 1.5% + - Signal level -7+-1dBm per frequency +*/ + +/*! \page mfc_r2_tone_rx_page MFC/R2 tone receiver + +\section mfc_r2_tone_rx_page_sec_1 What does it do? +The MFC/R2 tone receiver module provides for the detection of the +repertoire of 15 dual tones needs for the digital MFC/R2 signalling protocol. +It is compliant with ITU-T Q.441D. + +\section mfc_r2_tone_rx_page_sec_2 How does it work? +Basic MFC/R2 tone detection specs: + - Receiver response range: -5dBm to -35dBm + - Difference in level for a pair of frequencies + - Adjacent tones: <5dB + - Non-adjacent tones: <7dB + - Receiver not to detect a signal of 2 frequencies of level -5dB and + duration <7ms. + - Receiver not to recognise a signal of 2 frequencies having a difference + in level >=20dB. + - Max received signal frequency error: +-10Hz + - The sum of the operate and release times of a 2 frequency signal not to + exceed 80ms (there are no individual specs for the operate and release + times). + - Receiver not to release for signal interruptions <=7ms. + - System malfunction due to signal interruptions >7ms (typically 20ms) is + prevented by further logic elements. +*/ + +/*! \page bell_mf_tone_rx_page Bell MF tone receiver + +\section bell_mf_tone_rx_page_sec_1 What does it do? +The Bell MF tone receiver module provides for the detection of the +repertoire of 15 dual tones needs for various Bell MF signalling protocols. +It is compliant with ITU-T Q.320, ITU-T Q.322, ITU-T Q.323B. + +\section bell_mf_tone_rx_page_sec_2 How does it work? +Basic Bell MF tone detection specs: + - Frequency tolerance +- 1.5% +-10Hz + - Signal level -14dBm to 0dBm + - Perform a "two and only two tones present" test. + - Twist <= 6dB accepted + - Receiver sensitive to signals above -22dBm per frequency + - Test for a minimum of 55ms if KP, or 30ms of other signals. + - Signals to be recognised if the two tones arrive within 8ms of each other. + - Invalid signals result in the return of the re-order tone. + +Note: Above -3dBm the signal starts to clip. We can detect with a little clipping, + but not up to 0dBm, which the above spec seems to require. There isn't a lot + we can do about that. Is the spec. incorrectly worded about the dBm0 reference + point, or have I misunderstood it? +*/ + +/*! The maximum number of Bell MF digits we can buffer. */ +#define MAX_BELL_MF_DIGITS 128 + +/*! + Bell MF generator state descriptor. This defines the state of a single + working instance of a Bell MF generator. +*/ +typedef struct bell_mf_tx_state_s bell_mf_tx_state_t; + +/*! + Bell MF digit detector descriptor. +*/ +typedef struct bell_mf_rx_state_s bell_mf_rx_state_t; + +/*! + MFC/R2 tone detector descriptor. +*/ +typedef struct r2_mf_tx_state_s r2_mf_tx_state_t; + +/*! + MFC/R2 tone detector descriptor. +*/ +typedef struct r2_mf_rx_state_s r2_mf_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Generate a buffer of Bell MF tones. + \param s The Bell MF generator context. + \param amp The buffer for the generated signal. + \param max_samples The required number of generated samples. + \return The number of samples actually generated. This may be less than + max_samples if the input buffer empties. */ +SPAN_DECLARE(int) bell_mf_tx(bell_mf_tx_state_t *s, int16_t amp[], int max_samples); + +/*! \brief Put a string of digits in a Bell MF generator's input buffer. + \param s The Bell MF generator context. + \param digits The string of digits to be added. + \param len The length of the string of digits. If negative, the string is + assumed to be a NULL terminated string. + \return The number of digits actually added. This may be less than the + length of the digit string, if the buffer fills up. */ +SPAN_DECLARE(int) bell_mf_tx_put(bell_mf_tx_state_t *s, const char *digits, int len); + +/*! \brief Initialise a Bell MF generator context. + \param s The Bell MF generator context. + \return A pointer to the Bell MF generator context.*/ +SPAN_DECLARE(bell_mf_tx_state_t *) bell_mf_tx_init(bell_mf_tx_state_t *s); + +/*! \brief Release a Bell MF generator context. + \param s The Bell MF generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) bell_mf_tx_release(bell_mf_tx_state_t *s); + +/*! \brief Free a Bell MF generator context. + \param s The Bell MF generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) bell_mf_tx_free(bell_mf_tx_state_t *s); + +/*! \brief Generate a block of R2 MF tones. + \param s The R2 MF generator context. + \param amp The buffer for the generated signal. + \param samples The required number of generated samples. + \return The number of samples actually generated. */ +SPAN_DECLARE(int) r2_mf_tx(r2_mf_tx_state_t *s, int16_t amp[], int samples); + +/*! \brief Generate a block of R2 MF tones. + \param s The R2 MF generator context. + \param digit The digit to be generated. + \return 0 for OK, or -1 for a bad request. */ +SPAN_DECLARE(int) r2_mf_tx_put(r2_mf_tx_state_t *s, char digit); + +/*! \brief Initialise an R2 MF tone generator context. + \param s The R2 MF generator context. + \param fwd TRUE if the context is for forward signals. FALSE if the + context is for backward signals. + \return A pointer to the MFC/R2 generator context.*/ +SPAN_DECLARE(r2_mf_tx_state_t *) r2_mf_tx_init(r2_mf_tx_state_t *s, int fwd); + +/*! \brief Release an R2 MF tone generator context. + \param s The R2 MF tone generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) r2_mf_tx_release(r2_mf_tx_state_t *s); + +/*! \brief Free an R2 MF tone generator context. + \param s The R2 MF tone generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) r2_mf_tx_free(r2_mf_tx_state_t *s); + +/*! Process a block of received Bell MF audio samples. + \brief Process a block of received Bell MF audio samples. + \param s The Bell MF receiver context. + \param amp The audio sample buffer. + \param samples The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) bell_mf_rx(bell_mf_rx_state_t *s, const int16_t amp[], int samples); + +/*! \brief Get a string of digits from a Bell MF receiver's output buffer. + \param s The Bell MF receiver context. + \param buf The buffer for the received digits. + \param max The maximum number of digits to be returned, + \return The number of digits actually returned. */ +SPAN_DECLARE(size_t) bell_mf_rx_get(bell_mf_rx_state_t *s, char *buf, int max); + +/*! \brief Initialise a Bell MF receiver context. + \param s The Bell MF receiver context. + \param callback An optional callback routine, used to report received digits. If + no callback routine is set, digits may be collected, using the bell_mf_rx_get() + function. + \param user_data An opaque pointer which is associated with the context, + and supplied in callbacks. + \return A pointer to the Bell MF receiver context.*/ +SPAN_DECLARE(bell_mf_rx_state_t *) bell_mf_rx_init(bell_mf_rx_state_t *s, + digits_rx_callback_t callback, + void *user_data); + +/*! \brief Release a Bell MF receiver context. + \param s The Bell MF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) bell_mf_rx_release(bell_mf_rx_state_t *s); + +/*! \brief Free a Bell MF receiver context. + \param s The Bell MF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) bell_mf_rx_free(bell_mf_rx_state_t *s); + +/*! Process a block of received R2 MF audio samples. + \brief Process a block of received R2 MF audio samples. + \param s The R2 MF receiver context. + \param amp The audio sample buffer. + \param samples The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) r2_mf_rx(r2_mf_rx_state_t *s, const int16_t amp[], int samples); + +/*! \brief Get the current digit from an R2 MF receiver. + \param s The R2 MF receiver context. + \return The number digits being received. */ +SPAN_DECLARE(int) r2_mf_rx_get(r2_mf_rx_state_t *s); + +/*! \brief Initialise an R2 MF receiver context. + \param s The R2 MF receiver context. + \param fwd TRUE if the context is for forward signals. FALSE if the + context is for backward signals. + \param callback An optional callback routine, used to report received digits. If + no callback routine is set, digits may be collected, using the r2_mf_rx_get() + function. + \param user_data An opaque pointer which is associated with the context, + and supplied in callbacks. + \return A pointer to the R2 MF receiver context. */ +SPAN_DECLARE(r2_mf_rx_state_t *) r2_mf_rx_init(r2_mf_rx_state_t *s, + int fwd, + tone_report_func_t callback, + void *user_data); + +/*! \brief Release an R2 MF receiver context. + \param s The R2 MF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) r2_mf_rx_release(r2_mf_rx_state_t *s); + +/*! \brief Free an R2 MF receiver context. + \param s The R2 MF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) r2_mf_rx_free(r2_mf_rx_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/bert.h b/Libraries/spandsp/spandsp/spandsp/bert.h new file mode 100644 index 000000000..3871255ba --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/bert.h @@ -0,0 +1,162 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bert.h - Bit error rate tests. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bert.h,v 1.23 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_BERT_H_) +#define _SPANDSP_BERT_H_ + +/*! \page bert_page The Bit Error Rate tester +\section bert_page_sec_1 What does it do? +The Bit Error Rate tester generates a pseudo random bit stream. It also accepts such +a pattern, synchronises to it, and checks the bit error rate in this stream. + +\section bert_page_sec_2 How does it work? +The Bit Error Rate tester generates a bit stream, with a repeating 2047 bit pseudo +random pattern, using an 11 stage polynomial generator. It also accepts such a pattern, +synchronises to it, and checks the bit error rate in this stream. If the error rate is +excessive the tester assumes synchronisation has been lost, and it attempts to +resynchronise with the stream. + +The bit error rate is continuously assessed against decadic ranges - + > 1 in 10^2 + > 1 in 10^3 + > 1 in 10^4 + > 1 in 10^5 + > 1 in 10^6 + > 1 in 10^7 + < 1 in 10^7 +To ensure fairly smooth results from this assessment, each decadic level is assessed +over 10/error rate bits. That is, to assess if the signal's BER is above or below 1 in 10^5 +the software looks over 10*10^5 => 10^6 bits. +*/ + +enum +{ + BERT_REPORT_SYNCED = 0, + BERT_REPORT_UNSYNCED, + BERT_REPORT_REGULAR, + BERT_REPORT_GT_10_2, + BERT_REPORT_LT_10_2, + BERT_REPORT_LT_10_3, + BERT_REPORT_LT_10_4, + BERT_REPORT_LT_10_5, + BERT_REPORT_LT_10_6, + BERT_REPORT_LT_10_7 +}; + +/* The QBF strings should be: + "VoyeZ Le BricK GeanT QuE J'ExaminE PreS Du WharF 123 456 7890 + - * : = $ % ( )" + "ThE QuicK BrowN FoX JumpS OveR ThE LazY DoG 123 456 7890 + - * : = $ % ( )" +*/ + +enum +{ + BERT_PATTERN_ZEROS = 0, + BERT_PATTERN_ONES, + BERT_PATTERN_7_TO_1, + BERT_PATTERN_3_TO_1, + BERT_PATTERN_1_TO_1, + BERT_PATTERN_1_TO_3, + BERT_PATTERN_1_TO_7, + BERT_PATTERN_QBF, + BERT_PATTERN_ITU_O151_23, + BERT_PATTERN_ITU_O151_20, + BERT_PATTERN_ITU_O151_15, + BERT_PATTERN_ITU_O152_11, + BERT_PATTERN_ITU_O153_9 +}; + +/*! + Bit error rate tester (BERT) results descriptor. This is used to report the + results of a BER test. +*/ +typedef struct +{ + int total_bits; + int bad_bits; + int resyncs; +} bert_results_t; + +typedef void (*bert_report_func_t)(void *user_data, int reason, bert_results_t *bert_results); + +/*! + Bit error rate tester (BERT) descriptor. This defines the working state for a + single instance of the BERT. +*/ +typedef struct bert_state_s bert_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Return a short description of a BERT event. + \param event The event type. + \return A pointer to a short text string describing the event. */ +SPAN_DECLARE(const char *) bert_event_to_str(int event); + +/*! Initialise a BERT context. + \param s The BERT context. + \param limit The maximum test duration. + \param pattern One of the supported BERT signal patterns. + \param resync_len ??? + \param resync_percent The percentage of bad bits which will cause a resync. + \return The BERT context. */ +SPAN_DECLARE(bert_state_t *) bert_init(bert_state_t *s, int limit, int pattern, int resync_len, int resync_percent); + +SPAN_DECLARE(int) bert_release(bert_state_t *s); + +SPAN_DECLARE(int) bert_free(bert_state_t *s); + +/*! Get the next bit of the BERT sequence from the generator. + \param s The BERT context. + \return The bit. */ +SPAN_DECLARE(int) bert_get_bit(bert_state_t *s); + +/*! Put the next bit of the BERT sequence to the analyser. + \param s The BERT context. + \param bit The bit. */ +SPAN_DECLARE(void) bert_put_bit(bert_state_t *s, int bit); + +/*! Set the callback function for reporting the test status. + \param s The BERT context. + \param freq The required frequency of regular reports. + \param reporter The callback function. + \param user_data An opaque pointer passed to the reporter routine. */ +SPAN_DECLARE(void) bert_set_report(bert_state_t *s, int freq, bert_report_func_t reporter, void *user_data); + +/*! Get the results of the BERT. + \param s The BERT context. + \param results The results. + \return The size of the result structure. */ +SPAN_DECLARE(int) bert_result(bert_state_t *s, bert_results_t *results); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/biquad.h b/Libraries/spandsp/spandsp/spandsp/biquad.h new file mode 100644 index 000000000..1373dfef7 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/biquad.h @@ -0,0 +1,117 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * biquad.h - General telephony bi-quad section routines (currently this just + * handles canonic/type 2 form) + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: biquad.h,v 1.14 2008/04/17 14:26:59 steveu Exp $ + */ + +/*! \page biquad_page Bi-quadratic filter sections +\section biquad_page_sec_1 What does it do? +???. + +\section biquad_page_sec_2 How does it work? +???. +*/ + +#if !defined(_SPANDSP_BIQUAD_H_) +#define _SPANDSP_BIQUAD_H_ + +typedef struct +{ + int32_t gain; + int32_t a1; + int32_t a2; + int32_t b1; + int32_t b2; + + int32_t z1; + int32_t z2; + +#if FIRST_ORDER_NOISE_SHAPING + int32_t residue; +#elif SECOND_ORDER_NOISE_SHAPING + int32_t residue1; + int32_t residue2; +#endif +} biquad2_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ void biquad2_init(biquad2_state_t *bq, + int32_t gain, + int32_t a1, + int32_t a2, + int32_t b1, + int32_t b2) +{ + bq->gain = gain; + bq->a1 = a1; + bq->a2 = a2; + bq->b1 = b1; + bq->b2 = b2; + + bq->z1 = 0; + bq->z2 = 0; + +#if FIRST_ORDER_NOISE_SHAPING + bq->residue = 0; +#elif SECOND_ORDER_NOISE_SHAPING + bq->residue1 = 0; + bq->residue2 = 0; +#endif +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t biquad2(biquad2_state_t *bq, int16_t sample) +{ + int32_t y; + int32_t z0; + + z0 = sample*bq->gain + bq->z1*bq->a1 + bq->z2*bq->a2; + y = z0 + bq->z1*bq->b1 + bq->z2*bq->b2; + + bq->z2 = bq->z1; + bq->z1 = z0 >> 15; +#if FIRST_ORDER_NOISE_SHAPING + y += bq->residue; + bq->residue = y & 0x7FFF; +#elif SECOND_ORDER_NOISE_SHAPING + y += (2*bq->residue1 - bq->residue2); + bq->residue2 = bq->residue1; + bq->residue1 = y & 0x7FFF; +#endif + y >>= 15; + return (int16_t) y; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/bit_operations.h b/Libraries/spandsp/spandsp/spandsp/bit_operations.h new file mode 100644 index 000000000..56850ffd3 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/bit_operations.h @@ -0,0 +1,314 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bit_operations.h - Various bit level operations, such as bit reversal + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bit_operations.h,v 1.27 2009/07/10 13:15:56 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_BIT_OPERATIONS_H_) +#define _SPANDSP_BIT_OPERATIONS_H_ + +#if defined(__i386__) || defined(__x86_64__) +#if !defined(__SUNPRO_C) || (__SUNPRO_C >= 0x0590) +#define SPANDSP_USE_86_ASM +#endif +#endif + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Find the bit position of the highest set bit in a word + \param bits The word to be searched + \return The bit number of the highest set bit, or -1 if the word is zero. */ +static __inline__ int top_bit(unsigned int bits) +{ +#if defined(SPANDSP_USE_86_ASM) + int res; + + __asm__ (" xorl %[res],%[res];\n" + " decl %[res];\n" + " bsrl %[bits],%[res]\n" + : [res] "=&r" (res) + : [bits] "rm" (bits)); + return res; +#elif defined(__ppc__) || defined(__powerpc__) + int res; + + __asm__ ("cntlzw %[res],%[bits];\n" + : [res] "=&r" (res) + : [bits] "r" (bits)); + return 31 - res; +#elif defined(_M_IX86) + /* Visual Studio i386 */ + __asm + { + xor eax, eax + dec eax + bsr eax, bits + } +#elif defined(_M_X64) + /* Visual Studio x86_64 */ + /* TODO: Need the appropriate x86_64 code */ + int res; + + if (bits == 0) + return -1; + res = 0; + if (bits & 0xFFFF0000) + { + bits &= 0xFFFF0000; + res += 16; + } + if (bits & 0xFF00FF00) + { + bits &= 0xFF00FF00; + res += 8; + } + if (bits & 0xF0F0F0F0) + { + bits &= 0xF0F0F0F0; + res += 4; + } + if (bits & 0xCCCCCCCC) + { + bits &= 0xCCCCCCCC; + res += 2; + } + if (bits & 0xAAAAAAAA) + { + bits &= 0xAAAAAAAA; + res += 1; + } + return res; +#else + int res; + + if (bits == 0) + return -1; + res = 0; + if (bits & 0xFFFF0000) + { + bits &= 0xFFFF0000; + res += 16; + } + if (bits & 0xFF00FF00) + { + bits &= 0xFF00FF00; + res += 8; + } + if (bits & 0xF0F0F0F0) + { + bits &= 0xF0F0F0F0; + res += 4; + } + if (bits & 0xCCCCCCCC) + { + bits &= 0xCCCCCCCC; + res += 2; + } + if (bits & 0xAAAAAAAA) + { + bits &= 0xAAAAAAAA; + res += 1; + } + return res; +#endif +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the bit position of the lowest set bit in a word + \param bits The word to be searched + \return The bit number of the lowest set bit, or -1 if the word is zero. */ +static __inline__ int bottom_bit(unsigned int bits) +{ + int res; + +#if defined(SPANDSP_USE_86_ASM) + __asm__ (" xorl %[res],%[res];\n" + " decl %[res];\n" + " bsfl %[bits],%[res]\n" + : [res] "=&r" (res) + : [bits] "rm" (bits)); + return res; +#else + if (bits == 0) + return -1; + res = 31; + if (bits & 0x0000FFFF) + { + bits &= 0x0000FFFF; + res -= 16; + } + if (bits & 0x00FF00FF) + { + bits &= 0x00FF00FF; + res -= 8; + } + if (bits & 0x0F0F0F0F) + { + bits &= 0x0F0F0F0F; + res -= 4; + } + if (bits & 0x33333333) + { + bits &= 0x33333333; + res -= 2; + } + if (bits & 0x55555555) + { + bits &= 0x55555555; + res -= 1; + } + return res; +#endif +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Bit reverse a byte. + \param data The byte to be reversed. + \return The bit reversed version of data. */ +static __inline__ uint8_t bit_reverse8(uint8_t x) +{ +#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || defined(__powerpc__) + /* If multiply is fast */ + return ((x*0x0802U & 0x22110U) | (x*0x8020U & 0x88440U))*0x10101U >> 16; +#else + /* If multiply is slow, but we have a barrel shifter */ + x = (x >> 4) | (x << 4); + x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); + return ((x & 0xAA) >> 1) | ((x & 0x55) << 1); +#endif +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Bit reverse a 16 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +SPAN_DECLARE(uint16_t) bit_reverse16(uint16_t data); + +/*! \brief Bit reverse a 32 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +SPAN_DECLARE(uint32_t) bit_reverse32(uint32_t data); + +/*! \brief Bit reverse each of the four bytes in a 32 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +SPAN_DECLARE(uint32_t) bit_reverse_4bytes(uint32_t data); + +#if defined(__x86_64__) +/*! \brief Bit reverse each of the eight bytes in a 64 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +SPAN_DECLARE(uint64_t) bit_reverse_8bytes(uint64_t data); +#endif + +/*! \brief Bit reverse each bytes in a buffer. + \param to The buffer to place the reversed data in. + \param from The buffer containing the data to be reversed. + \param len The length of the data in the buffer. */ +SPAN_DECLARE(void) bit_reverse(uint8_t to[], const uint8_t from[], int len); + +/*! \brief Find the number of set bits in a 32 bit word. + \param x The word to be searched. + \return The number of set bits. */ +SPAN_DECLARE(int) one_bits32(uint32_t x); + +/*! \brief Create a mask as wide as the number in a 32 bit word. + \param x The word to be searched. + \return The mask. */ +SPAN_DECLARE(uint32_t) make_mask32(uint32_t x); + +/*! \brief Create a mask as wide as the number in a 16 bit word. + \param x The word to be searched. + \return The mask. */ +SPAN_DECLARE(uint16_t) make_mask16(uint16_t x); + +/*! \brief Find the least significant one in a word, and return a word + with just that bit set. + \param x The word to be searched. + \return The word with the single set bit. */ +static __inline__ uint32_t least_significant_one32(uint32_t x) +{ + return (x & (-(int32_t) x)); +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the most significant one in a word, and return a word + with just that bit set. + \param x The word to be searched. + \return The word with the single set bit. */ +static __inline__ uint32_t most_significant_one32(uint32_t x) +{ +#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || defined(__powerpc__) + return 1 << top_bit(x); +#else + x = make_mask32(x); + return (x ^ (x >> 1)); +#endif +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the parity of a byte. + \param x The byte to be checked. + \return 1 for odd, or 0 for even. */ +static __inline__ int parity8(uint8_t x) +{ + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the parity of a 16 bit word. + \param x The word to be checked. + \return 1 for odd, or 0 for even. */ +static __inline__ int parity16(uint16_t x) +{ + x ^= (x >> 8); + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the parity of a 32 bit word. + \param x The word to be checked. + \return 1 for odd, or 0 for even. */ +static __inline__ int parity32(uint32_t x) +{ + x ^= (x >> 16); + x ^= (x >> 8); + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/bitstream.h b/Libraries/spandsp/spandsp/spandsp/bitstream.h new file mode 100644 index 000000000..a934a77ed --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/bitstream.h @@ -0,0 +1,81 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bitstream.h - Bitstream composition and decomposition routines. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bitstream.h,v 1.14.4.1 2009/12/28 12:20:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_BITSTREAM_H_) +#define _SPANDSP_BITSTREAM_H_ + +/*! \page bitstream_page Bitstream composition and decomposition +\section bitstream_page_sec_1 What does it do? + +\section bitstream_page_sec_2 How does it work? +*/ + +/*! Bitstream handler state */ +typedef struct bitstream_state_s bitstream_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Put a chunk of bits into the output buffer. + \param s A pointer to the bitstream context. + \param c A pointer to the bitstream output buffer. + \param value The value to be pushed into the output buffer. + \param bits The number of bits of value to be pushed. 1 to 25 bits is valid. */ +SPAN_DECLARE(void) bitstream_put(bitstream_state_t *s, uint8_t **c, uint32_t value, int bits); + +/*! \brief Get a chunk of bits from the input buffer. + \param s A pointer to the bitstream context. + \param c A pointer to the bitstream input buffer. + \param bits The number of bits of value to be grabbed. 1 to 25 bits is valid. + \return The value retrieved from the input buffer. */ +SPAN_DECLARE(uint32_t) bitstream_get(bitstream_state_t *s, const uint8_t **c, int bits); + +/*! \brief Flush any residual bit to the output buffer. + \param s A pointer to the bitstream context. + \param c A pointer to the bitstream output buffer. */ +SPAN_DECLARE(void) bitstream_flush(bitstream_state_t *s, uint8_t **c); + +/*! \brief Initialise a bitstream context. + \param s A pointer to the bitstream context. + \param lsb_first TRUE if the bit stream is LSB first, else its MSB first. + \return A pointer to the bitstream context. */ +SPAN_DECLARE(bitstream_state_t *) bitstream_init(bitstream_state_t *s, int direction); + +SPAN_DECLARE(int) bitstream_release(bitstream_state_t *s); + +SPAN_DECLARE(int) bitstream_free(bitstream_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/complex.h b/Libraries/spandsp/spandsp/spandsp/complex.h new file mode 100644 index 000000000..4575f68b6 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/complex.h @@ -0,0 +1,507 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * complex.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: complex.h,v 1.20 2009/02/21 05:39:08 steveu Exp $ + */ + +/*! \file */ + +/*! \page complex_page Complex number support +\section complex_page_sec_1 What does it do? +Complex number support is part of the C99 standard. However, support for this +in C compilers is still patchy. A set of complex number feaures is provided as +a "temporary" measure, until native C language complex number support is +widespread. +*/ + +#if !defined(_SPANDSP_COMPLEX_H_) +#define _SPANDSP_COMPLEX_H_ + +/*! + Floating complex type. +*/ +typedef struct +{ + /*! \brief Real part. */ + float re; + /*! \brief Imaginary part. */ + float im; +} complexf_t; + +/*! + Floating complex type. +*/ +typedef struct +{ + /*! \brief Real part. */ + double re; + /*! \brief Imaginary part. */ + double im; +} complex_t; + +#if defined(HAVE_LONG_DOUBLE) +/*! + Long double complex type. +*/ +typedef struct +{ + /*! \brief Real part. */ + long double re; + /*! \brief Imaginary part. */ + long double im; +} complexl_t; +#endif + +/*! + Complex integer type. +*/ +typedef struct +{ + /*! \brief Real part. */ + int re; + /*! \brief Imaginary part. */ + int im; +} complexi_t; + +/*! + Complex 16 bit integer type. +*/ +typedef struct +{ + /*! \brief Real part. */ + int16_t re; + /*! \brief Imaginary part. */ + int16_t im; +} complexi16_t; + +/*! + Complex 32 bit integer type. +*/ +typedef struct +{ + /*! \brief Real part. */ + int32_t re; + /*! \brief Imaginary part. */ + int32_t im; +} complexi32_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ complexf_t complex_setf(float re, float im) +{ + complexf_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_set(double re, double im) +{ + complex_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_setl(long double re, long double im) +{ + complexl_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexi_t complex_seti(int re, int im) +{ + complexi_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_seti16(int16_t re, int16_t im) +{ + complexi16_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_seti32(int32_t re, int32_t im) +{ + complexi32_t z; + + z.re = re; + z.im = im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexf_t complex_addf(const complexf_t *x, const complexf_t *y) +{ + complexf_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_add(const complex_t *x, const complex_t *y) +{ + complex_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_addl(const complexl_t *x, const complexl_t *y) +{ + complexl_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexi_t complex_addi(const complexi_t *x, const complexi_t *y) +{ + complexi_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_addi16(const complexi16_t *x, const complexi16_t *y) +{ + complexi16_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_addi32(const complexi32_t *x, const complexi32_t *y) +{ + complexi32_t z; + + z.re = x->re + y->re; + z.im = x->im + y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexf_t complex_subf(const complexf_t *x, const complexf_t *y) +{ + complexf_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_sub(const complex_t *x, const complex_t *y) +{ + complex_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_subl(const complexl_t *x, const complexl_t *y) +{ + complexl_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexi_t complex_subi(const complexi_t *x, const complexi_t *y) +{ + complexi_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_subi16(const complexi16_t *x, const complexi16_t *y) +{ + complexi16_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_subi32(const complexi32_t *x, const complexi32_t *y) +{ + complexi32_t z; + + z.re = x->re - y->re; + z.im = x->im - y->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexf_t complex_mulf(const complexf_t *x, const complexf_t *y) +{ + complexf_t z; + + z.re = x->re*y->re - x->im*y->im; + z.im = x->re*y->im + x->im*y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_mul(const complex_t *x, const complex_t *y) +{ + complex_t z; + + z.re = x->re*y->re - x->im*y->im; + z.im = x->re*y->im + x->im*y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_mull(const complexl_t *x, const complexl_t *y) +{ + complexl_t z; + + z.re = x->re*y->re - x->im*y->im; + z.im = x->re*y->im + x->im*y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexi_t complex_muli(const complexi_t *x, const complexi_t *y) +{ + complexi_t z; + + z.re = x->re*y->re - x->im*y->im; + z.im = x->re*y->im + x->im*y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_muli16(const complexi16_t *x, const complexi16_t *y) +{ + complexi16_t z; + + z.re = (int16_t) ((int32_t) x->re*(int32_t) y->re - (int32_t) x->im*(int32_t) y->im); + z.im = (int16_t) ((int32_t) x->re*(int32_t) y->im + (int32_t) x->im*(int32_t) y->re); + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_mul_q1_15(const complexi16_t *x, const complexi16_t *y) +{ + complexi16_t z; + + z.re = (int16_t) (((int32_t) x->re*(int32_t) y->re - (int32_t) x->im*(int32_t) y->im) >> 15); + z.im = (int16_t) (((int32_t) x->re*(int32_t) y->im + (int32_t) x->im*(int32_t) y->re) >> 15); + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_muli32i16(const complexi32_t *x, const complexi16_t *y) +{ + complexi32_t z; + + z.re = x->re*(int32_t) y->re - x->im*(int32_t) y->im; + z.im = x->re*(int32_t) y->im + x->im*(int32_t) y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_muli32(const complexi32_t *x, const complexi32_t *y) +{ + complexi32_t z; + + z.re = x->re*y->re - x->im*y->im; + z.im = x->re*y->im + x->im*y->re; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexf_t complex_divf(const complexf_t *x, const complexf_t *y) +{ + complexf_t z; + float f; + + f = y->re*y->re + y->im*y->im; + z.re = ( x->re*y->re + x->im*y->im)/f; + z.im = (-x->re*y->im + x->im*y->re)/f; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_div(const complex_t *x, const complex_t *y) +{ + complex_t z; + double f; + + f = y->re*y->re + y->im*y->im; + z.re = ( x->re*y->re + x->im*y->im)/f; + z.im = (-x->re*y->im + x->im*y->re)/f; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_divl(const complexl_t *x, const complexl_t *y) +{ + complexl_t z; + long double f; + + f = y->re*y->re + y->im*y->im; + z.re = ( x->re*y->re + x->im*y->im)/f; + z.im = (-x->re*y->im + x->im*y->re)/f; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexf_t complex_conjf(const complexf_t *x) +{ + complexf_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complex_t complex_conj(const complex_t *x) +{ + complex_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ complexl_t complex_conjl(const complexl_t *x) +{ + complexl_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ complexi_t complex_conji(const complexi_t *x) +{ + complexi_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi16_t complex_conji16(const complexi16_t *x) +{ + complexi16_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexi32_t complex_conji32(const complexi32_t *x) +{ + complexi32_t z; + + z.re = x->re; + z.im = -x->im; + return z; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ float powerf(const complexf_t *x) +{ + return x->re*x->re + x->im*x->im; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ double power(const complex_t *x) +{ + return x->re*x->re + x->im*x->im; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ long double powerl(const complexl_t *x) +{ + return x->re*x->re + x->im*x->im; +} +/*- End of function --------------------------------------------------------*/ +#endif + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/complex_filters.h b/Libraries/spandsp/spandsp/spandsp/complex_filters.h new file mode 100644 index 000000000..efc99ab85 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/complex_filters.h @@ -0,0 +1,75 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * complex_filters.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: complex_filters.h,v 1.14 2009/02/03 16:28:41 steveu Exp $ + */ + +#if !defined(_SPANDSP_COMPLEX_FILTERS_H_) +#define _SPANDSP_COMPLEX_FILTERS_H_ + +typedef struct filter_s filter_t; + +typedef float (*filter_step_func_t)(filter_t *fi, float x); + +/*! Filter state */ +typedef struct +{ + int nz; + int np; + filter_step_func_t fsf; +} fspec_t; + +struct filter_s +{ + fspec_t *fs; + float sum; + int ptr; /* Only for moving average filters */ + float v[]; +}; + +typedef struct +{ + filter_t *ref; + filter_t *imf; +} cfilter_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(filter_t *) filter_create(fspec_t *fs); +SPAN_DECLARE(void) filter_delete(filter_t *fi); +SPAN_DECLARE(float) filter_step(filter_t *fi, float x); + +SPAN_DECLARE(cfilter_t *) cfilter_create(fspec_t *fs); +SPAN_DECLARE(void) cfilter_delete(cfilter_t *cfi); +SPAN_DECLARE(complexf_t) cfilter_step(cfilter_t *cfi, const complexf_t *z); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/complex_vector_float.h b/Libraries/spandsp/spandsp/spandsp/complex_vector_float.h new file mode 100644 index 000000000..abfb8bd0e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/complex_vector_float.h @@ -0,0 +1,172 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * complex_vector_float.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: complex_vector_float.h,v 1.13 2009/02/04 13:18:53 steveu Exp $ + */ + +#if !defined(_SPANDSP_COMPLEX_VECTOR_FLOAT_H_) +#define _SPANDSP_COMPLEX_VECTOR_FLOAT_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ void cvec_copyf(complexf_t z[], const complexf_t x[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x[i]; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_copy(complex_t z[], const complex_t x[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x[i]; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ void cvec_copyl(complexl_t z[], const complexl_t x[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x[i]; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ void cvec_zerof(complexf_t z[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = complex_setf(0.0f, 0.0f); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_zero(complex_t z[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = complex_set(0.0, 0.0); +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ void cvec_zerol(complexl_t z[], int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = complex_setl(0.0, 0.0); +} +/*- End of function --------------------------------------------------------*/ +#endif + +static __inline__ void cvec_setf(complexf_t z[], complexf_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_set(complex_t z[], complex_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(HAVE_LONG_DOUBLE) +static __inline__ void cvec_setl(complexl_t z[], complexl_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ +#endif + +SPAN_DECLARE(void) cvec_mulf(complexf_t z[], const complexf_t x[], const complexf_t y[], int n); + +SPAN_DECLARE(void) cvec_mul(complex_t z[], const complex_t x[], const complex_t y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) cvec_mull(complexl_t z[], const complexl_t x[], const complexl_t y[], int n); +#endif + +/*! \brief Find the dot product of two complex float vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexf_t) cvec_dot_prodf(const complexf_t x[], const complexf_t y[], int n); + +/*! \brief Find the dot product of two complex double vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complex_t) cvec_dot_prod(const complex_t x[], const complex_t y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +/*! \brief Find the dot product of two complex long double vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexl_t) cvec_dot_prodl(const complexl_t x[], const complexl_t y[], int n); +#endif + +/*! \brief Find the dot product of two complex float vectors, where the first is a circular buffer + with an offset for the starting position. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \param pos The starting position in the x vector. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexf_t) cvec_circular_dot_prodf(const complexf_t x[], const complexf_t y[], int n, int pos); + +SPAN_DECLARE(void) cvec_lmsf(const complexf_t x[], complexf_t y[], int n, const complexf_t *error); + +SPAN_DECLARE(void) cvec_circular_lmsf(const complexf_t x[], complexf_t y[], int n, int pos, const complexf_t *error); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/complex_vector_int.h b/Libraries/spandsp/spandsp/spandsp/complex_vector_int.h new file mode 100644 index 000000000..114d00e43 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/complex_vector_int.h @@ -0,0 +1,131 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * complex_vector_int.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: complex_vector_int.h,v 1.4 2009/01/31 08:48:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_COMPLEX_VECTOR_INT_H_) +#define _SPANDSP_COMPLEX_VECTOR_INT_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ void cvec_copyi(complexi_t z[], const complexi_t x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_copyi16(complexi16_t z[], const complexi16_t x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_copyi32(complexi32_t z[], const complexi32_t x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_zeroi(complexi_t z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_zeroi16(complexi16_t z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_zeroi32(complexi32_t z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_seti(complexi_t z[], complexi_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_seti16(complexi16_t z[], complexi16_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void cvec_seti32(complexi32_t z[], complexi32_t *x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = *x; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the dot product of two complex int16_t vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexi32_t) cvec_dot_prodi16(const complexi16_t x[], const complexi16_t y[], int n); + +/*! \brief Find the dot product of two complex int32_t vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexi32_t) cvec_dot_prodi32(const complexi32_t x[], const complexi32_t y[], int n); + +/*! \brief Find the dot product of two complex int16_t vectors, where the first is a circular buffer + with an offset for the starting position. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \param pos The starting position in the x vector. + \return The dot product of the two vectors. */ +SPAN_DECLARE(complexi32_t) cvec_circular_dot_prodi16(const complexi16_t x[], const complexi16_t y[], int n, int pos); + +SPAN_DECLARE(void) cvec_lmsi16(const complexi16_t x[], complexi16_t y[], int n, const complexi16_t *error); + +SPAN_DECLARE(void) cvec_circular_lmsi16(const complexi16_t x[], complexi16_t y[], int n, int pos, const complexi16_t *error); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/crc.h b/Libraries/spandsp/spandsp/spandsp/crc.h new file mode 100644 index 000000000..d2dab6539 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/crc.h @@ -0,0 +1,98 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * crc.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: crc.h,v 1.5 2009/01/31 08:48:11 steveu Exp $ + */ + +/*! \file */ + +/*! \page crc_page CRC + +\section crc_page_sec_1 What does it do? + +\section crc_page_sec_2 How does it work? +*/ + +#if !defined(_SPANDSP_CRC_H_) +#define _SPANDSP_CRC_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Calculate the ITU/CCITT CRC-32 value in buffer. + \param buf The buffer containing the data. + \param len The length of the frame. + \param crc The initial CRC value. This is usually 0xFFFFFFFF, or 0 for a new block (it depends on + the application). It is previous returned CRC value for the continuation of a block. + \return The CRC value. +*/ +SPAN_DECLARE(uint32_t) crc_itu32_calc(const uint8_t *buf, int len, uint32_t crc); + +/*! \brief Append an ITU/CCITT CRC-32 value to a frame. + \param buf The buffer containing the frame. This must be at least 2 bytes longer than + the frame it contains, to allow room for the CRC value. + \param len The length of the frame. + \return The new length of the frame. +*/ +SPAN_DECLARE(int) crc_itu32_append(uint8_t *buf, int len); + +/*! \brief Check the ITU/CCITT CRC-32 value in a frame. + \param buf The buffer containing the frame. + \param len The length of the frame. + \return TRUE if the CRC is OK, else FALSE. +*/ +SPAN_DECLARE(int) crc_itu32_check(const uint8_t *buf, int len); + +/*! \brief Calculate the ITU/CCITT CRC-16 value in buffer. + \param buf The buffer containing the data. + \param len The length of the frame. + \param crc The initial CRC value. This is usually 0xFFFF, or 0 for a new block (it depends on + the application). It is previous returned CRC value for the continuation of a block. + \return The CRC value. +*/ +SPAN_DECLARE(uint16_t) crc_itu16_calc(const uint8_t *buf, int len, uint16_t crc); + +/*! \brief Append an ITU/CCITT CRC-16 value to a frame. + \param buf The buffer containing the frame. This must be at least 2 bytes longer than + the frame it contains, to allow room for the CRC value. + \param len The length of the frame. + \return The new length of the frame. +*/ +SPAN_DECLARE(int) crc_itu16_append(uint8_t *buf, int len); + +/*! \brief Check the ITU/CCITT CRC-16 value in a frame. + \param buf The buffer containing the frame. + \param len The length of the frame. + \return TRUE if the CRC is OK, else FALSE. +*/ +SPAN_DECLARE(int) crc_itu16_check(const uint8_t *buf, int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/dc_restore.h b/Libraries/spandsp/spandsp/spandsp/dc_restore.h new file mode 100644 index 000000000..4061f4113 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/dc_restore.h @@ -0,0 +1,93 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * dc_restore.h - General telephony routines to restore the zero D.C. + * level to audio which has a D.C. bias. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: dc_restore.h,v 1.24 2008/09/19 14:02:05 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_DC_RESTORE_H_) +#define _SPANDSP_DC_RESTORE_H_ + +/*! \page dc_restore_page Removing DC bias from a signal + +\section dc_restore_page_sec_1 What does it do? + +Telecoms signals often contain considerable DC, but DC upsets a lot of signal +processing functions. Placing a zero DC restorer at the front of the processing +chain can often simplify the downstream processing. + +\section dc_restore_page_sec_2 How does it work? + +The DC restorer uses a leaky integrator to provide a long-ish term estimate of +the DC bias in the signal. A 32 bit estimate is used for the 16 bit audio, so +the noise introduced by the estimation can be keep in the lower bits, and the 16 +bit DC value, which is subtracted from the signal, is fairly clean. The +following code fragment shows the algorithm used. dc_bias is a 32 bit integer, +while the sample and the resulting clean_sample are 16 bit integers. + + dc_bias += ((((int32_t) sample << 15) - dc_bias) >> 14); + clean_sample = sample - (dc_bias >> 15); +*/ + +/*! + Zero DC restoration descriptor. This defines the working state for a single + instance of DC content filter. +*/ +typedef struct +{ + int32_t state; +} dc_restore_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ void dc_restore_init(dc_restore_state_t *dc) +{ + dc->state = 0; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t dc_restore(dc_restore_state_t *dc, int16_t sample) +{ + dc->state += ((((int32_t) sample << 15) - dc->state) >> 14); + return (int16_t) (sample - (dc->state >> 15)); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t dc_restore_estimate(dc_restore_state_t *dc) +{ + return (int16_t) (dc->state >> 15); +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/dds.h b/Libraries/spandsp/spandsp/spandsp/dds.h new file mode 100644 index 000000000..a2228a47a --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/dds.h @@ -0,0 +1,259 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * dds.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: dds.h,v 1.23 2009/01/31 08:48:11 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_DDS_H_) +#define _SPANDSP_DDS_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Find the phase rate value to achieve a particular frequency. + \param frequency The desired frequency, in Hz. + \return The phase rate which while achieve the desired frequency. +*/ +SPAN_DECLARE(int32_t) dds_phase_rate(float frequency); + +/*! \brief Find the frequency, in Hz, equivalent to a phase rate. + \param phase_rate The phase rate. + \return The equivalent frequency, in Hz. +*/ +SPAN_DECLARE(float) dds_frequency(int32_t phase_rate); + +/*! \brief Find the scaling factor needed to achieve a specified level in dBm0. + \param level The desired signal level, in dBm0. + \return The scaling factor. +*/ +SPAN_DECLARE(int16_t) dds_scaling_dbm0(float level); + +/*! \brief Find the scaling factor needed to achieve a specified level in dBmov. + \param level The desired signal level, in dBmov. + \return The scaling factor. +*/ +SPAN_DECLARE(int16_t) dds_scaling_dbov(float level); + +/*! \brief Find the amplitude for a particular phase. + \param phase The desired phase 32 bit phase. + \return The signal amplitude. +*/ +SPAN_DECLARE(int16_t) dds_lookup(uint32_t phase); + +/*! \brief Find the amplitude for a particular phase offset from an accumulated phase. + \param phase_acc The accumulated phase. + \param phase_offset The phase offset. + \return The signal amplitude. +*/ +SPAN_DECLARE(int16_t) dds_offset(uint32_t phase_acc, int32_t phase_offset); + +/*! \brief Advance the phase, without returning any new signal sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. +*/ +SPAN_DECLARE(void) dds_advance(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Generate an integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The signal amplitude, between -32767 and 32767. +*/ +SPAN_DECLARE(int16_t) dds(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Lookup the integer value of a specified phase. + \param phase The phase accumulator value to be looked up. + \return The signal amplitude, between -32767 and 32767. +*/ +SPAN_DECLARE(int16_t) dds_lookup(uint32_t phase); + +/*! \brief Generate an integer tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The signal amplitude, between -32767 and 32767. +*/ +SPAN_DECLARE(int16_t) dds_mod(uint32_t *phase_acc, int32_t phase_rate, int16_t scale, int32_t phase); + +/*! \brief Lookup the complex integer value of a specified phase. + \param phase The phase accumulator value to be looked up. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi_t) dds_lookup_complexi(uint32_t phase); + +/*! \brief Generate a complex integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi_t) dds_complexi(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Generate a complex integer tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi_t) dds_complexi_mod(uint32_t *phase_acc, int32_t phase_rate, int16_t scale, int32_t phase); + +/*! \brief Generate a complex 16 bit integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi16_t) dds_lookup_complexi16(uint32_t phase); + +/*! \brief Generate a complex 16 bit integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi16_t) dds_complexi16(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Generate a complex 16bit integer tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi16_t) dds_complexi16_mod(uint32_t *phase_acc, int32_t phase_rate, int16_t scale, int32_t phase); + +/*! \brief Generate a complex 32 bit integer tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi32_t) dds_complexi32_mod(uint32_t *phase_acc, int32_t phase_rate, int16_t scale, int32_t phase); + +/*! \brief Generate a complex 32 bit integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi32_t) dds_lookup_complexi32(uint32_t phase); + +/*! \brief Generate a complex 32 bit integer tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi32_t) dds_complexi32(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Generate a complex 32 bit integer tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The complex signal amplitude, between (-32767, -32767) and (32767, 32767). +*/ +SPAN_DECLARE(complexi32_t) dds_complexi32_mod(uint32_t *phase_acc, int32_t phase_rate, int16_t scale, int32_t phase); + +/*! \brief Find the phase rate equivalent to a frequency, in Hz. + \param frequency The frequency, in Hz. + \return The equivalent phase rate. +*/ +SPAN_DECLARE(int32_t) dds_phase_ratef(float frequency); + +/*! \brief Find the frequency, in Hz, equivalent to a phase rate. + \param phase_rate The phase rate. + \return The equivalent frequency, in Hz. +*/ +SPAN_DECLARE(float) dds_frequencyf(int32_t phase_rate); + +/*! \brief Find the scaling factor equivalent to a dBm0 value. + \param level The signal level in dBm0. + \return The equivalent scaling factor. +*/ +SPAN_DECLARE(float) dds_scaling_dbm0f(float level); + +/*! \brief Find the scaling factor equivalent to a dBmov value. + \param level The signal level in dBmov. + \return The equivalent scaling factor. +*/ +SPAN_DECLARE(float) dds_scaling_dbovf(float level); + +/*! \brief Advance the phase, without returning any new signal sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. +*/ +SPAN_DECLARE(void) dds_advancef(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Generate a floating point tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The signal amplitude, between -1.0 and 1.0. +*/ +SPAN_DECLARE(float) ddsf(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Lookup the floating point value of a specified phase. + \param phase The phase accumulator value to be looked up. + \return The signal amplitude, between -1.0 and 1.0. +*/ +SPAN_DECLARE(float) dds_lookupf(uint32_t phase); + +/*! \brief Generate a floating point tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The signal amplitude, between -1.0 and 1.0. +*/ +SPAN_DECLARE(float) dds_modf(uint32_t *phase_acc, int32_t phase_rate, float scale, int32_t phase); + +/*! \brief Generate a complex floating point tone sample. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \return The complex signal amplitude, between (-1.0, -1.0) and (1.0, 1.0). +*/ +SPAN_DECLARE(complexf_t) dds_complexf(uint32_t *phase_acc, int32_t phase_rate); + +/*! \brief Lookup the complex value of a specified phase. + \param phase The phase accumulator value to be looked up. + \return The complex signal amplitude, between (-1.0, -1.0) and (1.0, 1.0). +*/ +SPAN_DECLARE(complexf_t) dds_lookup_complexf(uint32_t phase_acc); + +/*! \brief Generate a complex floating point tone sample, with modulation. + \param phase_acc A pointer to a phase accumulator value. + \param phase_rate The phase increment to be applied. + \param scale The scaling factor. + \param phase The phase offset. + \return The complex signal amplitude, between (-1.0, -1.0) and (1.0, 1.0). +*/ +SPAN_DECLARE(complexf_t) dds_complex_modf(uint32_t *phase_acc, int32_t phase_rate, float scale, int32_t phase); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/dtmf.h b/Libraries/spandsp/spandsp/spandsp/dtmf.h new file mode 100644 index 000000000..d3f81eaa1 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/dtmf.h @@ -0,0 +1,218 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * dtmf.h - DTMF tone generation and detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: dtmf.h,v 1.32 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_DTMF_H_) +#define _SPANDSP_DTMF_H_ + +/*! \page dtmf_rx_page DTMF receiver +\section dtmf_rx_page_sec_1 What does it do? +The DTMF receiver detects the standard DTMF digits. It is compliant with +ITU-T Q.23, ITU-T Q.24, and the local DTMF specifications of most administrations. +Its passes the test suites. It also scores *very* well on the standard +talk-off tests. + +The current design uses floating point extensively. It is not tolerant of DC. +It is expected that a DC restore stage will be placed before the DTMF detector. +Unless the dial tone filter is switched on, the detector has poor tolerance +of dial tone. Whether this matter depends on your application. If you are using +the detector in an IVR application you will need proper echo cancellation to +get good performance in the presence of speech prompts, so dial tone will not +exist. If you do need good dial tone tolerance, a dial tone filter can be +enabled in the detector. + +The DTMF receiver's design assumes the channel is free of any DC component. + +\section dtmf_rx_page_sec_2 How does it work? +Like most other DSP based DTMF detector's, this one uses the Goertzel algorithm +to look for the DTMF tones. What makes each detector design different is just how +that algorithm is used. + +Basic DTMF specs: + - Minimum tone on = 40ms + - Minimum tone off = 50ms + - Maximum digit rate = 10 per second + - Normal twist <= 8dB accepted + - Reverse twist <= 4dB accepted + - S/N >= 15dB will detect OK + - Attenuation <= 26dB will detect OK + - Frequency tolerance +- 1.5% will detect, +-3.5% will reject + +TODO: +*/ + +/*! \page dtmf_tx_page DTMF tone generation +\section dtmf_tx_page_sec_1 What does it do? + +The DTMF tone generation module provides for the generation of the +repertoire of 16 DTMF dual tones. + +\section dtmf_tx_page_sec_2 How does it work? +*/ + +#define MAX_DTMF_DIGITS 128 + +typedef void (*digits_rx_callback_t)(void *user_data, const char *digits, int len); + +/*! + DTMF generator state descriptor. This defines the state of a single + working instance of a DTMF generator. +*/ +typedef struct dtmf_tx_state_s dtmf_tx_state_t; + +/*! + DTMF digit detector descriptor. +*/ +typedef struct dtmf_rx_state_s dtmf_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Generate a buffer of DTMF tones. + \param s The DTMF generator context. + \param amp The buffer for the generated signal. + \param max_samples The required number of generated samples. + \return The number of samples actually generated. This may be less than + max_samples if the input buffer empties. */ +SPAN_DECLARE(int) dtmf_tx(dtmf_tx_state_t *s, int16_t amp[], int max_samples); + +/*! \brief Put a string of digits in a DTMF generator's input buffer. + \param s The DTMF generator context. + \param digits The string of digits to be added. + \param len The length of the string of digits. If negative, the string is + assumed to be a NULL terminated string. + \return The number of digits actually added. This may be less than the + length of the digit string, if the buffer fills up. */ +SPAN_DECLARE(int) dtmf_tx_put(dtmf_tx_state_t *s, const char *digits, int len); + +/*! \brief Change the transmit level for a DTMF tone generator context. + \param s The DTMF generator context. + \param level The level of the low tone, in dBm0. + \param twist The twist, in dB. */ +SPAN_DECLARE(void) dtmf_tx_set_level(dtmf_tx_state_t *s, int level, int twist); + +/*! \brief Change the transmit on and off time for a DTMF tone generator context. + \param s The DTMF generator context. + \param on-time The on time, in ms. + \param off_time The off time, in ms. */ +SPAN_DECLARE(void) dtmf_tx_set_timing(dtmf_tx_state_t *s, int on_time, int off_time); + +/*! \brief Initialise a DTMF tone generator context. + \param s The DTMF generator context. + \return A pointer to the DTMF generator context. */ +SPAN_DECLARE(dtmf_tx_state_t *) dtmf_tx_init(dtmf_tx_state_t *s); + +/*! \brief Release a DTMF tone generator context. + \param s The DTMF tone generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) dtmf_tx_release(dtmf_tx_state_t *s); + +/*! \brief Free a DTMF tone generator context. + \param s The DTMF tone generator context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) dtmf_tx_free(dtmf_tx_state_t *s); + +/*! Set a optional realtime callback for a DTMF receiver context. This function + is called immediately a confirmed state change occurs in the received DTMF. It + is called with the ASCII value for a DTMF tone pair, or zero to indicate no tone + is being received. + \brief Set a realtime callback for a DTMF receiver context. + \param s The DTMF receiver context. + \param callback Callback routine used to report the start and end of digits. + \param user_data An opaque pointer which is associated with the context, + and supplied in callbacks. */ +SPAN_DECLARE(void) dtmf_rx_set_realtime_callback(dtmf_rx_state_t *s, + tone_report_func_t callback, + void *user_data); + +/*! \brief Adjust a DTMF receiver context. + \param s The DTMF receiver context. + \param filter_dialtone TRUE to enable filtering of dialtone, FALSE + to disable, < 0 to leave unchanged. + \param twist Acceptable twist, in dB. < 0 to leave unchanged. + \param reverse_twist Acceptable reverse twist, in dB. < 0 to leave unchanged. + \param threshold The minimum acceptable tone level for detection, in dBm0. + <= -99 to leave unchanged. */ +SPAN_DECLARE(void) dtmf_rx_parms(dtmf_rx_state_t *s, + int filter_dialtone, + int twist, + int reverse_twist, + int threshold); + +/*! Process a block of received DTMF audio samples. + \brief Process a block of received DTMF audio samples. + \param s The DTMF receiver context. + \param amp The audio sample buffer. + \param samples The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) dtmf_rx(dtmf_rx_state_t *s, const int16_t amp[], int samples); + +/*! Get the status of DTMF detection during processing of the last audio + chunk. + \brief Get the status of DTMF detection during processing of the last + audio chunk. + \param s The DTMF receiver context. + \return The current digit status. Either 'x' for a "maybe" condition, or the + digit being detected. */ +SPAN_DECLARE(int) dtmf_rx_status(dtmf_rx_state_t *s); + +/*! \brief Get a string of digits from a DTMF receiver's output buffer. + \param s The DTMF receiver context. + \param digits The buffer for the received digits. + \param max The maximum number of digits to be returned, + \return The number of digits actually returned. */ +SPAN_DECLARE(size_t) dtmf_rx_get(dtmf_rx_state_t *s, char *digits, int max); + +/*! \brief Initialise a DTMF receiver context. + \param s The DTMF receiver context. + \param callback An optional callback routine, used to report received digits. If + no callback routine is set, digits may be collected, using the dtmf_rx_get() + function. + \param user_data An opaque pointer which is associated with the context, + and supplied in callbacks. + \return A pointer to the DTMF receiver context. */ +SPAN_DECLARE(dtmf_rx_state_t *) dtmf_rx_init(dtmf_rx_state_t *s, + digits_rx_callback_t callback, + void *user_data); + +/*! \brief Release a DTMF receiver context. + \param s The DTMF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) dtmf_rx_release(dtmf_rx_state_t *s); + +/*! \brief Free a DTMF receiver context. + \param s The DTMF receiver context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) dtmf_rx_free(dtmf_rx_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/echo.h b/Libraries/spandsp/spandsp/spandsp/echo.h new file mode 100644 index 000000000..b47690918 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/echo.h @@ -0,0 +1,194 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo.h - An echo cancellor, suitable for electrical and acoustic + * cancellation. This code does not currently comply with + * any relevant standards (e.g. G.164/5/7/8). + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: echo.h,v 1.20 2009/09/22 13:11:04 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_ECHO_H_) +#define _SPANDSP_ECHO_H_ + +/*! \page echo_can_page Line echo cancellation for voice + +\section echo_can_page_sec_1 What does it do? +This module aims to provide G.168-2002 compliant echo cancellation, to remove +electrical echoes (e.g. from 2-4 wire hybrids) from voice calls. + +\section echo_can_page_sec_2 How does it work? +The heart of the echo cancellor is FIR filter. This is adapted to match the echo +impulse response of the telephone line. It must be long enough to adequately cover +the duration of that impulse response. The signal transmitted to the telephone line +is passed through the FIR filter. Once the FIR is properly adapted, the resulting +output is an estimate of the echo signal received from the line. This is subtracted +from the received signal. The result is an estimate of the signal which originated +at the far end of the line, free from echos of our own transmitted signal. + +The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and was +introduced in 1960. It is the commonest form of filter adaption used in things +like modem line equalisers and line echo cancellers. There it works very well. +However, it only works well for signals of constant amplitude. It works very poorly +for things like speech echo cancellation, where the signal level varies widely. +This is quite easy to fix. If the signal level is normalised - similar to applying +AGC - LMS can work as well for a signal of varying amplitude as it does for a modem +signal. This normalised least mean squares (NLMS) algorithm is the commonest one used +for speech echo cancellation. Many other algorithms exist - e.g. RLS (essentially +the same as Kalman filtering), FAP, etc. Some perform significantly better than NLMS. +However, factors such as computational complexity and patents favour the use of NLMS. + +A simple refinement to NLMS can improve its performance with speech. NLMS tends +to adapt best to the strongest parts of a signal. If the signal is white noise, +the NLMS algorithm works very well. However, speech has more low frequency than +high frequency content. Pre-whitening (i.e. filtering the signal to flatten +its spectrum) the echo signal improves the adapt rate for speech, and ensures the +final residual signal is not heavily biased towards high frequencies. A very low +complexity filter is adequate for this, so pre-whitening adds little to the +compute requirements of the echo canceller. + +An FIR filter adapted using pre-whitened NLMS performs well, provided certain +conditions are met: + + - The transmitted signal has poor self-correlation. + - There is no signal being generated within the environment being cancelled. + +The difficulty is that neither of these can be guaranteed. + +If the adaption is performed while transmitting noise (or something fairly noise +like, such as voice) the adaption works very well. If the adaption is performed +while transmitting something highly correlative (typically narrow band energy +such as signalling tones or DTMF), the adaption can go seriously wrong. The reason +is there is only one solution for the adaption on a near random signal - the impulse +response of the line. For a repetitive signal, there are any number of solutions +which converge the adaption, and nothing guides the adaption to choose the generalised +one. Allowing an untrained canceller to converge on this kind of narrowband +energy probably a good thing, since at least it cancels the tones. Allowing a well +converged canceller to continue converging on such energy is just a way to ruin +its generalised adaption. A narrowband detector is needed, so adapation can be +suspended at appropriate times. + +The adaption process is based on trying to eliminate the received signal. When +there is any signal from within the environment being cancelled it may upset the +adaption process. Similarly, if the signal we are transmitting is small, noise +may dominate and disturb the adaption process. If we can ensure that the +adaption is only performed when we are transmitting a significant signal level, +and the environment is not, things will be OK. Clearly, it is easy to tell when +we are sending a significant signal. Telling, if the environment is generating a +significant signal, and doing it with sufficient speed that the adaption will +not have diverged too much more we stop it, is a little harder. + +The key problem in detecting when the environment is sourcing significant energy +is that we must do this very quickly. Given a reasonably long sample of the +received signal, there are a number of strategies which may be used to assess +whether that signal contains a strong far end component. However, by the time +that assessment is complete the far end signal will have already caused major +mis-convergence in the adaption process. An assessment algorithm is needed which +produces a fairly accurate result from a very short burst of far end energy. + +\section echo_can_page_sec_3 How do I use it? +The echo cancellor processes both the transmit and receive streams sample by +sample. The processing function is not declared inline. Unfortunately, +cancellation requires many operations per sample, so the call overhead is only a +minor burden. +*/ + +#include "fir.h" + +/* Mask bits for the adaption mode */ +enum +{ + ECHO_CAN_USE_ADAPTION = 0x01, + ECHO_CAN_USE_NLP = 0x02, + ECHO_CAN_USE_CNG = 0x04, + ECHO_CAN_USE_CLIP = 0x08, + ECHO_CAN_USE_SUPPRESSOR = 0x10, + ECHO_CAN_USE_TX_HPF = 0x20, + ECHO_CAN_USE_RX_HPF = 0x40, + ECHO_CAN_DISABLE = 0x80 +}; + +/*! + G.168 echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +typedef struct echo_can_state_s echo_can_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Create a voice echo canceller context. + \param len The length of the canceller, in samples. + \return The new canceller context, or NULL if the canceller could not be created. +*/ +SPAN_DECLARE(echo_can_state_t *) echo_can_init(int len, int adaption_mode); + +/*! Release a voice echo canceller context. + \param ec The echo canceller context. + \return 0 for OK, else -1. +*/ +SPAN_DECLARE(int) echo_can_release(echo_can_state_t *ec); + +/*! Free a voice echo canceller context. + \param ec The echo canceller context. + \return 0 for OK, else -1. +*/ +SPAN_DECLARE(int) echo_can_free(echo_can_state_t *ec); + +/*! Flush (reinitialise) a voice echo canceller context. + \param ec The echo canceller context. +*/ +SPAN_DECLARE(void) echo_can_flush(echo_can_state_t *ec); + +/*! Set the adaption mode of a voice echo canceller context. + \param ec The echo canceller context. + \param adaption_mode The mode. +*/ +SPAN_DECLARE(void) echo_can_adaption_mode(echo_can_state_t *ec, int adaption_mode); + +/*! Process a sample through a voice echo canceller. + \param ec The echo canceller context. + \param tx The transmitted audio sample. + \param rx The received audio sample. + \return The clean (echo cancelled) received sample. +*/ +SPAN_DECLARE(int16_t) echo_can_update(echo_can_state_t *ec, int16_t tx, int16_t rx); + +/*! Process to high pass filter the tx signal. + \param ec The echo canceller context. + \param tx The transmitted auio sample. + \return The HP filtered transmit sample, send this to your D/A. +*/ +SPAN_DECLARE(int16_t) echo_can_hpf_tx(echo_can_state_t *ec, int16_t tx); + +SPAN_DECLARE(void) echo_can_snapshot(echo_can_state_t *ec); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/expose.h b/Libraries/spandsp/spandsp/spandsp/expose.h new file mode 100644 index 000000000..da14722aa --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/expose.h @@ -0,0 +1,90 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * expose.h - Expose the internal structures of spandsp, for users who + * really need that. + * + * Written by Steve Underwood + * + * Copyright (C) 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: expose.h,v 1.14.4.1 2009/12/19 09:47:56 steveu Exp $ + */ + +/*! \file */ + +/* TRY TO ONLY INCLUDE THIS IF YOU REALLY REALLY HAVE TO */ + +#if !defined(_SPANDSP_EXPOSE_H_) +#define _SPANDSP_EXPOSE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/fast_convert.h b/Libraries/spandsp/spandsp/spandsp/fast_convert.h new file mode 100644 index 000000000..ccf275aaf --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/fast_convert.h @@ -0,0 +1,438 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fast_convert.h - Quick ways to convert floating point numbers to integers + * + * Written by Steve Underwood + * + * Copyright (C) 2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fast_convert.h,v 1.9 2009/10/03 04:37:25 steveu Exp $ + */ + +#if !defined(_SPANDSP_FAST_CONVERT_H_) +#define _SPANDSP_FAST_CONVERT_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* The following code, to handle issues with lrint() and lrintf() on various + * platforms, is adapted from similar code in libsndfile, which is: + * + * Copyright (C) 2001-2004 Erik de Castro Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/* + * On Intel Pentium processors (especially PIII and probably P4), converting + * from float to int is very slow. To meet the C specs, the code produced by + * most C compilers targeting Pentium needs to change the FPU rounding mode + * before the float to int conversion is performed. + * + * Changing the FPU rounding mode causes the FPU pipeline to be flushed. It + * is this flushing of the pipeline which is so slow. + * + * Fortunately the ISO C99 specification defines the functions lrint, lrintf, + * llrint and llrintf which fix this problem as a side effect. + * + * On Unix-like systems, the configure process should have detected the + * presence of these functions. If they weren't found we have to replace them + * here with a standard C cast. + */ + +/* + * The C99 prototypes for these functions are as follows: + * + * int rintf(float x); + * int rint(double x); + * long int lrintf(float x); + * long int lrint(double x); + * long long int llrintf(float x); + * long long int llrint(double x); + * + * The presence of the required functions are detected during the configure + * process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in + * the config file. + */ + +#if defined(__CYGWIN__) +#if !defined(__cplusplus) + /* + * CYGWIN has lrint and lrintf functions, but they are slow and buggy: + * http://sourceware.org/ml/cygwin/2005-06/msg00153.html + * http://sourceware.org/ml/cygwin/2005-09/msg00047.html + * The latest version of cygwin seems to have made no effort to fix this. + * These replacement functions (pulled from the Public Domain MinGW + * math.h header) replace the native versions. + */ + static __inline__ long int lrint(double x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + + return retval; + } + + static __inline__ long int lrintf(float x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + return retval; + } +#endif + + /* The fastest way to convert is the equivalent of lrint() */ + static __inline__ long int lfastrint(double x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + + return retval; + } + + static __inline__ long int lfastrintf(float x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + return retval; + } +#elif defined(__GNUC__) || (__SUNPRO_C >= 0x0590) + +#if defined(__i386__) + /* These routines are guaranteed fast on an i386 machine. Using the built in + lrint() and lrintf() should be similar, but they may not always be enabled. + Sometimes, especially with "-O0", you might get slow calls to routines. */ + static __inline__ long int lfastrint(double x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + + return retval; + } + + static __inline__ long int lfastrintf(float x) + { + long int retval; + + __asm__ __volatile__ + ( + "fistpl %0" + : "=m" (retval) + : "t" (x) + : "st" + ); + return retval; + } +#elif defined(__x86_64__) + /* On an x86_64 machine, the fastest thing seems to be a pure assignment from a + double or float to an int. It looks like the design on the x86_64 took account + of the default behaviour specified for C. */ + static __inline__ long int lfastrint(double x) + { + return (long int) (x); + } + + static __inline__ long int lfastrintf(float x) + { + return (long int) (x); + } +#elif defined(__ppc__) || defined(__powerpc__) + static __inline__ long int lfastrint(register double x) + { + int res[2]; + + __asm__ __volatile__ + ( + "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (x) /* Input */ + : "memory" + ); + + return res[1]; + } + + static __inline__ long int lfastrintf(register float x) + { + int res[2]; + + __asm__ __volatile__ + ( + "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (x) /* Input */ + : "memory" + ); + + return res[1]; + } +#else + /* Fallback routines, for unrecognised platforms */ + static __inline__ long int lfastrint(double x) + { + return (long int) x; + } + + static __inline__ long int lfastrintf(float x) + { + return (long int) x; + } +#endif + +#elif defined(_M_IX86) + /* Visual Studio i386 */ + /* + * Win32 doesn't seem to have the lrint() and lrintf() functions. + * Therefore implement inline versions of these functions here. + */ + + __inline long int lrint(double x) + { + long int i; + + _asm + { + fld x + fistp i + }; + return i; + } + + __inline long int lrintf(float x) + { + long int i; + + _asm + { + fld x + fistp i + }; + return i; + } + + __inline float rintf(float flt) + { + _asm + { fld flt + frndint + } + } + + __inline double rint(double dbl) + { + _asm + { + fld dbl + frndint + } + } + + __inline long int lfastrint(double x) + { + long int i; + + _asm + { + fld x + fistp i + }; + return i; + } + + __inline long int lfastrintf(float x) + { + long int i; + + _asm + { + fld x + fistp i + }; + return i; + } +#elif defined(_M_X64) + /* Visual Studio x86_64 */ + /* x86_64 machines will do best with a simple assignment. */ +#include + + __inline long int lrint(double x) + { + return (long int)_mm_cvtsd_si64x( _mm_loadu_pd ((const double*)&x) ); + } + + __inline long int lrintf(float x) + { + return _mm_cvt_ss2si( _mm_load_ss((const float*)&x) ); + } + + __inline long int lfastrint(double x) + { + return (long int) (x); + } + + __inline long int lfastrintf(float x) + { + return (long int) (x); + } +#elif defined(__MWERKS__) && defined(macintosh) + /* This MacOS 9 solution was provided by Stephane Letz */ + + long int __inline__ lfastrint(register double x) + { + long int res[2]; + + asm + { + fctiw x, x + stfd x, res + } + return res[1]; + } + + long int __inline__ lfastrintf(register float x) + { + long int res[2]; + + asm + { + fctiw x, x + stfd x, res + } + return res[1]; + } +#elif defined(__MACH__) && defined(__APPLE__) && (defined(__ppc__) || defined(__powerpc__)) + /* For Apple Mac OS/X - do recent versions still need this? */ + + static __inline__ long int lfastrint(register double x) + { + int res[2]; + + __asm__ __volatile__ + ( + "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (x) /* Input */ + : "memory" + ); + + return res[1]; + } + + static __inline__ long int lfastrintf(register float x) + { + int res[2]; + + __asm__ __volatile__ + ( + "fctiw %1, %1\n\t" + "stfd %1, %0" + : "=m" (res) /* Output */ + : "f" (x) /* Input */ + : "memory" + ); + + return res[1]; + } +#else + /* There is nothing else to do, but use a simple casting operation, instead of a real + rint() type function. Since we are only trying to use rint() to speed up conversions, + the accuracy issues related to changing the rounding scheme are of little concern + to us. */ + + #if !defined(__sgi) && !defined(__sunos) && !defined(__solaris) && !defined(__sun) + #warning "No usable lrint() and lrintf() functions available." + #warning "Replacing these functions with a simple C cast." + #endif + + static __inline__ long int lrint(double x) + { + return (long int) (x); + } + + static __inline__ long int lrintf(float x) + { + return (long int) (x); + } + + static __inline__ long int lfastrint(double x) + { + return (long int) (x); + } + + static __inline__ long int lfastrintf(float x) + { + return (long int) (x); + } +#endif + +#if defined(__cplusplus) +} +#endif + +#endif + +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/fax.h b/Libraries/spandsp/spandsp/spandsp/fax.h new file mode 100644 index 000000000..fe0598b1a --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/fax.h @@ -0,0 +1,133 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fax.h - definitions for analogue line ITU T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fax.h,v 1.39 2009/03/13 12:59:26 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_FAX_H_) +#define _SPANDSP_FAX_H_ + +/*! \page fax_page FAX over analogue modem handling + +\section fax_page_sec_1 What does it do? + +\section fax_page_sec_2 How does it work? +*/ + +typedef struct fax_state_s fax_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Apply T.30 receive processing to a block of audio samples. + \brief Apply T.30 receive processing to a block of audio samples. + \param s The FAX context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. This should only be non-zero if + the software has reached the end of the FAX call. +*/ +SPAN_DECLARE(int) fax_rx(fax_state_t *s, int16_t *amp, int len); + +/*! Apply fake T.30 receive processing when a block of audio samples is missing (e.g due + to packet loss). + \brief Apply fake T.30 receive processing. + \param s The FAX context. + \param len The number of samples to fake. + \return The number of samples unprocessed. This should only be non-zero if + the software has reached the end of the FAX call. +*/ +SPAN_DECLARE(int) fax_rx_fillin(fax_state_t *s, int len); + +/*! Apply T.30 transmit processing to generate a block of audio samples. + \brief Apply T.30 transmit processing to generate a block of audio samples. + \param s The FAX context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. This will be zero when + there is nothing to send. +*/ +SPAN_DECLARE(int) fax_tx(fax_state_t *s, int16_t *amp, int max_len); + +/*! Select whether silent audio will be sent when FAX transmit is idle. + \brief Select whether silent audio will be sent when FAX transmit is idle. + \param s The FAX context. + \param transmit_on_idle TRUE if silent audio should be output when the FAX transmitter is + idle. FALSE to transmit zero length audio when the FAX transmitter is idle. The default + behaviour is FALSE. +*/ +SPAN_DECLARE(void) fax_set_transmit_on_idle(fax_state_t *s, int transmit_on_idle); + +/*! Select whether talker echo protection tone will be sent for the image modems. + \brief Select whether TEP will be sent for the image modems. + \param s The FAX context. + \param use_tep TRUE if TEP should be sent. +*/ +SPAN_DECLARE(void) fax_set_tep_mode(fax_state_t *s, int use_tep); + +/*! Get a pointer to the T.30 engine associated with a FAX context. + \brief Get a pointer to the T.30 engine associated with a FAX context. + \param s The FAX context. + \return A pointer to the T.30 context, or NULL. +*/ +SPAN_DECLARE(t30_state_t *) fax_get_t30_state(fax_state_t *s); + +/*! Get a pointer to the logging context associated with a FAX context. + \brief Get a pointer to the logging context associated with a FAX context. + \param s The FAX context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) fax_get_logging_state(fax_state_t *s); + +/*! Initialise a FAX context. + \brief Initialise a FAX context. + \param s The FAX context. + \param calling_party TRUE if the context is for a calling party. FALSE if the + context is for an answering party. + \return A pointer to the FAX context, or NULL if there was a problem. +*/ +SPAN_DECLARE(fax_state_t *) fax_init(fax_state_t *s, int calling_party); + +/*! Release a FAX context. + \brief Release a FAX context. + \param s The FAX context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) fax_release(fax_state_t *s); + +/*! Free a FAX context. + \brief Free a FAX context. + \param s The FAX context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) fax_free(fax_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/fax_modems.h b/Libraries/spandsp/spandsp/spandsp/fax_modems.h new file mode 100644 index 000000000..ad48b8f24 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/fax_modems.h @@ -0,0 +1,91 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fax_modems.h - definitions for the analogue modem set for fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fax_modems.h,v 1.11 2009/04/26 12:55:23 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_FAX_MODEMS_H_) +#define _SPANDSP_FAX_MODEMS_H_ + +enum +{ + FAX_MODEM_NONE = -1, + FAX_MODEM_FLUSH = 0, + FAX_MODEM_SILENCE_TX, + FAX_MODEM_SILENCE_RX, + FAX_MODEM_CED_TONE, + FAX_MODEM_CNG_TONE, + FAX_MODEM_NOCNG_TONE, + FAX_MODEM_V21_TX, + FAX_MODEM_V17_TX, + FAX_MODEM_V27TER_TX, + FAX_MODEM_V29_TX, + FAX_MODEM_V21_RX, + FAX_MODEM_V17_RX, + FAX_MODEM_V27TER_RX, + FAX_MODEM_V29_RX +}; + +/*! + The set of modems needed for FAX, plus the auxilliary stuff, like tone generation. +*/ +typedef struct fax_modems_state_s fax_modems_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* N.B. the following are currently a work in progress */ +SPAN_DECLARE(int) fax_modems_v17_v21_rx(void *user_data, const int16_t amp[], int len); +SPAN_DECLARE(int) fax_modems_v27ter_v21_rx(void *user_data, const int16_t amp[], int len); +SPAN_DECLARE(int) fax_modems_v29_v21_rx(void *user_data, const int16_t amp[], int len); +SPAN_DECLARE(int) fax_modems_v17_v21_rx_fillin(void *user_data, int len); +SPAN_DECLARE(int) fax_modems_v27ter_v21_rx_fillin(void *user_data, int len); +SPAN_DECLARE(int) fax_modems_v29_v21_rx_fillin(void *user_data, int len); +SPAN_DECLARE(void) fax_modems_start_rx_modem(fax_modems_state_t *s, int which); + +SPAN_DECLARE(void) fax_modems_set_tep_mode(fax_modems_state_t *s, int use_tep); + +SPAN_DECLARE(fax_modems_state_t *) fax_modems_init(fax_modems_state_t *s, + int use_tep, + hdlc_frame_handler_t hdlc_accept, + hdlc_underflow_handler_t hdlc_tx_underflow, + put_bit_func_t non_ecm_put_bit, + get_bit_func_t non_ecm_get_bit, + tone_report_func_t tone_callback, + void *user_data); + +SPAN_DECLARE(int) fax_modems_release(fax_modems_state_t *s); + +SPAN_DECLARE(int) fax_modems_free(fax_modems_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/fir.h b/Libraries/spandsp/spandsp/spandsp/fir.h new file mode 100644 index 000000000..c3944c176 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/fir.h @@ -0,0 +1,304 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fir.h - General telephony FIR routines + * + * Written by Steve Underwood + * + * Copyright (C) 2002 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fir.h,v 1.13 2008/04/17 14:27:00 steveu Exp $ + */ + +/*! \page fir_page FIR filtering +\section fir_page_sec_1 What does it do? +???. + +\section fir_page_sec_2 How does it work? +???. +*/ + +#if !defined(_SPANDSP_FIR_H_) +#define _SPANDSP_FIR_H_ + +#if defined(USE_MMX) || defined(USE_SSE2) +#include "mmx.h" +#endif + +/*! + 16 bit integer FIR descriptor. This defines the working state for a single + instance of an FIR filter using 16 bit integer coefficients. +*/ +typedef struct +{ + int taps; + int curr_pos; + const int16_t *coeffs; + int16_t *history; +} fir16_state_t; + +/*! + 32 bit integer FIR descriptor. This defines the working state for a single + instance of an FIR filter using 32 bit integer coefficients, and filtering + 16 bit integer data. +*/ +typedef struct +{ + int taps; + int curr_pos; + const int32_t *coeffs; + int16_t *history; +} fir32_state_t; + +/*! + Floating point FIR descriptor. This defines the working state for a single + instance of an FIR filter using floating point coefficients and data. +*/ +typedef struct +{ + int taps; + int curr_pos; + const float *coeffs; + float *history; +} fir_float_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ const int16_t *fir16_create(fir16_state_t *fir, + const int16_t *coeffs, + int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; +#if defined(USE_MMX) || defined(USE_SSE2) + if ((fir->history = malloc(2*taps*sizeof(int16_t)))) + memset(fir->history, 0, 2*taps*sizeof(int16_t)); +#else + if ((fir->history = (int16_t *) malloc(taps*sizeof(int16_t)))) + memset(fir->history, 0, taps*sizeof(int16_t)); +#endif + return fir->history; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void fir16_flush(fir16_state_t *fir) +{ +#if defined(USE_MMX) || defined(USE_SSE2) + memset(fir->history, 0, 2*fir->taps*sizeof(int16_t)); +#else + memset(fir->history, 0, fir->taps*sizeof(int16_t)); +#endif +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void fir16_free(fir16_state_t *fir) +{ + free(fir->history); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t fir16(fir16_state_t *fir, int16_t sample) +{ + int i; + int32_t y; +#if defined(USE_MMX) + mmx_t *mmx_coeffs; + mmx_t *mmx_hist; + + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + + mmx_coeffs = (mmx_t *) fir->coeffs; + mmx_hist = (mmx_t *) &fir->history[fir->curr_pos]; + i = fir->taps; + pxor_r2r(mm4, mm4); + /* 8 samples per iteration, so the filter must be a multiple of 8 long. */ + while (i > 0) + { + movq_m2r(mmx_coeffs[0], mm0); + movq_m2r(mmx_coeffs[1], mm2); + movq_m2r(mmx_hist[0], mm1); + movq_m2r(mmx_hist[1], mm3); + mmx_coeffs += 2; + mmx_hist += 2; + pmaddwd_r2r(mm1, mm0); + pmaddwd_r2r(mm3, mm2); + paddd_r2r(mm0, mm4); + paddd_r2r(mm2, mm4); + i -= 8; + } + movq_r2r(mm4, mm0); + psrlq_i2r(32, mm0); + paddd_r2r(mm0, mm4); + movd_r2m(mm4, y); + emms(); +#elif defined(USE_SSE2) + xmm_t *xmm_coeffs; + xmm_t *xmm_hist; + + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + + xmm_coeffs = (xmm_t *) fir->coeffs; + xmm_hist = (xmm_t *) &fir->history[fir->curr_pos]; + i = fir->taps; + pxor_r2r(xmm4, xmm4); + /* 16 samples per iteration, so the filter must be a multiple of 16 long. */ + while (i > 0) + { + movdqu_m2r(xmm_coeffs[0], xmm0); + movdqu_m2r(xmm_coeffs[1], xmm2); + movdqu_m2r(xmm_hist[0], xmm1); + movdqu_m2r(xmm_hist[1], xmm3); + xmm_coeffs += 2; + xmm_hist += 2; + pmaddwd_r2r(xmm1, xmm0); + pmaddwd_r2r(xmm3, xmm2); + paddd_r2r(xmm0, xmm4); + paddd_r2r(xmm2, xmm4); + i -= 16; + } + movdqa_r2r(xmm4, xmm0); + psrldq_i2r(8, xmm0); + paddd_r2r(xmm0, xmm4); + movdqa_r2r(xmm4, xmm0); + psrldq_i2r(4, xmm0); + paddd_r2r(xmm0, xmm4); + movd_r2m(xmm4, y); +#else + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i]*fir->history[i - offset1]; + for ( ; i >= 0; i--) + y += fir->coeffs[i]*fir->history[i + offset2]; +#endif + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ const int16_t *fir32_create(fir32_state_t *fir, + const int32_t *coeffs, + int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; + fir->history = (int16_t *) malloc(taps*sizeof(int16_t)); + if (fir->history) + memset(fir->history, '\0', taps*sizeof(int16_t)); + return fir->history; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void fir32_flush(fir32_state_t *fir) +{ + memset(fir->history, 0, fir->taps*sizeof(int16_t)); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void fir32_free(fir32_state_t *fir) +{ + free(fir->history); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t fir32(fir32_state_t *fir, int16_t sample) +{ + int i; + int32_t y; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i]*fir->history[i - offset1]; + for ( ; i >= 0; i--) + y += fir->coeffs[i]*fir->history[i + offset2]; + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ const float *fir_float_create(fir_float_state_t *fir, + const float *coeffs, + int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; + fir->history = (float *) malloc(taps*sizeof(float)); + if (fir->history) + memset(fir->history, '\0', taps*sizeof(float)); + return fir->history; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void fir_float_free(fir_float_state_t *fir) +{ + free(fir->history); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t fir_float(fir_float_state_t *fir, int16_t sample) +{ + int i; + float y; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i]*fir->history[i - offset1]; + for ( ; i >= 0; i--) + y += fir->coeffs[i]*fir->history[i + offset2]; + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) y; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/fsk.h b/Libraries/spandsp/spandsp/spandsp/fsk.h new file mode 100644 index 000000000..854d9e6a2 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/fsk.h @@ -0,0 +1,255 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fsk.h - FSK modem transmit and receive parts + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fsk.h,v 1.41 2009/11/02 13:25:20 steveu Exp $ + */ + +/*! \file */ + +/*! \page fsk_page FSK modems +\section fsk_page_sec_1 What does it do? +Most of the oldest telephony modems use incoherent FSK modulation. This module can +be used to implement both the transmit and receive sides of a number of these +modems. There are integrated definitions for: + + - V.21 + - V.23 + - Bell 103 + - Bell 202 + - Weitbrecht (Used for TDD - Telecoms Device for the Deaf) + +The audio output or input is a stream of 16 bit samples, at 8000 samples/second. +The transmit and receive sides can be used independantly. + +\section fsk_page_sec_2 The transmitter + +The FSK transmitter uses a DDS generator to synthesise the waveform. This +naturally produces phase coherent transitions, as the phase update rate is +switched, producing a clean spectrum. The symbols are not generally an integer +number of samples long. However, the symbol time for the fastest data rate +generally used (1200bps) is more than 7 samples long. The jitter resulting from +switching at the nearest sample is, therefore, acceptable. No interpolation is +used. + +\section fsk_page_sec_3 The receiver + +The FSK receiver uses a quadrature correlation technique to demodulate the +signal. Two DDS quadrature oscillators are used. The incoming signal is +correlated with the oscillator signals over a period of one symbol. The +oscillator giving the highest net correlation from its I and Q outputs is the +one that matches the frequency being transmitted during the correlation +interval. Because the transmission is totally asynchronous, the demodulation +process must run sample by sample to find the symbol transitions. The +correlation is performed on a sliding window basis, so the computational load of +demodulating sample by sample is not great. + +Two modes of symbol synchronisation are provided: + + - In synchronous mode, symbol transitions are smoothed, to track their true + position in the prescence of high timing jitter. This provides the most + reliable symbol recovery in poor signal to noise conditions. However, it + takes a little time to settle, so it not really suitable for data streams + which must start up instantaneously (e.g. the TDD systems used by hearing + impaired people). + + - In asynchronous mode each transition is taken at face value, with no temporal + smoothing. There is no settling time for this mode, but when the signal to + noise ratio is very poor it does not perform as well as the synchronous mode. +*/ + +#if !defined(_SPANDSP_FSK_H_) +#define _SPANDSP_FSK_H_ + +/*! + FSK modem specification. This defines the frequencies, signal levels and + baud rate (== bit rate for simple FSK) for a single channel of an FSK modem. +*/ +typedef struct +{ + /*! Short text name for the modem. */ + const char *name; + /*! The frequency of the zero bit state, in Hz */ + int freq_zero; + /*! The frequency of the one bit state, in Hz */ + int freq_one; + /*! The transmit power level, in dBm0 */ + int tx_level; + /*! The minimum acceptable receive power level, in dBm0 */ + int min_level; + /*! The bit rate of the modem, in units of 1/100th bps */ + int baud_rate; +} fsk_spec_t; + +/* Predefined FSK modem channels */ +enum +{ + FSK_V21CH1 = 0, + FSK_V21CH2, + FSK_V23CH1, + FSK_V23CH2, + FSK_BELL103CH1, + FSK_BELL103CH2, + FSK_BELL202, + FSK_WEITBRECHT, /* 45.45 baud version, used for TDD (Telecom Device for the Deaf) */ + FSK_WEITBRECHT50 /* 50 baud version, used for TDD (Telecom Device for the Deaf) */ +}; + +enum +{ + FSK_FRAME_MODE_ASYNC = 0, + FSK_FRAME_MODE_SYNC = 1, + FSK_FRAME_MODE_5N1_FRAMES = 7, /* 5 bits of data + start bit + stop bit */ + FSK_FRAME_MODE_7N1_FRAMES = 9, /* 7 bits of data + start bit + stop bit */ + FSK_FRAME_MODE_8N1_FRAMES = 10 /* 8 bits of data + start bit + stop bit */ +}; + +SPAN_DECLARE_DATA extern const fsk_spec_t preset_fsk_specs[]; + +/*! + FSK modem transmit descriptor. This defines the state of a single working + instance of an FSK modem transmitter. +*/ +typedef struct fsk_tx_state_s fsk_tx_state_t; + +/* The longest window will probably be 106 for 75 baud */ +#define FSK_MAX_WINDOW_LEN 128 + +/*! + FSK modem receive descriptor. This defines the state of a single working + instance of an FSK modem receiver. +*/ +typedef struct fsk_rx_state_s fsk_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an FSK modem transmit context. + \brief Initialise an FSK modem transmit context. + \param s The modem context. + \param spec The specification of the modem tones and rate. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(fsk_tx_state_t *) fsk_tx_init(fsk_tx_state_t *s, + const fsk_spec_t *spec, + get_bit_func_t get_bit, + void *user_data); + +SPAN_DECLARE(int) fsk_tx_restart(fsk_tx_state_t *s, const fsk_spec_t *spec); + +SPAN_DECLARE(int) fsk_tx_release(fsk_tx_state_t *s); + +SPAN_DECLARE(int) fsk_tx_free(fsk_tx_state_t *s); + +/*! Adjust an FSK modem transmit context's power output. + \brief Adjust an FSK modem transmit context's power output. + \param s The modem context. + \param power The power level, in dBm0 */ +SPAN_DECLARE(void) fsk_tx_power(fsk_tx_state_t *s, float power); + +SPAN_DECLARE(void) fsk_tx_set_get_bit(fsk_tx_state_t *s, get_bit_func_t get_bit, void *user_data); + +/*! Change the modem status report function associated with an FSK modem transmit context. + \brief Change the modem status report function associated with an FSK modem transmit context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) fsk_tx_set_modem_status_handler(fsk_tx_state_t *s, modem_tx_status_func_t handler, void *user_data); + +/*! Generate a block of FSK modem audio samples. + \brief Generate a block of FSK modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) fsk_tx(fsk_tx_state_t *s, int16_t amp[], int len); + +/*! Get the current received signal power. + \param s The modem context. + \return The signal power, in dBm0. */ +SPAN_DECLARE(float) fsk_rx_signal_power(fsk_rx_state_t *s); + +/*! Adjust an FSK modem receive context's carrier detect power threshold. + \brief Adjust an FSK modem receive context's carrier detect power threshold. + \param s The modem context. + \param cutoff The power level, in dBm0 */ +SPAN_DECLARE(void) fsk_rx_signal_cutoff(fsk_rx_state_t *s, float cutoff); + +/*! Initialise an FSK modem receive context. + \brief Initialise an FSK modem receive context. + \param s The modem context. + \param spec The specification of the modem tones and rate. + \param framing_mode 0 for fully asynchronous mode. 1 for synchronous mode. >1 for + this many bits per asynchronous character frame. + \param put_bit The callback routine used to put the received data. + \param user_data An opaque pointer. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(fsk_rx_state_t *) fsk_rx_init(fsk_rx_state_t *s, + const fsk_spec_t *spec, + int framing_mode, + put_bit_func_t put_bit, + void *user_data); + +SPAN_DECLARE(int) fsk_rx_restart(fsk_rx_state_t *s, const fsk_spec_t *spec, int framing_mode); + +SPAN_DECLARE(int) fsk_rx_release(fsk_rx_state_t *s); + +SPAN_DECLARE(int) fsk_rx_free(fsk_rx_state_t *s); + +/*! Process a block of received FSK modem audio samples. + \brief Process a block of received FSK modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE_NONSTD(int) fsk_rx(fsk_rx_state_t *s, const int16_t *amp, int len); + +/*! Fake processing of a missing block of received FSK modem audio samples + (e.g due to packet loss). + \brief Fake processing of a missing block of received FSK modem audio samples. + \param s The modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE(int) fsk_rx_fillin(fsk_rx_state_t *s, int len); + +SPAN_DECLARE(void) fsk_rx_set_put_bit(fsk_rx_state_t *s, put_bit_func_t put_bit, void *user_data); + +/*! Change the modem status report function associated with an FSK modem receive context. + \brief Change the modem status report function associated with an FSK modem receive context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) fsk_rx_set_modem_status_handler(fsk_rx_state_t *s, modem_rx_status_func_t handler, void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/g168models.h b/Libraries/spandsp/spandsp/spandsp/g168models.h new file mode 100644 index 000000000..887dffdc6 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/g168models.h @@ -0,0 +1,340 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g168models.h - line models for echo cancellation tests against the G.168 + * spec. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: g168models.h,v 1.9 2008/08/29 09:28:13 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_G168MODELS_H_) +#define _SPANDSP_G168MODELS_H_ + +/*! \page g168_test_data_page The test data from the G.168 specification +*/ + +/*! + The line model from section D.2 of G.168. + + These are the coefficients for the line simulation model defined in + section D.2 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d2_coeffs[] = +{ + -436, -829, -2797, -4208, -17968, -11215, 46150, 34480, + -10427, 9049, -1309, -6320, 390, -8191, -1751, -6051, + -3796, -4055, -3948, -2557, -3372, -1808, -2259, -1300, + -1098, -618, -340, -61, 323, 419, 745, 716, + 946, 880, 1014, 976, 1033, 1091, 1053, 1042, + 794, 831, 899, 716, 390, 313, 304, 304, + 73, -119, -109, -176, -359, -407, -512, -580, + -704, -618, -685, -791, -772, -820, -839, -724 +}; +#define LINE_MODEL_D2_GAIN 1.39E-5f + +/*! + The line model from section D.3 of G.168. + + These are the coefficients for the line simulation model defined in + section D.3 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d3_coeffs[] = +{ + -381, 658, 1730, -51, -3511, -1418, 7660, 8861, + -8106, -21370, -5307, 23064, 24020, 1020, -12374, -16296, + -19524, -7480, 13509, 17115, 13952, 13952, 97, -9326, + -9046, -15208, -9853, -3858, -1979, 6029, 5616, 7214, + 6820, 3935, 3919, 921, 1316, -693, -759, -1517, + -2176, -2028, -2654, -1814, -2077, -1468, -1221, -842, + -463, -298, -68, 64, 493, 723, 789, 954, + 756, 839, 872, 1020, 789, 822, 558, 658, + 476, 377, 377, 262, 97, -68, -183, -232, + -331, -347, -430, -314, -430, -463, -463, -414, + -381, -479, -479, -512, -479, -397, -430, -397, + -298, -265, -249, -216, -249, -265, -166, -232 +}; +#define LINE_MODEL_D3_GAIN 1.44E-5f + +/*! + The line model from section D.4 of G.168. + + These are the coefficients for the line simulation model defined in + section D.4 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d4_coeffs[] = +{ + -448, -436, 2230, 2448, -4178, -7050, 5846, 18581, + 2322, -26261, -16249, 21637, 25649, -2267, -10311, -4693, + -12690, -7428, 14164, 13467, 4438, 8627, 456, -11879, + -6352, -5104, -7496, 3271, 6566, 4277, 11131, 7562, + 1475, 3728, -3525, -7301, -3101, -9269, -6146, -2553, + -6272, 811, 124, 788, 5147, 2172, 5387, 4598, + 3535, 4004, 2311, 2150, 1017, 330, -139, -573, + -1100, -1157, -1180, -1455, -1123, -1386, -1123, -1066, + -1020, -1100, -1008, -1077, -1088, -917, -917, -963, + -814, -871, -734, -642, -562, -356, -379, -345, + -230, -233, -333, -356, -390, -310, -265, -368, + -310, -310, -390, -482, -459, -482, -551, -573 +}; +#define LINE_MODEL_D4_GAIN 1.52E-5f + +/*! + The line model from section D.5 of G.168. + + These are the coefficients for the line simulation model defined in + section D.5 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d5_coeffs[] = +{ + 160, 312, -241, -415, 897, 908, -1326, -1499, + 2405, 3347, -3624, -7733, 4041, 14484, -1477, -21739, + -4470, 25356, 11458, -19696, -11800, 5766, 789, 6633, + 14624, -6975, -17156, -187, 149, 1515, 14907, 4345, + -7128, -2757, -10185, -7083, 6850, 3944, 6969, 8694, + -4068, -3852, -5793, -9371, 453, 1060, 3965, 9463, + 2393, 2784, -892, -7366, -3376, -5847, -2399, 3011, + 1537, 6623, 4205, 1602, 1592, -4752, -3646, -5207, + -5577, -501, -1174, 4041, 5647, 4628, 7252, 2123, + 2654, -881, -4113, -3244, -7289, -3830, -4600, -2508, + 431, -144, 4184, 2372, 4617, 3576, 2382, 2839, + -404, 539, -1803, -1401, -1705, -2269, -783, -1608, + -220, -306, 257, 615, 225, 561, 8, 344, + 127, -57, 182, 41, 203, -111, 95, -79, + 30, 84, -13, -68, -241, -68, -24, 19, + -57, -24, 30, -68, 84, -155, -68, 19 +}; +#define LINE_MODEL_D5_GAIN 1.77E-5f + +/*! + The line model from section D.6 of G.168. + + These are the coefficients for the line simulation model defined in + section D.6 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d6_coeffs[] = +{ + 293, 268, 475, 460, 517, 704, 581, 879, + 573, 896, 604, 787, 561, 538, 440, 97, + 265, -385, 20, -938, -523, -1438, -1134, -1887, + -1727, -1698, -4266, -22548, -43424, 2743, 25897, 7380, + 21499, 11983, 10400, 11667, 3889, 7241, 925, 2018, + -821, -2068, -2236, -4283, -3406, -5022, -4039, -4842, + -4104, -4089, -3582, -2978, -2734, -1805, -1608, -645, + -495, 279, 471, 947, 1186, 1438, 1669, 1640, + 1901, 1687, 1803, 1543, 1566, 1342, 1163, 963, + 733, 665, 323, 221, -14, -107, -279, -379, + -468, -513, -473, -588, -612, -652, -616, -566, + -515, -485, -404, -344, -290, -202, -180, -123 +}; +#define LINE_MODEL_D6_GAIN 9.33E-6f + +/*! + The line model from section D.7 of G.168. + + These are the coefficients for the line simulation model defined in + section D.8 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d7_coeffs[] = +{ + 29, 109, -83, 198, -294, -135, -415, -202, + -444, -337, -313, -450, -105, -503, 145, -490, + 267, -231, 340, 77, 343, 783, 158, 1341, + 195, 1798, 344, 1845, 629, 1604, 1182, 940, + 5163, 19522, 8421, -50953, -9043, 18046, -13553, 13336, + -3471, -107, 1788, -7409, 2469, -7994, 490, -3860, + -837, 490, -636, 3682, 1141, 5019, 2635, 5025, + 3946, 4414, 4026, 3005, 3380, 1616, 2007, 158, + 388, -1198, -1117, -2134, -2547, -2589, -3310, -2778, + -3427, -2779, -3116, -2502, -2399, -1956, -1539, -1239, + -570, -377, 251, 331, 964, 1177, 1449, 1564, + 1724, 1871, 1767, 1802, 1630, 1632, 1379, 1271, + 1063, 856, 711, 482, 289, 54, -137, -321, + -490, -638, -764, -836, -800, -859, -838, -837, + -834, -740, -673, -581, -493, -436, -327, -201 +}; +#define LINE_MODEL_D7_GAIN 1.51E-5f + +/*! + The line model from section D.8 of G.168. + + These are the coefficients for the line simulation model defined in + section D.8 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d8_coeffs[] = +{ + 258, -111, 337, -319, 347, -434, 192, -450, + -108, -343, -596, -177, -1187, -52, -1781, -147, + -1959, -326, -1601, -1389, -13620, -720, 33818, -10683, + -6742, 12489, -9862, 8950, -1574, 758, 3526, -3118, + 2421, -8966, -4901, 11385, 18072, -14410, -7473, 19836, + -16854, -3115, 9483, -17799, 7399, -4342, -7415, 7929, + -10726, 6239, -2526, -1317, 5345, -4565, 6868, -2195, + 3425, 1969, -109, 3963, -1275, 3087, -892, 1239, + 2, -427, 596, -1184, 551, -1244, 141, -743, + -415, -372, -769, -183, -785, -270, -659, -377, + -523, -325, -245, -255, -60, 35, 218, 149, + 340, 233, 365, 303, 251, 230, 209, 179 +}; +#define LINE_MODEL_D8_GAIN 2.33E-5f + +/*! + The line model from section D.9 of G.168. + + These are the coefficients for the line simulation model defined in + section D.9 of G.168. It may be used with the fir32_xxx + routines to build a line simulator. +*/ +const int32_t line_model_d9_coeffs[] = +{ + 80, 31, 4, 42, 42, -61, -81, -64, + 121, -102, -26, 1002, -9250, -22562, 39321, 35681, + -35289, 25312, -1457, -229, 15659, -6786, 16791, 3860, + 2239, -28730, -11885, 33871, -176, -16421, 18173, -9669, + -10163, 9941, -19365, 3592, -5907, -10257, 5336, -12933, + 4348, -4802, -1791, 3035, -4433, 5553, -2596, 3992, + 1255, 1450, 4079, 324, 4340, 1059, 3083, 1917, + 1756, 2478, 1027, 1871, 845, 1284, 813, 806, + 869, 471, 646, 438, 449, 432, 473, 394, + 452, 538, 717, 723, 850, 756, 753, 899, + 555, 669, 619, 500, 650, 615, 516, 492, + 427, 291, 356, 147, 107, -50, -88, -59, + -238, -165, -183 +}; +#define LINE_MODEL_D9_GAIN 1.33E-5f + +/*! + The filter coefficients for the bandpass filter specified for level measurements + in section 6.4.1.2.1 of G.168. +*/ +const float level_measurement_bp_coeffs[] = +{ + 0.0000, 0.0006, 0.0005, 0.0004, 0.0011, + 0.0000, 0.0015, -0.0003, 0.0012, -0.0002, + 0.0000, 0.0002, -0.0020, 0.0005, -0.0040, + 0.0000, -0.0047, -0.0019, -0.0033, -0.0047, + 0.0000, -0.0068, 0.0036, -0.0057, 0.0054, + 0.0000, 0.0044, 0.0095, 0.0017, 0.0188, + 0.0000, 0.0225, 0.0024, 0.0163, 0.0092, + 0.0000, 0.0164, -0.0210, 0.0161, -0.0375, + 0.0000, -0.0406, -0.0357, -0.0267, -0.0871, + 0.0000, -0.1420, 0.0289, -0.1843, 0.0475, + 0.8006, 0.0475, -0.1843, 0.0289, -0.1420, + 0.0000, -0.0871, -0.0267, -0.0357, -0.0406, + 0.0000, -0.0375, 0.0161, -0.0210, 0.0164, + 0.0000, 0.0092, 0.0163, 0.0024, 0.0225, + 0.0000, 0.0188, 0.0017, 0.0095, 0.0044, + 0.0000, 0.0054, -0.0057, 0.0036, -0.0068, + 0.0000, -0.0047, -0.0033, -0.0019, -0.0047, + 0.0000, -0.0040, 0.0005, -0.0020, 0.0002, + 0.0000, -0.0002, 0.0012, -0.0003, 0.0015, + 0.0000, 0.0011, 0.0004, 0.0005, 0.0006, + 0.0000 +}; + +/*! + The composite source signal "voiced" section from section C.1 of G.168. +*/ +const int css_c1[] = +{ + -155, 276, 517, 578, 491, 302, 86, -103, + -207, -198, 60, 190, 543, 948, 1362, 1741, + 2043, 2276, 2422, 2500, 2552, 2595, 2655, 2758, + 2896, 3060, 3224, 3370, 3500, 3569, 3603, 3603, + 3595, 3586, 3595, 3638, 3724, 3819, 3922, 4000, + 4043, 4034, 3974, 3862, 3724, 3577, 3439, 3336, + 3267, 3224, 3198, 3172, 3129, 3043, 2914, 2750, + 2560, 2353, 2155, 1991, 1853, 1750, 1672, 1603, + 1534, 1440, 1310, 1146, 965, 776, 603, 448, + 345, 276, 250, 250, 267, 267, 241, 190, + 103, -9, -138, -267, -388, -491, -569, -638, + -698, -759, -813, -888, -957, -1034, -1103, -1146, + -1181, -1190, -1198, -1215, -1259, -1327, -1457, -1629, + -1853, -2121, -2414, -2707, -3017, -3319, -3612, -3913, + -4224, -4560, -4922, -5301, -5715, -6137, -6560, -6948, + -7301, -7568, -7732, -7758, -7620, -7310, -6810, -6155, + -5344, -4439, -3474, -2508, -1595, -802 +}; + +/*! + The composite source signal "voiced" section from section C.3 of G.168. +*/ +const int css_c3[] = +{ + -198, -112, -9, 103, 233, 388, 543, 724, + 896, 1060, 1233, 1388, 1517, 1638, 1747, 1810, + 1845, 1845, 1802, 1707, 1569, 1379, 1146, 871, + 560, 233, -121, -491, -871, -1250, -1638, -2043, + -2465, -2896, -3345, -3819, -4310, -4810, -5319, -5836, + -6353, -6853, -7353, -7836, -8292, -8715, -9077, -9370, + -9542, -9542, -9361, -8956, -8327, -7465, -6396, -5163, + -3827, -2448, -1103, 155, 1293, 2241, 3034, 3655, + 4138, 4517, 4827, 5094, 5344, 5594, 5827, 6043, + 6215, 6344, 6413, 6422, 6379, 6310, 6215, 6120, + 6051, 6000, 5991, 5991, 6000, 6008, 5991, 5939, + 5853, 5715, 5560, 5387, 5215, 5043, 4879, 4732, + 4586, 4439, 4276, 4086, 3870, 3629, 3370, 3086, + 2801, 2534, 2267, 2034, 1819, 1612, 1422, 1224, + 1026, 819, 603, 388, 181, 9, -181, -328, + -448, -543, -629, -707, -784, -871, -948, -1026, + -1112, -1181, -1241, -1276, -1293, -1302, -1293, -1267, + -1250, -1233, -1224, -1224, -1224, -1224, -1215, -1198, + -1172, -1129, -1077, -1026, -974, -922, -888, -871, + -845, -828, -810, -793, -767, -741, -698, -672, + -638, -603, -595, -586, -595, -603, -621, -629, + -938, -638, -638, -638, -638, -638, -647, -664, + -690, -724, -767, -793, -819, -845, -853, -871, + -879, -888, -896, -922, -948, -974, -1009, -1026, + -1052, -1069, -1077, -1069, -1060, -1060, -1052, -1043, + -1043, -1052, -1060, -1060, -1060, -1052, -1034, -1017, + -991, -957, -931, -905, -888, -862, -845, -819, + -793, -767, -724, -672, -621, -560, -509, -457, + -397, -345, -276, -207, -112 +}; + +/*! + From section 6.4.2.7 of G.168 - Test No. 6 Non-divergence on narrow-band signals. + These tones and tone pairs are each applied for 5 seconds. +*/ +const int tones_6_4_2_7[][2] = +{ + { 697, 0}, + { 941, 0}, + {1336, 0}, + {1633, 0}, + { 697, 1209}, + { 770, 1336}, + { 852, 1477}, + { 941, 1633}, + { 0, 0} +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/g711.h b/Libraries/spandsp/spandsp/spandsp/g711.h new file mode 100644 index 000000000..274c61d62 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/g711.h @@ -0,0 +1,327 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g711.h - In line A-law and u-law conversion routines + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: g711.h,v 1.19 2009/04/12 09:12:10 steveu Exp $ + */ + +/*! \file */ + +/*! \page g711_page A-law and mu-law handling +Lookup tables for A-law and u-law look attractive, until you consider the impact +on the CPU cache. If it causes a substantial area of your processor cache to get +hit too often, cache sloshing will severely slow things down. The main reason +these routines are slow in C, is the lack of direct access to the CPU's "find +the first 1" instruction. A little in-line assembler fixes that, and the +conversion routines can be faster than lookup tables, in most real world usage. +A "find the first 1" instruction is available on most modern CPUs, and is a +much underused feature. + +If an assembly language method of bit searching is not available, these routines +revert to a method that can be a little slow, so the cache thrashing might not +seem so bad :( + +Feel free to submit patches to add fast "find the first 1" support for your own +favourite processor. + +Look up tables are used for transcoding between A-law and u-law, since it is +difficult to achieve the precise transcoding procedure laid down in the G.711 +specification by other means. +*/ + +#if !defined(_SPANDSP_G711_H_) +#define _SPANDSP_G711_H_ + +/* The usual values to use on idle channels, to emulate silence */ +/*! Idle value for A-law channels */ +#define G711_ALAW_IDLE_OCTET 0x5D +/*! Idle value for u-law channels */ +#define G711_ULAW_IDLE_OCTET 0xFF + +enum +{ + G711_ALAW = 0, + G711_ULAW +}; + +/*! + G.711 state + */ +typedef struct g711_state_s g711_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* N.B. It is tempting to use look-up tables for A-law and u-law conversion. + * However, you should consider the cache footprint. + * + * A 64K byte table for linear to x-law and a 512 byte table for x-law to + * linear sound like peanuts these days, and shouldn't an array lookup be + * real fast? No! When the cache sloshes as badly as this one will, a tight + * calculation may be better. The messiest part is normally finding the + * segment, but a little inline assembly can fix that on an i386, x86_64 and + * many other modern processors. + */ + +/* + * Mu-law is basically as follows: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +/* Enable the trap as per the MIL-STD */ +//#define ULAW_ZEROTRAP +/*! Bias for u-law encoding from linear. */ +#define ULAW_BIAS 0x84 + +/*! \brief Encode a linear sample to u-law + \param linear The sample to encode. + \return The u-law value. +*/ +static __inline__ uint8_t linear_to_ulaw(int linear) +{ + uint8_t u_val; + int mask; + int seg; + + /* Get the sign and the magnitude of the value. */ + if (linear >= 0) + { + linear = ULAW_BIAS + linear; + mask = 0xFF; + } + else + { + linear = ULAW_BIAS - linear; + mask = 0x7F; + } + + seg = top_bit(linear | 0xFF) - 7; + + /* + * Combine the sign, segment, quantization bits, + * and complement the code word. + */ + if (seg >= 8) + u_val = (uint8_t) (0x7F ^ mask); + else + u_val = (uint8_t) (((seg << 4) | ((linear >> (seg + 3)) & 0xF)) ^ mask); +#ifdef ULAW_ZEROTRAP + /* Optional ITU trap */ + if (u_val == 0) + u_val = 0x02; +#endif + return u_val; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Decode an u-law sample to a linear value. + \param ulaw The u-law sample to decode. + \return The linear value. +*/ +static __inline__ int16_t ulaw_to_linear(uint8_t ulaw) +{ + int t; + + /* Complement to obtain normal u-law value. */ + ulaw = ~ulaw; + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = (((ulaw & 0x0F) << 3) + ULAW_BIAS) << (((int) ulaw & 0x70) >> 4); + return (int16_t) ((ulaw & 0x80) ? (ULAW_BIAS - t) : (t - ULAW_BIAS)); +} +/*- End of function --------------------------------------------------------*/ + +/* + * A-law is basically as follows: + * + * Linear Input Code Compressed Code + * ----------------- --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +/*! The A-law alternate mark inversion mask */ +#define ALAW_AMI_MASK 0x55 + +/*! \brief Encode a linear sample to A-law + \param linear The sample to encode. + \return The A-law value. +*/ +static __inline__ uint8_t linear_to_alaw(int linear) +{ + int mask; + int seg; + + if (linear >= 0) + { + /* Sign (bit 7) bit = 1 */ + mask = ALAW_AMI_MASK | 0x80; + } + else + { + /* Sign (bit 7) bit = 0 */ + mask = ALAW_AMI_MASK; + linear = -linear - 1; + } + + /* Convert the scaled magnitude to segment number. */ + seg = top_bit(linear | 0xFF) - 7; + if (seg >= 8) + { + if (linear >= 0) + { + /* Out of range. Return maximum value. */ + return (uint8_t) (0x7F ^ mask); + } + /* We must be just a tiny step below zero */ + return (uint8_t) (0x00 ^ mask); + } + /* Combine the sign, segment, and quantization bits. */ + return (uint8_t) (((seg << 4) | ((linear >> ((seg) ? (seg + 3) : 4)) & 0x0F)) ^ mask); +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Decode an A-law sample to a linear value. + \param alaw The A-law sample to decode. + \return The linear value. +*/ +static __inline__ int16_t alaw_to_linear(uint8_t alaw) +{ + int i; + int seg; + + alaw ^= ALAW_AMI_MASK; + i = ((alaw & 0x0F) << 4); + seg = (((int) alaw & 0x70) >> 4); + if (seg) + i = (i + 0x108) << (seg - 1); + else + i += 8; + return (int16_t) ((alaw & 0x80) ? i : -i); +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Transcode from A-law to u-law, using the procedure defined in G.711. + \param alaw The A-law sample to transcode. + \return The best matching u-law value. +*/ +SPAN_DECLARE(uint8_t) alaw_to_ulaw(uint8_t alaw); + +/*! \brief Transcode from u-law to A-law, using the procedure defined in G.711. + \param ulaw The u-law sample to transcode. + \return The best matching A-law value. +*/ +SPAN_DECLARE(uint8_t) ulaw_to_alaw(uint8_t ulaw); + +/*! \brief Decode from u-law or A-law to linear. + \param s The G.711 context. + \param amp The linear audio buffer. + \param g711_data The G.711 data. + \param g711_bytes The number of G.711 samples to decode. + \return The number of samples of linear audio produced. +*/ +SPAN_DECLARE(int) g711_decode(g711_state_t *s, + int16_t amp[], + const uint8_t g711_data[], + int g711_bytes); + +/*! \brief Encode from linear to u-law or A-law. + \param s The G.711 context. + \param g711_data The G.711 data. + \param amp The linear audio buffer. + \param len The number of samples to encode. + \return The number of G.711 samples produced. +*/ +SPAN_DECLARE(int) g711_encode(g711_state_t *s, + uint8_t g711_data[], + const int16_t amp[], + int len); + +/*! \brief Transcode between u-law and A-law. + \param s The G.711 context. + \param g711_out The resulting G.711 data. + \param g711_in The original G.711 data. + \param g711_bytes The number of G.711 samples to transcode. + \return The number of G.711 samples produced. +*/ +SPAN_DECLARE(int) g711_transcode(g711_state_t *s, + uint8_t g711_out[], + const uint8_t g711_in[], + int g711_bytes); + +/*! Initialise a G.711 encode or decode context. + \param s The G.711 context. + \param mode The G.711 mode. + \return A pointer to the G.711 context, or NULL for error. */ +SPAN_DECLARE(g711_state_t *) g711_init(g711_state_t *s, int mode); + +/*! Release a G.711 encode or decode context. + \param s The G.711 context. + \return 0 for OK. */ +SPAN_DECLARE(int) g711_release(g711_state_t *s); + +/*! Free a G.711 encode or decode context. + \param s The G.711 context. + \return 0 for OK. */ +SPAN_DECLARE(int) g711_free(g711_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/g722.h b/Libraries/spandsp/spandsp/spandsp/g722.h new file mode 100644 index 000000000..66e5d79b7 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/g722.h @@ -0,0 +1,130 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g722.h - The ITU G.722 codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on a single channel G.722 codec which is: + * + ***** Copyright (c) CMU 1993 ***** + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722.h,v 1.26 2009/04/12 09:12:10 steveu Exp $ + */ + + +/*! \file */ + +#if !defined(_SPANDSP_G722_H_) +#define _SPANDSP_G722_H_ + +/*! \page g722_page G.722 encoding and decoding +\section g722_page_sec_1 What does it do? +The G.722 module is a bit exact implementation of the ITU G.722 specification for all three +specified bit rates - 64000bps, 56000bps and 48000bps. It passes the ITU tests. + +To allow fast and flexible interworking with narrow band telephony, the encoder and decoder +support an option for the linear audio to be an 8k samples/second stream. In this mode the +codec is considerably faster, and still fully compatible with wideband terminals using G.722. + +\section g722_page_sec_2 How does it work? +???. +*/ + +enum +{ + G722_SAMPLE_RATE_8000 = 0x0001, + G722_PACKED = 0x0002 +}; + +/*! + G.722 encode state + */ +typedef struct g722_encode_state_s g722_encode_state_t; + +/*! + G.722 decode state + */ +typedef struct g722_decode_state_s g722_decode_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an G.722 encode context. + \param s The G.722 encode context. + \param rate The required bit rate for the G.722 data. + The valid rates are 64000, 56000 and 48000. + \param options + \return A pointer to the G.722 encode context, or NULL for error. */ +SPAN_DECLARE(g722_encode_state_t *) g722_encode_init(g722_encode_state_t *s, int rate, int options); + +/*! Release a G.722 encode context. + \param s The G.722 encode context. + \return 0 for OK. */ +SPAN_DECLARE(int) g722_encode_release(g722_encode_state_t *s); + +/*! Free a G.722 encode context. + \param s The G.722 encode context. + \return 0 for OK. */ +SPAN_DECLARE(int) g722_encode_free(g722_encode_state_t *s); + +/*! Encode a buffer of linear PCM data to G.722 + \param s The G.722 context. + \param g722_data The G.722 data produced. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of bytes of G.722 data produced. */ +SPAN_DECLARE(int) g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len); + +/*! Initialise an G.722 decode context. + \param s The G.722 decode context. + \param rate The bit rate of the G.722 data. + The valid rates are 64000, 56000 and 48000. + \param options + \return A pointer to the G.722 decode context, or NULL for error. */ +SPAN_DECLARE(g722_decode_state_t *) g722_decode_init(g722_decode_state_t *s, int rate, int options); + +/*! Release a G.722 decode context. + \param s The G.722 decode context. + \return 0 for OK. */ +SPAN_DECLARE(int) g722_decode_release(g722_decode_state_t *s); + +/*! Free a G.722 decode context. + \param s The G.722 decode context. + \return 0 for OK. */ +SPAN_DECLARE(int) g722_decode_free(g722_decode_state_t *s); + +/*! Decode a buffer of G.722 data to linear PCM. + \param s The G.722 context. + \param amp The audio sample buffer. + \param g722_data + \param len + \return The number of samples returned. */ +SPAN_DECLARE(int) g722_decode(g722_decode_state_t *s, int16_t amp[], const uint8_t g722_data[], int len); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/Libraries/spandsp/spandsp/spandsp/g726.h b/Libraries/spandsp/spandsp/spandsp/g726.h new file mode 100644 index 000000000..f585b0e86 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/g726.h @@ -0,0 +1,122 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g726.h - ITU G.726 codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: g726.h,v 1.26 2009/04/12 09:12:10 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_G726_H_) +#define _SPANDSP_G726_H_ + +/*! \page g726_page G.726 encoding and decoding +\section g726_page_sec_1 What does it do? + +The G.726 module is a bit exact implementation of the full ITU G.726 specification. +It supports: + - 16 kbps, 24kbps, 32kbps, and 40kbps operation. + - Tandem adjustment, for interworking with A-law and u-law. + - Annex A support, for use in environments not using A-law or u-law. + +It passes the ITU tests. + +\section g726_page_sec_2 How does it work? +???. +*/ + +enum +{ + G726_ENCODING_LINEAR = 0, /* Interworking with 16 bit signed linear */ + G726_ENCODING_ULAW, /* Interworking with u-law */ + G726_ENCODING_ALAW /* Interworking with A-law */ +}; + +enum +{ + G726_PACKING_NONE = 0, + G726_PACKING_LEFT = 1, + G726_PACKING_RIGHT = 2 +}; + +/*! + G.726 state + */ +typedef struct g726_state_s g726_state_t; + +typedef int16_t (*g726_decoder_func_t)(g726_state_t *s, uint8_t code); + +typedef uint8_t (*g726_encoder_func_t)(g726_state_t *s, int16_t amp); + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a G.726 encode or decode context. + \param s The G.726 context. + \param bit_rate The required bit rate for the ADPCM data. + The valid rates are 16000, 24000, 32000 and 40000. + \param ext_coding The coding used outside G.726. + \param packing One of the G.726_PACKING_xxx options. + \return A pointer to the G.726 context, or NULL for error. */ +SPAN_DECLARE(g726_state_t *) g726_init(g726_state_t *s, int bit_rate, int ext_coding, int packing); + +/*! Release a G.726 encode or decode context. + \param s The G.726 context. + \return 0 for OK. */ +SPAN_DECLARE(int) g726_release(g726_state_t *s); + +/*! Free a G.726 encode or decode context. + \param s The G.726 context. + \return 0 for OK. */ +SPAN_DECLARE(int) g726_free(g726_state_t *s); + +/*! Decode a buffer of G.726 ADPCM data to linear PCM, a-law or u-law. + \param s The G.726 context. + \param amp The audio sample buffer. + \param g726_data + \param g726_bytes + \return The number of samples returned. */ +SPAN_DECLARE(int) g726_decode(g726_state_t *s, + int16_t amp[], + const uint8_t g726_data[], + int g726_bytes); + +/*! Encode a buffer of linear PCM data to G.726 ADPCM. + \param s The G.726 context. + \param g726_data The G.726 data produced. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of bytes of G.726 data produced. */ +SPAN_DECLARE(int) g726_encode(g726_state_t *s, + uint8_t g726_data[], + const int16_t amp[], + int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/gsm0610.h b/Libraries/spandsp/spandsp/spandsp/gsm0610.h new file mode 100644 index 000000000..b31b7db3e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/gsm0610.h @@ -0,0 +1,153 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * gsm0610.h - GSM 06.10 full rate speech codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: gsm0610.h,v 1.21 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_GSM0610_H_) +#define _SPANDSP_GSM0610_H_ + +/*! \page gsm0610_page GSM 06.10 encoding and decoding +\section gsm0610_page_sec_1 What does it do? + +The GSM 06.10 module is an version of the widely used GSM FR codec software +available from http://kbs.cs.tu-berlin.de/~jutta/toast.html. This version +was produced since some versions of this codec are not bit exact, or not +very efficient on modern processors. This implementation can use MMX instructions +on Pentium class processors, or alternative methods on other processors. It +passes all the ETSI test vectors. That is, it is a tested bit exact implementation. + +This implementation supports encoded data in one of three packing formats: + - Unpacked, with the 76 parameters of a GSM 06.10 code frame each occupying a + separate byte. (note that none of the parameters exceed 8 bits). + - Packed the the 33 byte per frame, used for VoIP, where 4 bits per frame are wasted. + - Packed in WAV49 format, where 2 frames are packed into 65 bytes. + +\section gsm0610_page_sec_2 How does it work? +???. +*/ + +enum +{ + GSM0610_PACKING_NONE, + GSM0610_PACKING_WAV49, + GSM0610_PACKING_VOIP +}; + +/*! + GSM 06.10 FR codec unpacked frame. +*/ +typedef struct +{ + int16_t LARc[8]; + int16_t Nc[4]; + int16_t bc[4]; + int16_t Mc[4]; + int16_t xmaxc[4]; + int16_t xMc[4][13]; +} gsm0610_frame_t; + +/*! + GSM 06.10 FR codec state descriptor. This defines the state of + a single working instance of the GSM 06.10 FR encoder or decoder. +*/ +typedef struct gsm0610_state_s gsm0610_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a GSM 06.10 encode or decode context. + \param s The GSM 06.10 context + \param packing One of the GSM0610_PACKING_xxx options. + \return A pointer to the GSM 06.10 context, or NULL for error. */ +SPAN_DECLARE(gsm0610_state_t *) gsm0610_init(gsm0610_state_t *s, int packing); + +/*! Release a GSM 06.10 encode or decode context. + \param s The GSM 06.10 context + \return 0 for success, else -1. */ +SPAN_DECLARE(int) gsm0610_release(gsm0610_state_t *s); + +/*! Free a GSM 06.10 encode or decode context. + \param s The GSM 06.10 context + \return 0 for success, else -1. */ +SPAN_DECLARE(int) gsm0610_free(gsm0610_state_t *s); + +/*! Set the packing format for a GSM 06.10 encode or decode context. + \param s The GSM 06.10 context + \param packing One of the GSM0610_PACKING_xxx options. + \return 0 for success, else -1. */ +SPAN_DECLARE(int) gsm0610_set_packing(gsm0610_state_t *s, int packing); + +/*! Encode a buffer of linear PCM data to GSM 06.10. + \param s The GSM 06.10 context. + \param code The GSM 06.10 data produced. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of bytes of GSM 06.10 data produced. */ +SPAN_DECLARE(int) gsm0610_encode(gsm0610_state_t *s, uint8_t code[], const int16_t amp[], int len); + +/*! Decode a buffer of GSM 06.10 data to linear PCM. + \param s The GSM 06.10 context. + \param amp The audio sample buffer. + \param code The GSM 06.10 data. + \param len The number of bytes of GSM 06.10 data to be decoded. + \return The number of samples returned. */ +SPAN_DECLARE(int) gsm0610_decode(gsm0610_state_t *s, int16_t amp[], const uint8_t code[], int len); + +SPAN_DECLARE(int) gsm0610_pack_none(uint8_t c[], const gsm0610_frame_t *s); + +/*! Pack a pair of GSM 06.10 frames in the format used for wave files (wave type 49). + \param c The buffer for the packed data. This must be at least 65 bytes long. + \param s A pointer to the frames to be packed. + \return The number of bytes generated. */ +SPAN_DECLARE(int) gsm0610_pack_wav49(uint8_t c[], const gsm0610_frame_t *s); + +/*! Pack a GSM 06.10 frames in the format used for VoIP. + \param c The buffer for the packed data. This must be at least 33 bytes long. + \param s A pointer to the frame to be packed. + \return The number of bytes generated. */ +SPAN_DECLARE(int) gsm0610_pack_voip(uint8_t c[], const gsm0610_frame_t *s); + +SPAN_DECLARE(int) gsm0610_unpack_none(gsm0610_frame_t *s, const uint8_t c[]); + +/*! Unpack a pair of GSM 06.10 frames from the format used for wave files (wave type 49). + \param s A pointer to a buffer into which the frames will be packed. + \param c The buffer containing the data to be unpacked. This must be at least 65 bytes long. + \return The number of bytes absorbed. */ +SPAN_DECLARE(int) gsm0610_unpack_wav49(gsm0610_frame_t *s, const uint8_t c[]); + +/*! Unpack a GSM 06.10 frame from the format used for VoIP. + \param s A pointer to a buffer into which the frame will be packed. + \param c The buffer containing the data to be unpacked. This must be at least 33 bytes long. + \return The number of bytes absorbed. */ +SPAN_DECLARE(int) gsm0610_unpack_voip(gsm0610_frame_t *s, const uint8_t c[]); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of include ---------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/hdlc.h b/Libraries/spandsp/spandsp/spandsp/hdlc.h new file mode 100644 index 000000000..9ff9fb107 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/hdlc.h @@ -0,0 +1,252 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * hdlc.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: hdlc.h,v 1.45 2009/06/02 16:03:56 steveu Exp $ + */ + +/*! \file */ + +/*! \page hdlc_page HDLC + +\section hdlc_page_sec_1 What does it do? +The HDLC module provides bit stuffing, destuffing, framing and deframing, +according to the HDLC protocol. It also provides 16 and 32 bit CRC generation +and checking services for HDLC frames. + +HDLC may not be a DSP function, but is needed to accompany several DSP components. + +\section hdlc_page_sec_2 How does it work? +*/ + +#if !defined(_SPANDSP_HDLC_H_) +#define _SPANDSP_HDLC_H_ + +/*! + HDLC_MAXFRAME_LEN is the maximum length of a stuffed HDLC frame, excluding the CRC. +*/ +#define HDLC_MAXFRAME_LEN 400 + +typedef void (*hdlc_frame_handler_t)(void *user_data, const uint8_t *pkt, int len, int ok); +typedef void (*hdlc_underflow_handler_t)(void *user_data); + +/*! + HDLC receive descriptor. This contains all the state information for an HDLC receiver. + */ +typedef struct hdlc_rx_state_s hdlc_rx_state_t; + +/*! + HDLC received data statistics. + */ +typedef struct +{ + /*! \brief The number of bytes of good frames received (CRC not included). */ + unsigned long int bytes; + /*! \brief The number of good frames received. */ + unsigned long int good_frames; + /*! \brief The number of frames with CRC errors received. */ + unsigned long int crc_errors; + /*! \brief The number of too short and too long frames received. */ + unsigned long int length_errors; + /*! \brief The number of HDLC aborts received. */ + unsigned long int aborts; +} hdlc_rx_stats_t; + +/*! + HDLC transmit descriptor. This contains all the state information for an + HDLC transmitter. + */ +typedef struct hdlc_tx_state_s hdlc_tx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Initialise an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \param crc32 TRUE to use ITU CRC32. FALSE to use ITU CRC16. + \param report_bad_frames TRUE to request the reporting of bad frames. + \param framing_ok_threshold The number of back-to-back flags needed to + start the framing OK condition. This may be used where a series of + flag octets is used as a preamble, such as in the T.30 protocol. + \param handler The function to be called when a good HDLC frame is received. + \param user_data An opaque parameter for the callback routine. + \return A pointer to the HDLC receiver context. +*/ +SPAN_DECLARE(hdlc_rx_state_t *) hdlc_rx_init(hdlc_rx_state_t *s, + int crc32, + int report_bad_frames, + int framing_ok_threshold, + hdlc_frame_handler_t handler, + void *user_data); + +/*! Change the put_bit function associated with an HDLC receiver context. + \brief Change the put_bit function associated with an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \param handler The function to be called when a good HDLC frame is received. + \param user_data An opaque parameter for the callback routine. +*/ +SPAN_DECLARE(void) hdlc_rx_set_frame_handler(hdlc_rx_state_t *s, hdlc_frame_handler_t handler, void *user_data); + +/*! Change the status report function associated with an HDLC receiver context. + \brief Change the status report function associated with an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \param handler The callback routine used to report status changes. + \param user_data An opaque parameter for the callback routine. +*/ +SPAN_DECLARE(void) hdlc_rx_set_status_handler(hdlc_rx_state_t *s, modem_rx_status_func_t handler, void *user_data); + +/*! Release an HDLC receiver context. + \brief Release an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \return 0 for OK */ +SPAN_DECLARE(int) hdlc_rx_release(hdlc_rx_state_t *s); + +/*! Free an HDLC receiver context. + \brief Free an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \return 0 for OK */ +SPAN_DECLARE(int) hdlc_rx_free(hdlc_rx_state_t *s); + +/*! \brief Set the maximum frame length for an HDLC receiver context. + \param s A pointer to an HDLC receiver context. + \param max_len The maximum permitted length of a frame. +*/ +SPAN_DECLARE(void) hdlc_rx_set_max_frame_len(hdlc_rx_state_t *s, size_t max_len); + +/*! \brief Set the octet counting report interval. + \param s A pointer to an HDLC receiver context. + \param interval The interval, in octets. +*/ +SPAN_DECLARE(void) hdlc_rx_set_octet_counting_report_interval(hdlc_rx_state_t *s, + int interval); + +/*! \brief Get the current receive statistics. + \param s A pointer to an HDLC receiver context. + \param t A pointer to the buffer for the statistics. + \return 0 for OK, else -1. +*/ +SPAN_DECLARE(int) hdlc_rx_get_stats(hdlc_rx_state_t *s, + hdlc_rx_stats_t *t); + +/*! \brief Put a single bit of data to an HDLC receiver. + \param s A pointer to an HDLC receiver context. + \param new_bit The bit. +*/ +SPAN_DECLARE_NONSTD(void) hdlc_rx_put_bit(hdlc_rx_state_t *s, int new_bit); + +/*! \brief Put a byte of data to an HDLC receiver. + \param s A pointer to an HDLC receiver context. + \param new_byte The byte of data. +*/ +SPAN_DECLARE_NONSTD(void) hdlc_rx_put_byte(hdlc_rx_state_t *s, int new_byte); + +/*! \brief Put a series of bytes of data to an HDLC receiver. + \param s A pointer to an HDLC receiver context. + \param buf The buffer of data. + \param len The length of the data in the buffer. +*/ +SPAN_DECLARE_NONSTD(void) hdlc_rx_put(hdlc_rx_state_t *s, const uint8_t buf[], int len); + +/*! \brief Initialise an HDLC transmitter context. + \param s A pointer to an HDLC transmitter context. + \param crc32 TRUE to use ITU CRC32. FALSE to use ITU CRC16. + \param inter_frame_flags The minimum flag octets to insert between frames (usually one). + \param progressive TRUE if frame creation works in progressive mode. + \param handler The callback function called when the HDLC transmitter underflows. + \param user_data An opaque parameter for the callback routine. + \return A pointer to the HDLC transmitter context. +*/ +SPAN_DECLARE(hdlc_tx_state_t *) hdlc_tx_init(hdlc_tx_state_t *s, + int crc32, + int inter_frame_flags, + int progressive, + hdlc_underflow_handler_t handler, + void *user_data); + +SPAN_DECLARE(int) hdlc_tx_release(hdlc_tx_state_t *s); + +SPAN_DECLARE(int) hdlc_tx_free(hdlc_tx_state_t *s); + +/*! \brief Set the maximum frame length for an HDLC transmitter context. + \param s A pointer to an HDLC transmitter context. + \param max_len The maximum length. +*/ +SPAN_DECLARE(void) hdlc_tx_set_max_frame_len(hdlc_tx_state_t *s, size_t max_len); + +/*! \brief Transmit a frame. + \param s A pointer to an HDLC transmitter context. + \param frame A pointer to the frame to be transmitted. + \param len The length of the frame to be transmitted. + \return 0 if the frame was accepted for transmission, else -1. +*/ +SPAN_DECLARE(int) hdlc_tx_frame(hdlc_tx_state_t *s, const uint8_t *frame, size_t len); + +/*! \brief Corrupt the frame currently being transmitted, by giving it the wrong CRC. + \param s A pointer to an HDLC transmitter context. + \return 0 if the frame was corrupted, else -1. +*/ +SPAN_DECLARE(int) hdlc_tx_corrupt_frame(hdlc_tx_state_t *s); + +/*! \brief Transmit a specified quantity of flag octets, typically as a preamble. + \param s A pointer to an HDLC transmitter context. + \param len The length of the required period of flags, in flag octets. If len is zero this + requests that HDLC transmission be terminated when the buffers have fully + drained. + \return 0 if the flags were accepted for transmission, else -1. +*/ +SPAN_DECLARE(int) hdlc_tx_flags(hdlc_tx_state_t *s, int len); + +/*! \brief Send an abort. + \param s A pointer to an HDLC transmitter context. + \return 0 if the frame was aborted, else -1. +*/ +SPAN_DECLARE(int) hdlc_tx_abort(hdlc_tx_state_t *s); + +/*! \brief Get the next bit for transmission. + \param s A pointer to an HDLC transmitter context. + \return The next bit for transmission. +*/ +SPAN_DECLARE_NONSTD(int) hdlc_tx_get_bit(hdlc_tx_state_t *s); + +/*! \brief Get the next byte for transmission. + \param s A pointer to an HDLC transmitter context. + \return The next byte for transmission. +*/ +SPAN_DECLARE_NONSTD(int) hdlc_tx_get_byte(hdlc_tx_state_t *s); + +/*! \brief Get the next sequence of bytes for transmission. + \param s A pointer to an HDLC transmitter context. + \param buf The buffer for the data. + \param max_len The number of bytes to get. + \return The number of bytes actually got. +*/ +SPAN_DECLARE_NONSTD(int) hdlc_tx_get(hdlc_tx_state_t *s, uint8_t buf[], size_t max_len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/ima_adpcm.h b/Libraries/spandsp/spandsp/spandsp/ima_adpcm.h new file mode 100644 index 000000000..7ef715490 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/ima_adpcm.h @@ -0,0 +1,117 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * ima_adpcm.c - Conversion routines between linear 16 bit PCM data and + * IMA/DVI/Intel ADPCM format. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on a bit from here, a bit from there, eye of toad, + * ear of bat, etc - plus, of course, my own 2 cents. + * + * $Id: ima_adpcm.h,v 1.25 2009/04/11 18:11:19 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_IMA_ADPCM_H_) +#define _SPANDSP_IMA_ADPCM_H_ + +/*! \page ima_adpcm_page IMA/DVI/Intel ADPCM encoding and decoding +\section ima_adpcm_page_sec_1 What does it do? +IMA ADPCM offers a good balance of simplicity and quality at a rate of +32kbps. + +\section ima_adpcm_page_sec_2 How does it work? + +\section ima_adpcm_page_sec_3 How do I use it? +*/ + +enum +{ + /*! IMA4 is the original IMA ADPCM variant */ + IMA_ADPCM_IMA4 = 0, + /*! DVI4 is the IMA ADPCM variant defined in RFC3551 */ + IMA_ADPCM_DVI4 = 1, + /*! VDVI is the variable bit rate IMA ADPCM variant defined in RFC3551 */ + IMA_ADPCM_VDVI = 2 +}; + +/*! + IMA (DVI/Intel) ADPCM conversion state descriptor. This defines the state of + a single working instance of the IMA ADPCM converter. This is used for + either linear to ADPCM or ADPCM to linear conversion. +*/ +typedef struct ima_adpcm_state_s ima_adpcm_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an IMA ADPCM encode or decode context. + \param s The IMA ADPCM context. + \param variant IMA_ADPCM_IMA4, IMA_ADPCM_DVI4, or IMA_ADPCM_VDVI. + \param chunk_size The size of a chunk, in samples. A chunk size of + zero sample samples means treat each encode or decode operation + as a chunk. + \return A pointer to the IMA ADPCM context, or NULL for error. */ +SPAN_DECLARE(ima_adpcm_state_t *) ima_adpcm_init(ima_adpcm_state_t *s, + int variant, + int chunk_size); + +/*! Release an IMA ADPCM encode or decode context. + \param s The IMA ADPCM context. + \return 0 for OK. */ +SPAN_DECLARE(int) ima_adpcm_release(ima_adpcm_state_t *s); + +/*! Free an IMA ADPCM encode or decode context. + \param s The IMA ADPCM context. + \return 0 for OK. */ +SPAN_DECLARE(int) ima_adpcm_free(ima_adpcm_state_t *s); + +/*! Encode a buffer of linear PCM data to IMA ADPCM. + \param s The IMA ADPCM context. + \param ima_data The IMA ADPCM data produced. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of bytes of IMA ADPCM data produced. */ +SPAN_DECLARE(int) ima_adpcm_encode(ima_adpcm_state_t *s, + uint8_t ima_data[], + const int16_t amp[], + int len); + +/*! Decode a buffer of IMA ADPCM data to linear PCM. + \param s The IMA ADPCM context. + \param amp The audio sample buffer. + \param ima_data The IMA ADPCM data + \param ima_bytes The number of bytes of IMA ADPCM data + \return The number of samples returned. */ +SPAN_DECLARE(int) ima_adpcm_decode(ima_adpcm_state_t *s, + int16_t amp[], + const uint8_t ima_data[], + int ima_bytes); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/logging.h b/Libraries/spandsp/spandsp/spandsp/logging.h new file mode 100644 index 000000000..9a93f8239 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/logging.h @@ -0,0 +1,141 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * logging.h - definitions for error and debug logging. + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: logging.h,v 1.20 2009/02/10 17:44:18 steveu Exp $ + */ + +/*! \file */ + +/*! \page logging_page Logging +\section logging_page_sec_1 What does it do? +???. +*/ + +#if !defined(_SPANDSP_LOGGING_H_) +#define _SPANDSP_LOGGING_H_ + +/*! General logging function for spandsp logging. */ +typedef void (*message_handler_func_t)(int level, const char *text); + +/*! Error logging function for spandsp logging. */ +typedef void (*error_handler_func_t)(const char *text); + +/* Logging elements */ +enum +{ + SPAN_LOG_SEVERITY_MASK = 0x00FF, + SPAN_LOG_SHOW_DATE = 0x0100, + SPAN_LOG_SHOW_SAMPLE_TIME = 0x0200, + SPAN_LOG_SHOW_SEVERITY = 0x0400, + SPAN_LOG_SHOW_PROTOCOL = 0x0800, + SPAN_LOG_SHOW_VARIANT = 0x1000, + SPAN_LOG_SHOW_TAG = 0x2000, + SPAN_LOG_SUPPRESS_LABELLING = 0x8000 +}; + +/* Logging severity levels */ +enum +{ + SPAN_LOG_NONE = 0, + SPAN_LOG_ERROR = 1, + SPAN_LOG_WARNING = 2, + SPAN_LOG_PROTOCOL_ERROR = 3, + SPAN_LOG_PROTOCOL_WARNING = 4, + SPAN_LOG_FLOW = 5, + SPAN_LOG_FLOW_2 = 6, + SPAN_LOG_FLOW_3 = 7, + SPAN_LOG_DEBUG = 8, + SPAN_LOG_DEBUG_2 = 9, + SPAN_LOG_DEBUG_3 = 10 +}; + +/*! + Logging descriptor. This defines the working state for a single instance of + the logging facility for spandsp. +*/ +typedef struct logging_state_s logging_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Test if logging of a specified severity level is enabled. + \brief Test if logging of a specified severity level is enabled. + \param s The logging context. + \param level The severity level to be tested. + \return TRUE if logging is enable, else FALSE. +*/ +SPAN_DECLARE(int) span_log_test(logging_state_t *s, int level); + +/*! Generate a log entry. + \brief Generate a log entry. + \param s The logging context. + \param level The severity level of the entry. + \param format ??? + \return 0 if no output generated, else 1. +*/ +SPAN_DECLARE(int) span_log(logging_state_t *s, int level, const char *format, ...); + +/*! Generate a log entry displaying the contents of a buffer. + \brief Generate a log entry displaying the contents of a buffer + \param s The logging context. + \param level The severity level of the entry. + \param tag A label for the log entry. + \param buf The buffer to be dumped to the log. + \param len The length of buf. + \return 0 if no output generated, else 1. +*/ +SPAN_DECLARE(int) span_log_buf(logging_state_t *s, int level, const char *tag, const uint8_t *buf, int len); + +SPAN_DECLARE(int) span_log_set_level(logging_state_t *s, int level); + +SPAN_DECLARE(int) span_log_set_tag(logging_state_t *s, const char *tag); + +SPAN_DECLARE(int) span_log_set_protocol(logging_state_t *s, const char *protocol); + +SPAN_DECLARE(int) span_log_set_sample_rate(logging_state_t *s, int samples_per_second); + +SPAN_DECLARE(int) span_log_bump_samples(logging_state_t *s, int samples); + +SPAN_DECLARE(void) span_log_set_message_handler(logging_state_t *s, message_handler_func_t func); + +SPAN_DECLARE(void) span_log_set_error_handler(logging_state_t *s, error_handler_func_t func); + +SPAN_DECLARE(void) span_set_message_handler(message_handler_func_t func); + +SPAN_DECLARE(void) span_set_error_handler(error_handler_func_t func); + +SPAN_DECLARE(logging_state_t *) span_log_init(logging_state_t *s, int level, const char *tag); + +SPAN_DECLARE(int) span_log_release(logging_state_t *s); + +SPAN_DECLARE(int) span_log_free(logging_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/lpc10.h b/Libraries/spandsp/spandsp/spandsp/lpc10.h new file mode 100644 index 000000000..d43955065 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/lpc10.h @@ -0,0 +1,120 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * lpc10.h - LPC10 low bit rate speech codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: lpc10.h,v 1.22 2009/04/11 18:11:19 steveu Exp $ + */ + +#if !defined(_SPANDSP_LPC10_H_) +#define _SPANDSP_LPC10_H_ + +/*! \page lpc10_page LPC10 encoding and decoding +\section lpc10_page_sec_1 What does it do? +The LPC10 module implements the US Department of Defense LPC10 +codec. This codec produces compressed data at 2400bps. At such +a low rate high fidelity cannot be expected. However, the speech +clarity is quite good, and this codec is unencumbered by patent +or other restrictions. + +\section lpc10_page_sec_2 How does it work? +???. +*/ + +#define LPC10_SAMPLES_PER_FRAME 180 +#define LPC10_BITS_IN_COMPRESSED_FRAME 54 + +/*! + LPC10 codec unpacked frame. +*/ +typedef struct +{ + /*! Pitch */ + int32_t ipitch; + /*! Energy */ + int32_t irms; + /*! Reflection coefficients */ + int32_t irc[10]; +} lpc10_frame_t; + +/*! + LPC10 codec encoder state descriptor. This defines the state of + a single working instance of the LPC10 encoder. +*/ +typedef struct lpc10_encode_state_s lpc10_encode_state_t; + +/*! + LPC10 codec decoder state descriptor. This defines the state of + a single working instance of the LPC10 decoder. +*/ +typedef struct lpc10_decode_state_s lpc10_decode_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an LPC10e encode context. + \param s The LPC10e context + \param error_correction ??? + \return A pointer to the LPC10e context, or NULL for error. */ +SPAN_DECLARE(lpc10_encode_state_t *) lpc10_encode_init(lpc10_encode_state_t *s, int error_correction); + +SPAN_DECLARE(int) lpc10_encode_release(lpc10_encode_state_t *s); + +SPAN_DECLARE(int) lpc10_encode_free(lpc10_encode_state_t *s); + +/*! Encode a buffer of linear PCM data to LPC10e. + \param s The LPC10e context. + \param ima_data The LPC10e data produced. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. This must be a multiple of 180, as + this is the number of samples on a frame. + \return The number of bytes of LPC10e data produced. */ +SPAN_DECLARE(int) lpc10_encode(lpc10_encode_state_t *s, uint8_t code[], const int16_t amp[], int len); + +/*! Initialise an LPC10e decode context. + \param s The LPC10e context + \param error_correction ??? + \return A pointer to the LPC10e context, or NULL for error. */ +SPAN_DECLARE(lpc10_decode_state_t *) lpc10_decode_init(lpc10_decode_state_t *st, int error_correction); + +SPAN_DECLARE(int) lpc10_decode_release(lpc10_decode_state_t *s); + +SPAN_DECLARE(int) lpc10_decode_free(lpc10_decode_state_t *s); + +/*! Decode a buffer of LPC10e data to linear PCM. + \param s The LPC10e context. + \param amp The audio sample buffer. + \param code The LPC10e data. + \param len The number of bytes of LPC10e data to be decoded. This must be a multiple of 7, + as each frame is packed into 7 bytes. + \return The number of samples returned. */ +SPAN_DECLARE(int) lpc10_decode(lpc10_decode_state_t *s, int16_t amp[], const uint8_t code[], int len); + + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of include ---------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/modem_connect_tones.h b/Libraries/spandsp/spandsp/spandsp/modem_connect_tones.h new file mode 100644 index 000000000..c104e2352 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/modem_connect_tones.h @@ -0,0 +1,177 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * modem_connect_tones.c - Generation and detection of tones + * associated with modems calling and + * answering calls. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: modem_connect_tones.h,v 1.24 2009/06/02 16:03:56 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_MODEM_CONNECT_TONES_H_) +#define _SPANDSP_MODEM_CONNECT_TONES_H_ + +/*! \page modem_connect_tones_page Modem connect tone detection + +\section modem_connect_tones_page_sec_1 What does it do? +Some telephony terminal equipment, such as modems, require a channel which is as +clear as possible. They use their own echo cancellation. If the network is also +performing echo cancellation the two cancellors can end up squabbling about the +nature of the channel, with bad results. A special tone is defined which should +cause the network to disable any echo cancellation processes. This is the echo +canceller disable tone. + +The tone detector's design assumes the channel is free of any DC component. + +\section modem_connect_tones_page_sec_2 How does it work? +A sharp notch filter is implemented as a single bi-quad section. The presence of +the 2100Hz disable tone is detected by comparing the notched filtered energy +with the unfiltered energy. If the notch filtered energy is much lower than the +unfiltered energy, then a large proportion of the energy must be at the notch +frequency. This type of detector may seem less intuitive than using a narrow +bandpass filter to isolate the energy at the notch freqency. However, a sharp +bandpass implemented as an IIR filter rings badly. The reciprocal notch filter +is very well behaved for our purpose. +*/ + +enum +{ + /*! \brief This is reported when a tone stops. */ + MODEM_CONNECT_TONES_NONE = 0, + /*! \brief CNG tone is a pure 1100Hz tone, in 0.5s bursts, with 3s silences in between. The + bursts repeat for as long as is required. */ + MODEM_CONNECT_TONES_FAX_CNG = 1, + /*! \brief ANS tone is a pure continuous 2100Hz+-15Hz tone for 3.3s+-0.7s. */ + MODEM_CONNECT_TONES_ANS = 2, + /*! \brief ANS with phase reversals tone is a 2100Hz+-15Hz tone for 3.3s+-0.7s, with a 180 degree + phase jump every 450ms+-25ms. */ + MODEM_CONNECT_TONES_ANS_PR = 3, + /*! \brief The ANSam tone is a version of ANS with 20% of 15Hz+-0.1Hz AM modulation, as per V.8 */ + MODEM_CONNECT_TONES_ANSAM = 4, + /*! \brief The ANSam with phase reversals tone is a version of ANS_PR with 20% of 15Hz+-0.1Hz AM + modulation, as per V.8 */ + MODEM_CONNECT_TONES_ANSAM_PR = 5, + /*! \brief FAX preamble in a string of V.21 HDLC flag octets. */ + MODEM_CONNECT_TONES_FAX_PREAMBLE = 6, + /*! \brief CED tone is the same as ANS tone. FAX preamble in a string of V.21 HDLC flag octets. + This is only valid as a tone type to receive. It is never reported as a detected tone + type. The report will either be for FAX preamble or CED/ANS tone. */ + MODEM_CONNECT_TONES_FAX_CED_OR_PREAMBLE = 7 +}; + +/*! \brief FAX CED tone is the same as ANS tone. */ +#define MODEM_CONNECT_TONES_FAX_CED MODEM_CONNECT_TONES_ANS + +/*! + Modem connect tones generator descriptor. This defines the state + of a single working instance of the tone generator. +*/ +typedef struct modem_connect_tones_tx_state_s modem_connect_tones_tx_state_t; + +/*! + Modem connect tones receiver descriptor. This defines the state + of a single working instance of the tone detector. +*/ +typedef struct modem_connect_tones_rx_state_s modem_connect_tones_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Initialise an instance of the modem connect tones generator. + \param s The context. +*/ +SPAN_DECLARE(modem_connect_tones_tx_state_t *) modem_connect_tones_tx_init(modem_connect_tones_tx_state_t *s, + int tone_type); + +/*! \brief Release an instance of the modem connect tones generator. + \param s The context. + \return 0 for OK, else -1. +*/ +SPAN_DECLARE(int) modem_connect_tones_tx_release(modem_connect_tones_tx_state_t *s); + +/*! \brief Free an instance of the modem connect tones generator. + \param s The context. + \return 0 for OK, else -1. +*/ +SPAN_DECLARE(int) modem_connect_tones_tx_free(modem_connect_tones_tx_state_t *s); + +/*! \brief Generate a block of modem connect tones samples. + \param s The context. + \param amp An array of signal samples. + \param len The number of samples to generate. + \return The number of samples generated. +*/ +SPAN_DECLARE_NONSTD(int) modem_connect_tones_tx(modem_connect_tones_tx_state_t *s, + int16_t amp[], + int len); + +/*! \brief Process a block of samples through an instance of the modem connect + tones detector. + \param s The context. + \param amp An array of signal samples. + \param len The number of samples in the array. + \return The number of unprocessed samples. +*/ +SPAN_DECLARE_NONSTD(int) modem_connect_tones_rx(modem_connect_tones_rx_state_t *s, + const int16_t amp[], + int len); + +/*! \brief Test if a modem_connect tone has been detected. + \param s The context. + \return TRUE if tone is detected, else FALSE. +*/ +SPAN_DECLARE(int) modem_connect_tones_rx_get(modem_connect_tones_rx_state_t *s); + +/*! \brief Initialise an instance of the modem connect tones detector. + \param s The context. + \param tone_type The type of connect tone being tested for. + \param tone_callback An optional callback routine, used to report tones + \param user_data An opaque pointer passed to the callback routine, + \return A pointer to the context. +*/ +SPAN_DECLARE(modem_connect_tones_rx_state_t *) modem_connect_tones_rx_init(modem_connect_tones_rx_state_t *s, + int tone_type, + tone_report_func_t tone_callback, + void *user_data); + +/*! \brief Release an instance of the modem connect tones detector. + \param s The context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) modem_connect_tones_rx_release(modem_connect_tones_rx_state_t *s); + +/*! \brief Free an instance of the modem connect tones detector. + \param s The context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) modem_connect_tones_rx_free(modem_connect_tones_rx_state_t *s); + +SPAN_DECLARE(const char *) modem_connect_tone_to_str(int tone); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/modem_echo.h b/Libraries/spandsp/spandsp/spandsp/modem_echo.h new file mode 100644 index 000000000..77ffe6b53 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/modem_echo.h @@ -0,0 +1,129 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * modem_echo.h - An echo cancellor, suitable for electrical echos in GSTN modems + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2004 Steve Underwood + * + * Based on a bit from here, a bit from there, eye of toad, + * ear of bat, etc - plus, of course, my own 2 cents. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: modem_echo.h,v 1.14 2009/09/22 13:11:04 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_MODEM_ECHO_H_) +#define _SPANDSP_MODEM_ECHO_H_ + +/*! \page modem_echo_can_page Line echo cancellation for modems + +\section modem_echo_can_page_sec_1 What does it do? +This module aims to cancel electrical echoes (e.g. from 2-4 wire hybrids) +in modem applications. It is not very suitable for speech applications, which +require additional refinements for satisfactory performance. It is, however, more +efficient and better suited to modem applications. + +\section modem_echo_can_page_sec_2 How does it work? +The heart of the echo cancellor is an adaptive FIR filter. This is adapted to +match the impulse response of the environment being cancelled. It must be long +enough to adequately cover the duration of that impulse response. The signal +being transmitted into the environment being cancelled is passed through the +FIR filter. The resulting output is an estimate of the echo signal. This is +then subtracted from the received signal, and the result should be an estimate +of the signal which originates within the environment being cancelled (people +talking in the room, or the signal from the far end of a telephone line) free +from the echos of our own transmitted signal. + +The FIR filter is adapted using the least mean squares (LMS) algorithm. This +algorithm is attributed to Widrow and Hoff, and was introduced in 1960. It is +the commonest form of filter adaption used in things like modem line equalisers +and line echo cancellers. It works very well if the signal level is constant, +which is true for a modem signal. To ensure good performa certain conditions must +be met: + + - The transmitted signal has weak self-correlation. + - There is no signal being generated within the environment being cancelled. + +The difficulty is that neither of these can be guaranteed. If the adaption is +performed while transmitting noise (or something fairly noise like, such as +voice) the adaption works very well. If the adaption is performed while +transmitting something highly correlative (e.g. tones, like DTMF), the adaption +can go seriously wrong. The reason is there is only one solution for the +adaption on a near random signal. For a repetitive signal, there are a number of +solutions which converge the adaption, and nothing guides the adaption to choose +the correct one. + +\section modem_echo_can_page_sec_3 How do I use it? +The echo cancellor processes both the transmit and receive streams sample by +sample. The processing function is not declared inline. Unfortunately, +cancellation requires many operations per sample, so the call overhead is only a +minor burden. +*/ + +#include "fir.h" + +/*! + Modem line echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +typedef struct modem_echo_can_state_s modem_echo_can_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Create a modem echo canceller context. + \param len The length of the canceller, in samples. + eturn The new canceller context, or NULL if the canceller could not be created. +*/ +SPAN_DECLARE(modem_echo_can_state_t *) modem_echo_can_create(int len); + +/*! Free a modem echo canceller context. + \param ec The echo canceller context. +*/ +SPAN_DECLARE(void) modem_echo_can_free(modem_echo_can_state_t *ec); + +/*! Flush (reinitialise) a modem echo canceller context. + \param ec The echo canceller context. +*/ +SPAN_DECLARE(void) modem_echo_can_flush(modem_echo_can_state_t *ec); + +/*! Set the adaption mode of a modem echo canceller context. + \param ec The echo canceller context. + \param adapt The mode. +*/ +SPAN_DECLARE(void) modem_echo_can_adaption_mode(modem_echo_can_state_t *ec, int adapt); + +/*! Process a sample through a modem echo canceller. + \param ec The echo canceller context. + \param tx The transmitted audio sample. + \param rx The received audio sample. + eturn The clean (echo cancelled) received sample. +*/ +SPAN_DECLARE(int16_t) modem_echo_can_update(modem_echo_can_state_t *ec, int16_t tx, int16_t rx); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/noise.h b/Libraries/spandsp/spandsp/spandsp/noise.h new file mode 100644 index 000000000..eca113686 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/noise.h @@ -0,0 +1,131 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * noise.h - A low complexity audio noise generator, suitable for + * real time generation (current just approx AWGN) + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: noise.h,v 1.17 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_NOISE_H_) +#define _SPANDSP_NOISE_H_ + +/*! \page noise_page Noise generation + +\section noise_page_sec_1 What does it do? +It generates audio noise. Currently it only generates reasonable quality +AWGN. It is designed to be of sufficiently low complexity to generate large +volumes of reasonable quality noise, in real time. + +Hoth noise is used to model indoor ambient noise when evaluating communications +systems such as telephones. It is named after D.F. Hoth, who made the first +systematic study of this. The official definition of Hoth noise is IEEE +standard 269-2001 (revised from 269-1992), "Draft Standard Methods for Measuring +Transmission Performance of Analog and Digital Telephone Sets, Handsets and Headsets." + +The table below gives the spectral density of Hoth noise, adjusted in level to produce +a reading of 50 dBA. + +Freq (Hz) Spectral Bandwidth Total power in + density 10 log_f each 1/3 octave band + (dB SPL/Hz) (dB) (dB SPL) + 100 32.4 13.5 45.9 + 125 30.9 14.7 45.5 + 160 29.1 15.7 44.9 + 200 27.6 16.5 44.1 + 250 26.0 17.6 43.6 + 315 24.4 18.7 43.1 + 400 22.7 19.7 42.3 + 500 21.1 20.6 41.7 + 630 19.5 21.7 41.2 + 800 17.8 22.7 40.4 +1000 16.2 23.5 39.7 +1250 14.6 24.7 39.3 +1600 12.9 25.7 38.7 +2000 11.3 26.5 37.8 +2500 9.6 27.6 37.2 +3150 7.8 28.7 36.5 +4000 5.4 29.7 34.8 +5000 2.6 30.6 33.2 +6300 -1.3 31.7 30.4 +8000 -6.6 32.7 26.0 + +The tolerance for each 1/3rd octave band is ¡Ó3dB. + +\section awgn_page_sec_2 How does it work? +The central limit theorem says if you add a few random numbers together, +the result starts to look Gaussian. In this case we sum 8 random numbers. +The result is fast, and perfectly good as a noise source for many purposes. +It should not be trusted as a high quality AWGN generator, for elaborate +modelling purposes. +*/ + +enum +{ + NOISE_CLASS_AWGN = 1, + NOISE_CLASS_HOTH +}; + +/*! + Noise generator descriptor. This contains all the state information for an instance + of the noise generator. + */ +typedef struct noise_state_s noise_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an audio noise generator. + \brief Initialise an audio noise generator. + \param s The noise generator context. + \param seed A seed for the underlying random number generator. + \param level The noise power level in dBmO. + \param class_of_noise The class of noise (e.g. AWGN). + \param quality A parameter which permits speed and accuracy of the noise + generation to be adjusted. + \return A pointer to the noise generator context. +*/ +SPAN_DECLARE(noise_state_t *) noise_init_dbm0(noise_state_t *s, int seed, float level, int class_of_noise, int quality); + +SPAN_DECLARE(noise_state_t *) noise_init_dbov(noise_state_t *s, int seed, float level, int class_of_noise, int quality); + +SPAN_DECLARE(int) noise_release(noise_state_t *s); + +SPAN_DECLARE(int) noise_free(noise_state_t *s); + +/*! Generate a sample of audio noise. + \brief Generate a sample of audio noise. + \param s The noise generator context. + \return The generated sample. +*/ +SPAN_DECLARE(int16_t) noise(noise_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/oki_adpcm.h b/Libraries/spandsp/spandsp/spandsp/oki_adpcm.h new file mode 100644 index 000000000..c9bfef7ef --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/oki_adpcm.h @@ -0,0 +1,104 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * oki_adpcm.h - Conversion routines between linear 16 bit PCM data and + * OKI (Dialogic) ADPCM format. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: oki_adpcm.h,v 1.24 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_OKI_ADPCM_H_) +#define _SPANDSP_OKI_ADPCM_H_ + +/*! \page okiadpcm_page OKI (Dialogic) ADPCM encoding and decoding +\section okiadpcm_page_sec_1 What does it do? +OKI ADPCM is widely used in the CTI industry because it is the principal format +supported by Dialogic. As the market leader, they tend to define "common +practice". It offers a good balance of simplicity and quality at rates of +24kbps or 32kbps. 32kbps is obtained by ADPCM compressing 8k samples/second linear +PCM. 24kbps is obtained by resampling to 6k samples/second and using the same ADPCM +compression algorithm on the slower samples. + +The algorithms for this ADPCM codec can be found in "PC Telephony - The complete guide +to designing, building and programming systems using Dialogic and Related Hardware" +by Bob Edgar. pg 272-276. */ + +/*! + Oki (Dialogic) ADPCM conversion state descriptor. This defines the state of + a single working instance of the Oki ADPCM converter. This is used for + either linear to ADPCM or ADPCM to linear conversion. +*/ +typedef struct oki_adpcm_state_s oki_adpcm_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise an Oki ADPCM encode or decode context. + \param s The Oki ADPCM context. + \param bit_rate The required bit rate for the ADPCM data. + The valid rates are 24000 and 32000. + \return A pointer to the Oki ADPCM context, or NULL for error. */ +SPAN_DECLARE(oki_adpcm_state_t *) oki_adpcm_init(oki_adpcm_state_t *s, + int bit_rate); + +/*! Release an Oki ADPCM encode or decode context. + \param s The Oki ADPCM context. + \return 0 for OK. */ +SPAN_DECLARE(int) oki_adpcm_release(oki_adpcm_state_t *s); + +/*! Free an Oki ADPCM encode or decode context. + \param s The Oki ADPCM context. + \return 0 for OK. */ +SPAN_DECLARE(int) oki_adpcm_free(oki_adpcm_state_t *s); + +/*! Decode a buffer of Oki ADPCM data to linear PCM. + \param s The Oki ADPCM context. + \param amp The audio sample buffer. + \param oki_data + \param oki_bytes + \return The number of samples returned. */ +SPAN_DECLARE(int) oki_adpcm_decode(oki_adpcm_state_t *s, + int16_t amp[], + const uint8_t oki_data[], + int oki_bytes); + +/*! Encode a buffer of linear PCM data to Oki ADPCM. + \param s The Oki ADPCM context. + \param oki_data The Oki ADPCM data produced + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of bytes of Oki ADPCM data produced. */ +SPAN_DECLARE(int) oki_adpcm_encode(oki_adpcm_state_t *s, + uint8_t oki_data[], + const int16_t amp[], + int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/playout.h b/Libraries/spandsp/spandsp/spandsp/playout.h new file mode 100644 index 000000000..abe3584a0 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/playout.h @@ -0,0 +1,216 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * playout.h + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: playout.h,v 1.14 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_PLAYOUT_H_) +#define _SPANDSP_PLAYOUT_H_ + +/*! \page playout_page Play-out (jitter buffering) +\section playout_page_sec_1 What does it do? +The play-out module provides a static or dynamic length buffer for received frames of +audio or video data. It's goal is to maximise the receiver's tolerance of jitter in the +timing of the received frames. + +Dynamic buffers are generally good for speech, since they adapt to provide the smallest delay +consistent with a low rate of packets arriving too late to be used. For things like FoIP and +MoIP, a static length of buffer is normally necessary. Any attempt to elastically change the +buffer length would wreck a modem's data flow. +*/ + +/* Return codes */ +enum +{ + PLAYOUT_OK = 0, + PLAYOUT_ERROR, + PLAYOUT_EMPTY, + PLAYOUT_NOFRAME, + PLAYOUT_FILLIN, + PLAYOUT_DROP +}; + +/* Frame types */ +#define PLAYOUT_TYPE_CONTROL 0 +#define PLAYOUT_TYPE_SILENCE 1 +#define PLAYOUT_TYPE_SPEECH 2 + +typedef int timestamp_t; + +typedef struct playout_frame_s +{ + /*! The actual frame data */ + void *data; + /*! The type of frame */ + int type; + /*! The timestamp assigned by the sending end */ + timestamp_t sender_stamp; + /*! The timespan covered by the data in this frame */ + timestamp_t sender_len; + /*! The timestamp assigned by the receiving end */ + timestamp_t receiver_stamp; + /*! Pointer to the next earlier frame */ + struct playout_frame_s *earlier; + /*! Pointer to the next later frame */ + struct playout_frame_s *later; +} playout_frame_t; + +/*! + Playout (jitter buffer) descriptor. This defines the working state + for a single instance of playout buffering. +*/ +typedef struct +{ + /*! TRUE if the buffer is dynamically sized */ + int dynamic; + /*! The minimum length (dynamic) or fixed length (static) of the buffer */ + int min_length; + /*! The maximum length (dynamic) or fixed length (static) of the buffer */ + int max_length; + /*! The target filter threshold for adjusting dynamic buffering. */ + int dropable_threshold; + + int start; + + /*! The queued frame list */ + playout_frame_t *first_frame; + playout_frame_t *last_frame; + /*! The free frame pool */ + playout_frame_t *free_frames; + + /*! The total frames input to the buffer, to date. */ + int frames_in; + /*! The total frames output from the buffer, to date. */ + int frames_out; + /*! The number of frames received out of sequence. */ + int frames_oos; + /*! The number of frames which were discarded, due to late arrival. */ + int frames_late; + /*! The number of frames which were never received. */ + int frames_missing; + /*! The number of frames trimmed from the stream, due to buffer shrinkage. */ + int frames_trimmed; + + timestamp_t latest_expected; + /*! The present jitter adjustment */ + timestamp_t current; + /*! The sender_stamp of the last speech frame */ + timestamp_t last_speech_sender_stamp; + /*! The duration of the last speech frame */ + timestamp_t last_speech_sender_len; + + int not_first; + /*! The time since the target buffer length was last changed. */ + timestamp_t since_last_step; + /*! Filter state for tracking the packets arriving just in time */ + int32_t state_just_in_time; + /*! Filter state for tracking the packets arriving late */ + int32_t state_late; + /*! The current target length of the buffer */ + int target_buffer_length; + /*! The current actual length of the buffer, which may lag behind the target value */ + int actual_buffer_length; +} playout_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Queue a frame + \param s The play-out context. + \param data The frame data. + \param sender_len Length of frame (for voice) in timestamp units. + \param sender_stamp Sending end's time stamp. + \param receiver_stamp Local time at which packet was received, in timestamp units. + \return One of + PLAYOUT_OK: Frame queued OK. + PLAYOUT_ERROR: Some problem occured - e.g. out of memory. */ +SPAN_DECLARE(int) playout_put(playout_state_t *s, void *data, int type, timestamp_t sender_len, timestamp_t sender_stamp, timestamp_t receiver_stamp); + +/*! Get the next frame. + \param s The play-out context. + \param frame The frame. + \param sender_stamp The sender's timestamp. + \return One of + PLAYOUT_OK: Suitable frame found. + PLAYOUT_DROP: A frame which should be dropped was found (e.g. it arrived too late). + The caller should request the same time again when this occurs. + PLAYOUT_NOFRAME: There's no frame scheduled for this time. + PLAYOUT_FILLIN: Synthetic signal must be generated, as no real data is available for + this time (either we need to grow, or there was a lost frame). + PLAYOUT_EMPTY: The buffer is empty. + */ +SPAN_DECLARE(int) playout_get(playout_state_t *s, playout_frame_t *frame, timestamp_t sender_stamp); + +/*! Unconditionally get the first buffered frame. This may be used to clear out the queue, and free + all its contents, before the context is freed. + \param s The play-out context. + \return The frame, or NULL is the queue is empty. */ +SPAN_DECLARE(playout_frame_t *) playout_get_unconditional(playout_state_t *s); + +/*! Find the current length of the buffer. + \param s The play-out context. + \return The length of the buffer. */ +SPAN_DECLARE(timestamp_t) playout_current_length(playout_state_t *s); + +/*! Find the time at which the next queued frame is due to play. + Note: This value may change backwards as freshly received out of order frames are + added to the buffer. + \param s The play-out context. + \return The next timestamp. */ +SPAN_DECLARE(timestamp_t) playout_next_due(playout_state_t *s); + +/*! Reset an instance of play-out buffering. + NOTE: The buffer should be empty before you call this function, otherwise + you will leak queued frames, and some internal structures + \param s The play-out context. + \param min_length Minimum length of the buffer, in samples. + \param max_length Maximum length of the buffer, in samples. If this equals min_length, static + length buffering is used. */ +SPAN_DECLARE(void) playout_restart(playout_state_t *s, int min_length, int max_length); + +/*! Create a new instance of play-out buffering. + \param min_length Minimum length of the buffer, in samples. + \param max_length Maximum length of the buffer, in samples. If this equals min_length, static + length buffering is used. + \return The new context */ +SPAN_DECLARE(playout_state_t *) playout_init(int min_length, int max_length); + +/*! Release an instance of play-out buffering. + \param s The play-out context to be releaased + \return 0 if OK, else -1 */ +SPAN_DECLARE(int) playout_release(playout_state_t *s); + +/*! Free an instance of play-out buffering. + \param s The play-out context to be destroyed + \return 0 if OK, else -1 */ +SPAN_DECLARE(int) playout_free(playout_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/plc.h b/Libraries/spandsp/spandsp/spandsp/plc.h new file mode 100644 index 000000000..f4cbade79 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/plc.h @@ -0,0 +1,173 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * plc.h + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: plc.h,v 1.21 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PLC_H_) +#define _SPANDSP_PLC_H_ + +/*! \page plc_page Packet loss concealment +\section plc_page_sec_1 What does it do? +The packet loss concealment module provides a synthetic fill-in signal, to minimise +the audible effect of lost packets in VoIP applications. It is not tied to any +particular codec, and could be used with almost any codec which does not +specify its own procedure for packet loss concealment. + +Where a codec specific concealment procedure exists, that algorithm is usually built +around knowledge of the characteristics of the particular codec. It will, therefore, +generally give better results for that particular codec than this generic concealer will. + +The PLC code implements an algorithm similar to the one described in Appendix 1 of G.711. +However, the G.711 algorithm is optimised for 10ms packets. Few people use such small +packets. 20ms is a much more common value, and longer packets are also quite common. The +algorithm has been adjusted with this in mind. Also, the G.711 approach causes an +algorithmic delay, and requires significant buffer manipulation when there is no packet +loss. The algorithm used here avoids this. It causes no delay, and achieves comparable +quality with normal speech. + +Note that both this algorithm, and the one in G.711 are optimised for speech. For most kinds +of music a much slower decay on bursts of lost packets give better results. + +\section plc_page_sec_2 How does it work? +While good packets are being received, the plc_rx() routine keeps a record of the trailing +section of the known speech signal. If a packet is missed, plc_fillin() is called to produce +a synthetic replacement for the real speech signal. The average mean difference function +(AMDF) is applied to the last known good signal, to determine its effective pitch. +Based on this, the last pitch period of signal is saved. Essentially, this cycle of speech +will be repeated over and over until the real speech resumes. However, several refinements +are needed to obtain smooth pleasant sounding results. + +- The two ends of the stored cycle of speech will not always fit together smoothly. This can + cause roughness, or even clicks, at the joins between cycles. To soften this, the + 1/4 pitch period of real speech preceeding the cycle to be repeated is blended with the last + 1/4 pitch period of the cycle to be repeated, using an overlap-add (OLA) technique (i.e. + in total, the last 5/4 pitch periods of real speech are used). + +- The start of the synthetic speech will not always fit together smoothly with the tail of + real speech passed on before the erasure was identified. Ideally, we would like to modify + the last 1/4 pitch period of the real speech, to blend it into the synthetic speech. However, + it is too late for that. We could have delayed the real speech a little, but that would + require more buffer manipulation, and hurt the efficiency of the no-lost-packets case + (which we hope is the dominant case). Instead we use a degenerate form of OLA to modify + the start of the synthetic data. The last 1/4 pitch period of real speech is time reversed, + and OLA is used to blend it with the first 1/4 pitch period of synthetic speech. The result + seems quite acceptable. + +- As we progress into the erasure, the chances of the synthetic signal being anything like + correct steadily fall. Therefore, the volume of the synthesized signal is made to decay + linearly, such that after 50ms of missing audio it is reduced to silence. + +- When real speech resumes, an extra 1/4 pitch period of synthetic speech is blended with the + start of the real speech. If the erasure is small, this smoothes the transition. If the erasure + is long, and the synthetic signal has faded to zero, the blending softens the start up of the + real signal, avoiding a kind of "click" or "pop" effect that might occur with a sudden onset. + +\section plc_page_sec_3 How do I use it? +Before audio is processed, call plc_init() to create an instance of the packet loss +concealer. For each received audio packet that is acceptable (i.e. not including those being +dropped for being too late) call plc_rx() to record the content of the packet. Note this may +modify the packet a little after a period of packet loss, to blend real synthetic data smoothly. +When a real packet is not available in time, call plc_fillin() to create a sythetic substitute. +That's it! +*/ + +/*! Minimum allowed pitch (66 Hz) */ +#define PLC_PITCH_MIN 120 +/*! Maximum allowed pitch (200 Hz) */ +#define PLC_PITCH_MAX 40 +/*! Maximum pitch OLA window */ +#define PLC_PITCH_OVERLAP_MAX (PLC_PITCH_MIN >> 2) +/*! The length over which the AMDF function looks for similarity (20 ms) */ +#define CORRELATION_SPAN 160 +/*! History buffer length. The buffer much also be at leat 1.25 times + PLC_PITCH_MIN, but that is much smaller than the buffer needs to be for + the pitch assessment. */ +#define PLC_HISTORY_LEN (CORRELATION_SPAN + PLC_PITCH_MIN) + +/*! + The generic packet loss concealer context. +*/ +typedef struct +{ + /*! Consecutive erased samples */ + int missing_samples; + /*! Current offset into pitch period */ + int pitch_offset; + /*! Pitch estimate */ + int pitch; + /*! Buffer for a cycle of speech */ + float pitchbuf[PLC_PITCH_MIN]; + /*! History buffer */ + int16_t history[PLC_HISTORY_LEN]; + /*! Current pointer into the history buffer */ + int buf_ptr; +} plc_state_t; + + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Process a block of received audio samples for PLC. + \brief Process a block of received audio samples for PLC. + \param s The packet loss concealer context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples in the buffer. */ +SPAN_DECLARE(int) plc_rx(plc_state_t *s, int16_t amp[], int len); + +/*! Fill-in a block of missing audio samples. + \brief Fill-in a block of missing audio samples. + \param s The packet loss concealer context. + \param amp The audio sample buffer. + \param len The number of samples to be synthesised. + \return The number of samples synthesized. */ +SPAN_DECLARE(int) plc_fillin(plc_state_t *s, int16_t amp[], int len); + +/*! Initialise a packet loss concealer context. + \brief Initialise a PLC context. + \param s The packet loss concealer context. + \return A pointer to the the packet loss concealer context. */ +SPAN_DECLARE(plc_state_t *) plc_init(plc_state_t *s); + +/*! Release a packet loss concealer context. + \param s The packet loss concealer context. + \return 0 for OK. */ +SPAN_DECLARE(int) plc_release(plc_state_t *s); + +/*! Free a packet loss concealer context. + \param s The packet loss concealer context. + \return 0 for OK. */ +SPAN_DECLARE(int) plc_free(plc_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/power_meter.h b/Libraries/spandsp/spandsp/spandsp/power_meter.h new file mode 100644 index 000000000..6e03b60be --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/power_meter.h @@ -0,0 +1,154 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * power_meter.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: power_meter.h,v 1.19 2009/05/19 14:15:09 steveu Exp $ + */ + +#if !defined(_POWER_METER_H_) +#define _POWER_METER_H_ + +/*! \page power_meter_page Power metering + +\section power_meter_page_sec_1 What does it do? +The power metering module implements a simple IIR type running power meter. The damping +factor of the IIR is selectable when the meter instance is created. + +Note that the definition of dBOv is quite vague in most places - is it peak since wave, +peak square wave, etc.? This code is based on the well defined wording in RFC3389: + +"For example, in the case of a u-law system, the reference would be a square wave with +values +/-8031, and this square wave represents 0dBov. This translates into 6.18dBm0". + +\section power_meter_page_sec_2 How does it work? +*/ + +/*! + Power meter descriptor. This defines the working state for a + single instance of a power measurement device. +*/ +typedef struct +{ + /*! The shift factor, which controls the damping of the power meter. */ + int shift; + + /*! The current power reading. */ + int32_t reading; +} power_meter_t; + +typedef struct +{ + power_meter_t short_term; + power_meter_t medium_term; + int signal_present; + int32_t surge; + int32_t sag; + int32_t min; +} power_surge_detector_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a power meter context. + \brief Initialise a power meter context. + \param s The power meter context. + \param shift The shift to be used by the IIR filter. + \return The power meter context. */ +SPAN_DECLARE(power_meter_t *) power_meter_init(power_meter_t *s, int shift); + +SPAN_DECLARE(int) power_meter_release(power_meter_t *s); + +SPAN_DECLARE(int) power_meter_free(power_meter_t *s); + +/*! Change the damping factor of a power meter context. + \brief Change the damping factor of a power meter context. + \param s The power meter context. + \param shift The new shift to be used by the IIR filter. + \return The power meter context. */ +SPAN_DECLARE(power_meter_t *) power_meter_damping(power_meter_t *s, int shift); + +/*! Update a power meter. + \brief Update a power meter. + \param s The power meter context. + \param amp The amplitude of the new audio sample. + \return The current power meter reading. */ +SPAN_DECLARE(int32_t) power_meter_update(power_meter_t *s, int16_t amp); + +/*! Get the current power meter reading. + \brief Get the current power meter reading. + \param s The power meter context. + \return The current power meter reading. */ +SPAN_DECLARE(int32_t) power_meter_current(power_meter_t *s); + +/*! Get the current power meter reading, in dBm0. + \brief Get the current power meter reading, in dBm0. + \param s The power meter context. + \return The current power meter reading, in dBm0. */ +SPAN_DECLARE(float) power_meter_current_dbm0(power_meter_t *s); + +/*! Get the current power meter reading, in dBOv. + \brief Get the current power meter reading, in dBOv. + \param s The power meter context. + \return The current power meter reading, in dBOv. */ +SPAN_DECLARE(float) power_meter_current_dbov(power_meter_t *s); + +/*! Get the power meter reading which represents a specified power level in dBm0. + \brief Get the current power meter reading, in dBm0. + \param level A power level, in dB0m. + \return The equivalent power meter reading. */ +SPAN_DECLARE(int32_t) power_meter_level_dbm0(float level); + +/*! Get the power meter reading which represents a specified power level in dBOv. + \brief Get the current power meter reading, in dBOv. + \param level A power level, in dBOv. + \return The equivalent power meter reading. */ +SPAN_DECLARE(int32_t) power_meter_level_dbov(float level); + +SPAN_DECLARE(int32_t) power_surge_detector(power_surge_detector_state_t *s, int16_t amp); + +/*! Get the current surge detector short term meter reading, in dBm0. + \brief Get the current surge detector meter reading, in dBm0. + \param s The power surge detector context. + \return The current power surge detector power reading, in dBm0. */ +SPAN_DECLARE(float) power_surge_detector_current_dbm0(power_surge_detector_state_t *s); + +/*! Get the current surge detector short term meter reading, in dBOv. + \brief Get the current surge detector meter reading, in dBOv. + \param s The power surge detector context. + \return The current power surge detector power reading, in dBOv. */ +SPAN_DECLARE(float) power_surge_detector_current_dbov(power_surge_detector_state_t *s); + +SPAN_DECLARE(power_surge_detector_state_t *) power_surge_detector_init(power_surge_detector_state_t *s, float min, float surge); + +SPAN_DECLARE(int) power_surge_detector_release(power_surge_detector_state_t *s); + +SPAN_DECLARE(int) power_surge_detector_free(power_surge_detector_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/README b/Libraries/spandsp/spandsp/spandsp/private/README new file mode 100644 index 000000000..41a6b0359 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/README @@ -0,0 +1,3 @@ +The header files in this directory should only be used by code tightly integrating itself with the +spandsp library to maximise performance. To maximise compatibility with futures revisions of spandsp, +most users should avoid using these headers, or probing into the spandsp data structures in other ways. \ No newline at end of file diff --git a/Libraries/spandsp/spandsp/spandsp/private/adsi.h b/Libraries/spandsp/spandsp/spandsp/private/adsi.h new file mode 100644 index 000000000..251cb0ec8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/adsi.h @@ -0,0 +1,120 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/adsi.h - Analogue display services interface and other call ID related handling. + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: adsi.h,v 1.4 2009/04/12 04:20:01 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_ADSI_H_) +#define _SPANDSP_PRIVATE_ADSI_H_ + +/*! + ADSI transmitter descriptor. This contains all the state information for an ADSI + (caller ID, CLASS, CLIP, ACLIP) transmit channel. + */ +struct adsi_tx_state_s +{ + /*! */ + int standard; + + /*! */ + tone_gen_descriptor_t alert_tone_desc; + /*! */ + tone_gen_state_t alert_tone_gen; + /*! */ + fsk_tx_state_t fsktx; + /*! */ + dtmf_tx_state_t dtmftx; + /*! */ + async_tx_state_t asynctx; + + /*! */ + int tx_signal_on; + + /*! */ + int byte_no; + /*! */ + int bit_pos; + /*! */ + int bit_no; + /*! */ + uint8_t msg[256]; + /*! */ + int msg_len; + /*! */ + int preamble_len; + /*! */ + int preamble_ones_len; + /*! */ + int postamble_ones_len; + /*! */ + int stop_bits; + /*! */ + int baudot_shift; + + /*! */ + logging_state_t logging; +}; + +/*! + ADSI receiver descriptor. This contains all the state information for an ADSI + (caller ID, CLASS, CLIP, ACLIP, JCLIP) receive channel. + */ +struct adsi_rx_state_s +{ + /*! */ + int standard; + /*! */ + put_msg_func_t put_msg; + /*! */ + void *user_data; + + /*! */ + fsk_rx_state_t fskrx; + /*! */ + dtmf_rx_state_t dtmfrx; + + /*! */ + int consecutive_ones; + /*! */ + int bit_pos; + /*! */ + int in_progress; + /*! */ + uint8_t msg[256]; + /*! */ + int msg_len; + /*! */ + int baudot_shift; + + /*! A count of the framing errors. */ + int framing_errors; + + /*! */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/async.h b/Libraries/spandsp/spandsp/spandsp/private/async.h new file mode 100644 index 000000000..dd75f5d56 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/async.h @@ -0,0 +1,91 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/async.h - Asynchronous serial bit stream encoding and decoding + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: async.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_ASYNC_H_) +#define _SPANDSP_PRIVATE_ASYNC_H_ + +/*! + Asynchronous data transmit descriptor. This defines the state of a single + working instance of a byte to asynchronous serial converter, for use + in FSK modems. +*/ +struct async_tx_state_s +{ + /*! \brief The number of data bits per character. */ + int data_bits; + /*! \brief The type of parity. */ + int parity; + /*! \brief The number of stop bits per character. */ + int stop_bits; + /*! \brief A pointer to the callback routine used to get characters to be transmitted. */ + get_byte_func_t get_byte; + /*! \brief An opaque pointer passed when calling get_byte. */ + void *user_data; + + /*! \brief A current, partially transmitted, character. */ + int byte_in_progress; + /*! \brief The current bit position within a partially transmitted character. */ + int bitpos; + /*! \brief Parity bit. */ + int parity_bit; +}; + +/*! + Asynchronous data receive descriptor. This defines the state of a single + working instance of an asynchronous serial to byte converter, for use + in FSK modems. +*/ +struct async_rx_state_s +{ + /*! \brief The number of data bits per character. */ + int data_bits; + /*! \brief The type of parity. */ + int parity; + /*! \brief The number of stop bits per character. */ + int stop_bits; + /*! \brief TRUE if V.14 rate adaption processing should be performed. */ + int use_v14; + /*! \brief A pointer to the callback routine used to handle received characters. */ + put_byte_func_t put_byte; + /*! \brief An opaque pointer passed when calling put_byte. */ + void *user_data; + + /*! \brief A current, partially complete, character. */ + int byte_in_progress; + /*! \brief The current bit position within a partially complete character. */ + int bitpos; + /*! \brief Parity bit. */ + int parity_bit; + + /*! A count of the number of parity errors seen. */ + int parity_errors; + /*! A count of the number of character framing errors seen. */ + int framing_errors; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/at_interpreter.h b/Libraries/spandsp/spandsp/spandsp/private/at_interpreter.h new file mode 100644 index 000000000..efe4f7901 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/at_interpreter.h @@ -0,0 +1,130 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/at_interpreter.h - AT command interpreter to V.251, V.252, V.253, T.31 and the 3GPP specs. + * + * Written by Steve Underwood + * + * Copyright (C) 2004, 2005, 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: at_interpreter.h,v 1.1 2008/11/30 05:43:37 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_AT_INTERPRETER_H_) +#define _SPANDSP_PRIVATE_AT_INTERPRETER_H_ + +typedef struct at_call_id_s at_call_id_t; + +struct at_call_id_s +{ + char *id; + char *value; + at_call_id_t *next; +}; + +/*! + AT descriptor. This defines the working state for a single instance of + the AT interpreter. +*/ +struct at_state_s +{ + at_profile_t p; + /*! Value set by +GCI */ + int country_of_installation; + /*! Value set by +FIT */ + int dte_inactivity_timeout; + /*! Value set by +FIT */ + int dte_inactivity_action; + /*! Value set by L */ + int speaker_volume; + /*! Value set by M */ + int speaker_mode; + /*! This is no real DTE rate. This variable is for compatibility this serially + connected modems. */ + /*! Value set by +IPR/+FPR */ + int dte_rate; + /*! Value set by +ICF */ + int dte_char_format; + /*! Value set by +ICF */ + int dte_parity; + /*! Value set by &C */ + int rlsd_behaviour; + /*! Value set by &D */ + int dtr_behaviour; + /*! Value set by +FCL */ + int carrier_loss_timeout; + /*! Value set by X */ + int result_code_mode; + /*! Value set by +IDSR */ + int dsr_option; + /*! Value set by +ILSD */ + int long_space_disconnect_option; + /*! Value set by +ICLOK */ + int sync_tx_clock_source; + /*! Value set by +EWIND */ + int rx_window; + /*! Value set by +EWIND */ + int tx_window; + + int v8bis_signal; + int v8bis_1st_message; + int v8bis_2nd_message; + int v8bis_sig_en; + int v8bis_msg_en; + int v8bis_supp_delay; + + uint8_t rx_data[256]; + int rx_data_bytes; + + int display_call_info; + int call_info_displayed; + at_call_id_t *call_id; + char *local_id; + /*! The currently select FAX modem class. 0 = data modem mode. */ + int fclass_mode; + int at_rx_mode; + int rings_indicated; + int do_hangup; + int silent_dial; + int command_dial; + int ok_is_pending; + int dte_is_waiting; + /*! \brief TRUE if a carrier is presnt. Otherwise FALSE. */ + int rx_signal_present; + /*! \brief TRUE if a modem has trained, Otherwise FALSE. */ + int rx_trained; + int transmit; + + char line[256]; + int line_ptr; + + at_modem_control_handler_t *modem_control_handler; + void *modem_control_user_data; + at_tx_handler_t *at_tx_handler; + void *at_tx_user_data; + at_class1_handler_t *class1_handler; + void *class1_user_data; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/awgn.h b/Libraries/spandsp/spandsp/spandsp/private/awgn.h new file mode 100644 index 000000000..9ee57951c --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/awgn.h @@ -0,0 +1,46 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/awgn.h - An additive Gaussian white noise generator + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: awgn.h,v 1.1 2008/11/30 12:38:27 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_AWGN_H_) +#define _SPANDSP_PRIVATE_AWGN_H_ + +/*! + AWGN generator descriptor. This contains all the state information for an AWGN generator. + */ +struct awgn_state_s +{ + double rms; + long int ix1; + long int ix2; + long int ix3; + double r[98]; + double gset; + int iset; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/bell_r2_mf.h b/Libraries/spandsp/spandsp/spandsp/private/bell_r2_mf.h new file mode 100644 index 000000000..91be0fe94 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/bell_r2_mf.h @@ -0,0 +1,104 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bell_r2_mf.h - Bell MF and MFC/R2 tone generation and detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bell_r2_mf.h,v 1.2 2008/10/13 14:19:18 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_BELL_R2_MF_H_) +#define _SPANDSP_PRIVATE_BELL_R2_MF_H_ + +/*! + Bell MF generator state descriptor. This defines the state of a single + working instance of a Bell MF generator. +*/ +struct bell_mf_tx_state_s +{ + /*! The tone generator. */ + tone_gen_state_t tones; + int current_sample; + union + { + queue_state_t queue; + uint8_t buf[QUEUE_STATE_T_SIZE(MAX_BELL_MF_DIGITS)]; + } queue; +}; + +/*! + Bell MF digit detector descriptor. +*/ +struct bell_mf_rx_state_s +{ + /*! Optional callback funcion to deliver received digits. */ + digits_rx_callback_t digits_callback; + /*! An opaque pointer passed to the callback function. */ + void *digits_callback_data; + /*! Tone detector working states */ + goertzel_state_t out[6]; + /*! Short term history of results from the tone detection, using in persistence checking */ + uint8_t hits[5]; + /*! The current sample number within a processing block. */ + int current_sample; + + /*! The number of digits which have been lost due to buffer overflows. */ + int lost_digits; + /*! The number of digits currently in the digit buffer. */ + int current_digits; + /*! The received digits buffer. This is a NULL terminated string. */ + char digits[MAX_BELL_MF_DIGITS + 1]; +}; + +/*! + MFC/R2 tone detector descriptor. +*/ +struct r2_mf_tx_state_s +{ + /*! The tone generator. */ + tone_gen_state_t tone; + /*! TRUE if generating forward tones, otherwise generating reverse tones. */ + int fwd; + /*! The current digit being generated. */ + int digit; +}; + +/*! + MFC/R2 tone detector descriptor. +*/ +struct r2_mf_rx_state_s +{ + /*! Optional callback funcion to deliver received digits. */ + tone_report_func_t callback; + /*! An opaque pointer passed to the callback function. */ + void *callback_data; + /*! TRUE is we are detecting forward tones. FALSE if we are detecting backward tones */ + int fwd; + /*! Tone detector working states */ + goertzel_state_t out[6]; + /*! The current sample number within a processing block. */ + int current_sample; + /*! The currently detected digit. */ + int current_digit; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/bert.h b/Libraries/spandsp/spandsp/spandsp/private/bert.h new file mode 100644 index 000000000..a1b0c3a17 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/bert.h @@ -0,0 +1,92 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/bert.h - Bit error rate tests. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bert.h,v 1.2 2009/04/14 16:04:54 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_BERT_H_) +#define _SPANDSP_PRIVATE_BERT_H_ + +typedef struct +{ + uint32_t reg; + int step; + int step_bit; + int bits; + int zeros; +} bert_tx_state_t; + +typedef struct +{ + uint32_t reg; + uint32_t ref_reg; + uint32_t master_reg; + int step; + int step_bit; + int resync; + int bits; + int zeros; + int resync_len; + int resync_percent; + int resync_bad_bits; + int resync_cnt; + int report_countdown; + int measurement_step; +} bert_rx_state_t; + +/*! + Bit error rate tester (BERT) descriptor. This defines the working state for a + single instance of the BERT. +*/ +struct bert_state_s +{ + int pattern; + int pattern_class; + bert_report_func_t reporter; + void *user_data; + int report_frequency; + int limit; + + uint32_t mask; + int shift; + int shift2; + int max_zeros; + int invert; + int resync_time; + + int decade_ptr[9]; + int decade_bad[9][10]; + int error_rate; + + bert_tx_state_t tx; + bert_rx_state_t rx; + + bert_results_t results; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/bitstream.h b/Libraries/spandsp/spandsp/spandsp/private/bitstream.h new file mode 100644 index 000000000..fe80c0bdb --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/bitstream.h @@ -0,0 +1,44 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/bitstream.h - Bitstream composition and decomposition routines. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: bitstream.h,v 1.1.4.1 2009/12/28 12:20:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_BITSTREAM_H_) +#define _SPANDSP_PRIVATE_BITSTREAM_H_ + +/*! Bitstream handler state */ +struct bitstream_state_s +{ + /*! The bit stream. */ + uint32_t bitstream; + /*! The residual bits in bitstream. */ + int residue; + /*! TRUE if the stream is LSB first, else MSB first */ + int lsb_first; +}; + + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/dtmf.h b/Libraries/spandsp/spandsp/spandsp/private/dtmf.h new file mode 100644 index 000000000..017cc2680 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/dtmf.h @@ -0,0 +1,111 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/dtmf.h - DTMF tone generation and detection + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: dtmf.h,v 1.1 2008/10/13 13:14:01 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_DTMF_H_) +#define _SPANDSP_PRIVATE_DTMF_H_ + +/*! + DTMF generator state descriptor. This defines the state of a single + working instance of a DTMF generator. +*/ +struct dtmf_tx_state_s +{ + tone_gen_state_t tones; + float low_level; + float high_level; + int on_time; + int off_time; + union + { + queue_state_t queue; + uint8_t buf[QUEUE_STATE_T_SIZE(MAX_DTMF_DIGITS)]; + } queue; +}; + +/*! + DTMF digit detector descriptor. +*/ +struct dtmf_rx_state_s +{ + /*! Optional callback funcion to deliver received digits. */ + digits_rx_callback_t digits_callback; + /*! An opaque pointer passed to the callback function. */ + void *digits_callback_data; + /*! Optional callback funcion to deliver real time digit state changes. */ + tone_report_func_t realtime_callback; + /*! An opaque pointer passed to the real time callback function. */ + void *realtime_callback_data; + /*! TRUE if dialtone should be filtered before processing */ + int filter_dialtone; +#if defined(SPANDSP_USE_FIXED_POINT) + /*! 350Hz filter state for the optional dialtone filter. */ + float z350[2]; + /*! 440Hz filter state for the optional dialtone filter. */ + float z440[2]; + /*! Maximum acceptable "normal" (lower bigger than higher) twist ratio. */ + float normal_twist; + /*! Maximum acceptable "reverse" (higher bigger than lower) twist ratio. */ + float reverse_twist; + /*! Minimum acceptable tone level for detection. */ + int32_t threshold; + /*! The accumlating total energy on the same period over which the Goertzels work. */ + int32_t energy; +#else + /*! 350Hz filter state for the optional dialtone filter. */ + float z350[2]; + /*! 440Hz filter state for the optional dialtone filter. */ + float z440[2]; + /*! Maximum acceptable "normal" (lower bigger than higher) twist ratio. */ + float normal_twist; + /*! Maximum acceptable "reverse" (higher bigger than lower) twist ratio. */ + float reverse_twist; + /*! Minimum acceptable tone level for detection. */ + float threshold; + /*! The accumlating total energy on the same period over which the Goertzels work. */ + float energy; +#endif + /*! Tone detector working states for the row tones. */ + goertzel_state_t row_out[4]; + /*! Tone detector working states for the column tones. */ + goertzel_state_t col_out[4]; + /*! The result of the last tone analysis. */ + uint8_t last_hit; + /*! The confirmed digit we are currently receiving */ + uint8_t in_digit; + /*! The current sample number within a processing block. */ + int current_sample; + + /*! The number of digits which have been lost due to buffer overflows. */ + int lost_digits; + /*! The number of digits currently in the digit buffer. */ + int current_digits; + /*! The received digits buffer. This is a NULL terminated string. */ + char digits[MAX_DTMF_DIGITS + 1]; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/echo.h b/Libraries/spandsp/spandsp/spandsp/private/echo.h new file mode 100644 index 000000000..4a9179831 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/echo.h @@ -0,0 +1,94 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/echo.h - An echo cancellor, suitable for electrical and acoustic + * cancellation. This code does not currently comply with + * any relevant standards (e.g. G.164/5/7/8). + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: echo.h,v 1.1 2009/09/22 13:11:04 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_ECHO_H_) +#define _SPANDSP_PRIVATE_ECHO_H_ + +/*! + G.168 echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +struct echo_can_state_s +{ + int tx_power[4]; + int rx_power[3]; + int clean_rx_power; + + int rx_power_threshold; + int nonupdate_dwell; + + int curr_pos; + + int taps; + int tap_mask; + int adaption_mode; + + int32_t supp_test1; + int32_t supp_test2; + int32_t supp1; + int32_t supp2; + int vad; + int cng; + + int16_t geigel_max; + int geigel_lag; + int dtd_onset; + int tap_set; + int tap_rotate_counter; + + int32_t latest_correction; /* Indication of the magnitude of the latest + adaption, or a code to indicate why adaption + was skipped, for test purposes */ + int32_t last_acf[28]; + int narrowband_count; + int narrowband_score; + + fir16_state_t fir_state; + /*! Echo FIR taps (16 bit version) */ + int16_t *fir_taps16[4]; + /*! Echo FIR taps (32 bit version) */ + int32_t *fir_taps32; + + /* DC and near DC blocking filter states */ + int32_t tx_hpf[2]; + int32_t rx_hpf[2]; + + /* Parameters for the optional Hoth noise generator */ + int cng_level; + int cng_rndnum; + int cng_filter; + + /* Snapshot sample of coeffs used for development */ + int16_t *snapshot; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/fax.h b/Libraries/spandsp/spandsp/spandsp/private/fax.h new file mode 100644 index 000000000..8db77dc2f --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/fax.h @@ -0,0 +1,50 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/fax.h - private definitions for analogue line ITU T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fax.h,v 1.1 2008/10/13 13:14:01 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_FAX_H_) +#define _SPANDSP_PRIVATE_FAX_H_ + +/*! + Analogue line T.30 FAX channel descriptor. This defines the state of a single working + instance of an analogue line soft-FAX machine. +*/ +struct fax_state_s +{ + /*! \brief The T.30 back-end */ + t30_state_t t30; + + /*! \brief The analogue modem front-end */ + fax_modems_state_t modems; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/fax_modems.h b/Libraries/spandsp/spandsp/spandsp/private/fax_modems.h new file mode 100644 index 000000000..a46749c0c --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/fax_modems.h @@ -0,0 +1,127 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/fax_modems.h - definitions for the analogue modem set for fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fax_modems.h,v 1.4 2009/09/04 14:38:47 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_FAX_MODEMS_H_) +#define _SPANDSP_PRIVATE_FAX_MODEMS_H_ + +/*! + The set of modems needed for FAX, plus the auxilliary stuff, like tone generation. +*/ +struct fax_modems_state_s +{ + /*! TRUE is talker echo protection should be sent for the image modems */ + int use_tep; + + /*! If TRUE, transmit silence when there is nothing else to transmit. If FALSE return only + the actual generated audio. Note that this only affects untimed silences. Timed silences + (e.g. the 75ms silence between V.21 and a high speed modem) will alway be transmitted as + silent audio. */ + int transmit_on_idle; + + /*! \brief An HDLC context used when transmitting HDLC messages. */ + hdlc_tx_state_t hdlc_tx; + /*! \brief An HDLC context used when receiving HDLC messages. */ + hdlc_rx_state_t hdlc_rx; + /*! \brief A V.21 FSK modem context used when transmitting HDLC over V.21 + messages. */ + fsk_tx_state_t v21_tx; + /*! \brief A V.21 FSK modem context used when receiving HDLC over V.21 + messages. */ + fsk_rx_state_t v21_rx; + /*! \brief A V.17 modem context used when sending FAXes at 7200bps, 9600bps + 12000bps or 14400bps */ + v17_tx_state_t v17_tx; + /*! \brief A V.29 modem context used when receiving FAXes at 7200bps, 9600bps + 12000bps or 14400bps */ + v17_rx_state_t v17_rx; + /*! \brief A V.29 modem context used when sending FAXes at 7200bps or + 9600bps */ + v29_tx_state_t v29_tx; + /*! \brief A V.29 modem context used when receiving FAXes at 7200bps or + 9600bps */ + v29_rx_state_t v29_rx; + /*! \brief A V.27ter modem context used when sending FAXes at 2400bps or + 4800bps */ + v27ter_tx_state_t v27ter_tx; + /*! \brief A V.27ter modem context used when receiving FAXes at 2400bps or + 4800bps */ + v27ter_rx_state_t v27ter_rx; + /*! \brief Used to insert timed silences. */ + silence_gen_state_t silence_gen; + /*! \brief CED or CNG generator */ + modem_connect_tones_tx_state_t connect_tx; + /*! \brief CED or CNG detector */ + modem_connect_tones_rx_state_t connect_rx; + /*! \brief */ + dc_restore_state_t dc_restore; + + /*! \brief The currently select receiver type */ + int current_rx_type; + /*! \brief The currently select transmitter type */ + int current_tx_type; + + /*! \brief TRUE if a carrier is present. Otherwise FALSE. */ + int rx_signal_present; + /*! \brief TRUE if a modem has trained correctly. */ + int rx_trained; + /*! \brief TRUE if an HDLC frame has been received correctly. */ + int rx_frame_received; + + /*! The current receive signal handler */ + span_rx_handler_t *rx_handler; + /*! The current receive missing signal fill-in handler */ + span_rx_fillin_handler_t *rx_fillin_handler; + void *rx_user_data; + + /*! The current transmit signal handler */ + span_tx_handler_t *tx_handler; + void *tx_user_data; + + /*! The next transmit signal handler, for two stage transmit operations. + E.g. a short silence followed by a modem signal. */ + span_tx_handler_t *next_tx_handler; + void *next_tx_user_data; + + /*! The current bit rate of the transmitter. */ + int tx_bit_rate; + /*! The current bit rate of the receiver. */ + int rx_bit_rate; + + /*! If TRUE, transmission is in progress */ + int transmit; + /*! \brief Audio logging file handle for received audio. */ + int audio_rx_log; + /*! \brief Audio logging file handle for transmitted audio. */ + int audio_tx_log; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/fsk.h b/Libraries/spandsp/spandsp/spandsp/private/fsk.h new file mode 100644 index 000000000..0fbbf0304 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/fsk.h @@ -0,0 +1,100 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/fsk.h - FSK modem transmit and receive parts + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: fsk.h,v 1.5 2009/04/01 13:22:40 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_FSK_H_) +#define _SPANDSP_PRIVATE_FSK_H_ + +/*! + FSK modem transmit descriptor. This defines the state of a single working + instance of an FSK modem transmitter. +*/ +struct fsk_tx_state_s +{ + int baud_rate; + /*! \brief The callback function used to get the next bit to be transmitted. */ + get_bit_func_t get_bit; + /*! \brief A user specified opaque pointer passed to the get_bit function. */ + void *get_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + int32_t phase_rates[2]; + int16_t scaling; + int32_t current_phase_rate; + uint32_t phase_acc; + int baud_frac; + int shutdown; +}; + +/*! + FSK modem receive descriptor. This defines the state of a single working + instance of an FSK modem receiver. +*/ +struct fsk_rx_state_s +{ + int baud_rate; + /*! \brief Synchronous/asynchronous framing control */ + int framing_mode; + /*! \brief The callback function used to put each bit received. */ + put_bit_func_t put_bit; + /*! \brief A user specified opaque pointer passed to the put_bit routine. */ + void *put_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + int32_t carrier_on_power; + int32_t carrier_off_power; + power_meter_t power; + /*! \brief The value of the last signal sample, using the a simple HPF for signal power estimation. */ + int16_t last_sample; + /*! \brief >0 if a signal above the minimum is present. It may or may not be a V.29 signal. */ + int signal_present; + + int32_t phase_rate[2]; + uint32_t phase_acc[2]; + + int correlation_span; + + complexi32_t window[2][FSK_MAX_WINDOW_LEN]; + complexi32_t dot[2]; + int buf_ptr; + + int frame_state; + int frame_bits; + int baud_phase; + int last_bit; + int scaling_shift; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/g711.h b/Libraries/spandsp/spandsp/spandsp/private/g711.h new file mode 100644 index 000000000..4e9021e00 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/g711.h @@ -0,0 +1,41 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/g711.h - In line A-law and u-law conversion routines + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: g711.h,v 1.2 2009/04/12 09:12:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_G711_H_) +#define _SPANDSP_PRIVATE_G711_H_ + +/*! + G.711 state + */ +struct g711_state_s +{ + /*! One of the G.711_xxx options */ + int mode; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/g722.h b/Libraries/spandsp/spandsp/spandsp/private/g722.h new file mode 100644 index 000000000..7d3b5d16f --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/g722.h @@ -0,0 +1,111 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/g722.h - The ITU G.722 codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on a single channel G.722 codec which is: + * + ***** Copyright (c) CMU 1993 ***** + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722.h,v 1.2 2009/04/12 09:12:11 steveu Exp $ + */ + + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_G722_H_) +#define _SPANDSP_PRIVATE_G722_H_ + +/*! The per band parameters for both encoding and decoding G.722 */ +typedef struct +{ + int16_t nb; + int16_t det; + int16_t s; + int16_t sz; + int16_t r; + int16_t p[2]; + int16_t a[2]; + int16_t b[6]; + int16_t d[7]; +} g722_band_t; + +/*! + G.722 encode state + */ +struct g722_encode_state_s +{ + /*! TRUE if the operating in the special ITU test mode, with the band split filters + disabled. */ + int itu_test_mode; + /*! TRUE if the G.722 data is packed */ + int packed; + /*! TRUE if encode from 8k samples/second */ + int eight_k; + /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ + int bits_per_sample; + + /*! Signal history for the QMF */ + int16_t x[12]; + int16_t y[12]; + int ptr; + + g722_band_t band[2]; + + uint32_t in_buffer; + int in_bits; + uint32_t out_buffer; + int out_bits; +}; + +/*! + G.722 decode state + */ +struct g722_decode_state_s +{ + /*! TRUE if the operating in the special ITU test mode, with the band split filters + disabled. */ + int itu_test_mode; + /*! TRUE if the G.722 data is packed */ + int packed; + /*! TRUE if decode to 8k samples/second */ + int eight_k; + /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ + int bits_per_sample; + + /*! Signal history for the QMF */ + int16_t x[12]; + int16_t y[12]; + int ptr; + + g722_band_t band[2]; + + uint32_t in_buffer; + int in_bits; + uint32_t out_buffer; + int out_bits; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/g726.h b/Libraries/spandsp/spandsp/spandsp/private/g726.h new file mode 100644 index 000000000..18d8f29f8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/g726.h @@ -0,0 +1,87 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/g726.h - ITU G.726 codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: g726.h,v 1.4 2009/04/12 09:12:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_G726_H_) +#define _SPANDSP_PRIVATE_G726_H_ + +/*! + * The following is the definition of the state structure + * used by the G.726 encoder and decoder to preserve their internal + * state between successive calls. The meanings of the majority + * of the state structure fields are explained in detail in the + * ITU Recommendation G.726. The field names are essentially indentical + * to variable names in the bit level description of the coding algorithm + * included in this recommendation. + */ +struct g726_state_s +{ + /*! The bit rate */ + int rate; + /*! The external coding, for tandem operation */ + int ext_coding; + /*! The number of bits per sample */ + int bits_per_sample; + /*! One of the G.726_PACKING_xxx options */ + int packing; + + /*! Locked or steady state step size multiplier. */ + int32_t yl; + /*! Unlocked or non-steady state step size multiplier. */ + int16_t yu; + /*! int16_t term energy estimate. */ + int16_t dms; + /*! Long term energy estimate. */ + int16_t dml; + /*! Linear weighting coefficient of 'yl' and 'yu'. */ + int16_t ap; + + /*! Coefficients of pole portion of prediction filter. */ + int16_t a[2]; + /*! Coefficients of zero portion of prediction filter. */ + int16_t b[6]; + /*! Signs of previous two samples of a partially reconstructed signal. */ + int16_t pk[2]; + /*! Previous 6 samples of the quantized difference signal represented in + an internal floating point format. */ + int16_t dq[6]; + /*! Previous 2 samples of the quantized difference signal represented in an + internal floating point format. */ + int16_t sr[2]; + /*! Delayed tone detect */ + int td; + + /*! \brief The bit stream processing context. */ + bitstream_state_t bs; + + /*! \brief The current encoder function. */ + g726_encoder_func_t enc_func; + /*! \brief The current decoder function. */ + g726_decoder_func_t dec_func; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/gsm0610.h b/Libraries/spandsp/spandsp/spandsp/private/gsm0610.h new file mode 100644 index 000000000..2414fd81f --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/gsm0610.h @@ -0,0 +1,65 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/gsm0610.h - GSM 06.10 full rate speech codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: gsm0610.h,v 1.2 2008/11/15 14:27:29 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_GSM0610_H_) +#define _SPANDSP_PRIVATE_GSM0610_H_ + +/*! + GSM 06.10 FR codec state descriptor. This defines the state of + a single working instance of the GSM 06.10 FR encoder or decoder. +*/ +struct gsm0610_state_s +{ + /*! \brief One of the packing modes */ + int packing; + + int16_t dp0[280]; + + /*! Preprocessing */ + int16_t z1; + int32_t L_z2; + /*! Pre-emphasis */ + int16_t mp; + + /*! Short term delay filter */ + int16_t u[8]; + int16_t LARpp[2][8]; + int16_t j; + + /*! Long term synthesis */ + int16_t nrp; + /*! Short term synthesis */ + int16_t v[9]; + /*! Decoder postprocessing */ + int16_t msr; + + /*! Encoder data */ + int16_t e[50]; +}; + +#endif +/*- End of include ---------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/hdlc.h b/Libraries/spandsp/spandsp/spandsp/private/hdlc.h new file mode 100644 index 000000000..f3ffb6c29 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/hdlc.h @@ -0,0 +1,140 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/hdlc.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: hdlc.h,v 1.3 2009/02/12 12:38:39 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_HDLC_H_) +#define _SPANDSP_PRIVATE_HDLC_H_ + +/*! + HDLC receive descriptor. This contains all the state information for an HDLC receiver. + */ +struct hdlc_rx_state_s +{ + /*! 2 for CRC-16, 4 for CRC-32 */ + int crc_bytes; + /*! \brief Maximum permitted frame length. */ + size_t max_frame_len; + /*! \brief The callback routine called to process each good received frame. */ + hdlc_frame_handler_t frame_handler; + /*! \brief An opaque parameter passed to the frame callback routine. */ + void *frame_user_data; + /*! \brief The callback routine called to report status changes. */ + modem_rx_status_func_t status_handler; + /*! \brief An opaque parameter passed to the status callback routine. */ + void *status_user_data; + /*! \brief TRUE if bad frames are to be reported. */ + int report_bad_frames; + /*! \brief The number of consecutive flags which must be seen before framing is + declared OK. */ + int framing_ok_threshold; + /*! \brief TRUE if framing OK has been announced. */ + int framing_ok_announced; + /*! \brief Number of consecutive flags seen so far. */ + int flags_seen; + + /*! \brief The raw (stuffed) bit stream buffer. */ + unsigned int raw_bit_stream; + /*! \brief The destuffed bit stream buffer. */ + unsigned int byte_in_progress; + /*! \brief The current number of bits in byte_in_progress. */ + int num_bits; + /*! \brief TRUE if in octet counting mode (e.g. for MTP). */ + int octet_counting_mode; + /*! \brief Octet count, to achieve the functionality needed for things + like MTP. */ + int octet_count; + /*! \brief The number of octets to be allowed between octet count reports. */ + int octet_count_report_interval; + + /*! \brief Buffer for a frame in progress. */ + uint8_t buffer[HDLC_MAXFRAME_LEN + 4]; + /*! \brief Length of a frame in progress. */ + size_t len; + + /*! \brief The number of bytes of good frames received (CRC not included). */ + unsigned long int rx_bytes; + /*! \brief The number of good frames received. */ + unsigned long int rx_frames; + /*! \brief The number of frames with CRC errors received. */ + unsigned long int rx_crc_errors; + /*! \brief The number of too short and too long frames received. */ + unsigned long int rx_length_errors; + /*! \brief The number of HDLC aborts received. */ + unsigned long int rx_aborts; +}; + +/*! + HDLC transmit descriptor. This contains all the state information for an + HDLC transmitter. + */ +struct hdlc_tx_state_s +{ + /*! 2 for CRC-16, 4 for CRC-32 */ + int crc_bytes; + /*! \brief The callback routine called to indicate transmit underflow. */ + hdlc_underflow_handler_t underflow_handler; + /*! \brief An opaque parameter passed to the callback routine. */ + void *user_data; + /*! \brief The minimum flag octets to insert between frames. */ + int inter_frame_flags; + /*! \brief TRUE if frame creation works in progressive mode. */ + int progressive; + /*! \brief Maximum permitted frame length. */ + size_t max_frame_len; + + /*! \brief The stuffed bit stream being created. */ + uint32_t octets_in_progress; + /*! \brief The number of bits currently in octets_in_progress. */ + int num_bits; + /*! \brief The currently rotated state of the flag octet. */ + int idle_octet; + /*! \brief The number of flag octets to send for a timed burst of flags. */ + int flag_octets; + /*! \brief The number of abort octets to send for a timed burst of aborts. */ + int abort_octets; + /*! \brief TRUE if the next underflow of timed flag octets should be reported */ + int report_flag_underflow; + + /*! \brief The current message being transmitted, with its CRC attached. */ + uint8_t buffer[HDLC_MAXFRAME_LEN + 4]; + /*! \brief The length of the message in the buffer. */ + size_t len; + /*! \brief The current send position within the buffer. */ + size_t pos; + /*! \brief The running CRC, as data fills the frame buffer. */ + uint32_t crc; + + /*! \brief The current byte being broken into bits for transmission. */ + int byte; + /*! \brief The number of bits remaining in byte. */ + int bits; + + /*! \brief TRUE if transmission should end on buffer underflow .*/ + int tx_end; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/ima_adpcm.h b/Libraries/spandsp/spandsp/spandsp/private/ima_adpcm.h new file mode 100644 index 000000000..5ca9f5bd8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/ima_adpcm.h @@ -0,0 +1,55 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/ima_adpcm.c - Conversion routines between linear 16 bit PCM data + * and IMA/DVI/Intel ADPCM format. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on a bit from here, a bit from there, eye of toad, + * ear of bat, etc - plus, of course, my own 2 cents. + * + * $Id: ima_adpcm.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_IMA_ADPCM_H_) +#define _SPANDSP_PRIVATE_IMA_ADPCM_H_ + +/*! + IMA (DVI/Intel) ADPCM conversion state descriptor. This defines the state of + a single working instance of the IMA ADPCM converter. This is used for + either linear to ADPCM or ADPCM to linear conversion. +*/ +struct ima_adpcm_state_s +{ + int variant; + /*! \brief The size of a chunk, in samples. */ + int chunk_size; + /*! \brief The last state of the ADPCM algorithm. */ + int last; + /*! \brief Current index into the step size table. */ + int step_index; + /*! \brief The current IMA code byte in progress. */ + uint16_t ima_byte; + int bits; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/logging.h b/Libraries/spandsp/spandsp/spandsp/private/logging.h new file mode 100644 index 000000000..ad3ec915e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/logging.h @@ -0,0 +1,48 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/logging.h - definitions for error and debug logging. + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: logging.h,v 1.1 2008/11/30 13:44:35 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_LOGGING_H_) +#define _SPANDSP_PRIVATE_LOGGING_H_ + +/*! + Logging descriptor. This defines the working state for a single instance of + the logging facility for spandsp. +*/ +struct logging_state_s +{ + int level; + int samples_per_second; + int64_t elapsed_samples; + const char *tag; + const char *protocol; + + message_handler_func_t span_message; + error_handler_func_t span_error; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/lpc10.h b/Libraries/spandsp/spandsp/spandsp/private/lpc10.h new file mode 100644 index 000000000..d983d9500 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/lpc10.h @@ -0,0 +1,220 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/lpc10.h - LPC10 low bit rate speech codec. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: lpc10.h,v 1.3 2009/04/12 09:12:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_LPC10_H_) +#define _SPANDSP_PRIVATE_LPC10_H_ + +/*! + LPC10 codec encoder state descriptor. This defines the state of + a single working instance of the LPC10 encoder. +*/ +struct lpc10_encode_state_s +{ + /*! \brief ??? */ + int error_correction; + + /* State used only by function high_pass_100hz */ + /*! \brief ??? */ + float z11; + /*! \brief ??? */ + float z21; + /*! \brief ??? */ + float z12; + /*! \brief ??? */ + float z22; + + /* State used by function lpc10_analyse */ + /*! \brief ??? */ + float inbuf[LPC10_SAMPLES_PER_FRAME*3]; + /*! \brief ??? */ + float pebuf[LPC10_SAMPLES_PER_FRAME*3]; + /*! \brief ??? */ + float lpbuf[696]; + /*! \brief ??? */ + float ivbuf[312]; + /*! \brief ??? */ + float bias; + /*! \brief No initial value necessary */ + int32_t osbuf[10]; + /*! \brief Initial value 1 */ + int32_t osptr; + /*! \brief ??? */ + int32_t obound[3]; + /*! \brief Initial value vwin[2][0] = 307; vwin[2][1] = 462; */ + int32_t vwin[3][2]; + /*! \brief Initial value awin[2][0] = 307; awin[2][1] = 462; */ + int32_t awin[3][2]; + /*! \brief ??? */ + int32_t voibuf[4][2]; + /*! \brief ??? */ + float rmsbuf[3]; + /*! \brief ??? */ + float rcbuf[3][10]; + /*! \brief ??? */ + float zpre; + + /* State used by function onset */ + /*! \brief ??? */ + float n; + /*! \brief Initial value 1.0f */ + float d__; + /*! \brief No initial value necessary */ + float fpc; + /*! \brief ??? */ + float l2buf[16]; + /*! \brief ??? */ + float l2sum1; + /*! \brief Initial value 1 */ + int32_t l2ptr1; + /*! \brief Initial value 9 */ + int32_t l2ptr2; + /*! \brief No initial value necessary */ + int32_t lasti; + /*! \brief Initial value FALSE */ + int hyst; + + /* State used by function lpc10_voicing */ + /*! \brief Initial value 20.0f */ + float dither; + /*! \brief ??? */ + float snr; + /*! \brief ??? */ + float maxmin; + /*! \brief Initial value is probably unnecessary */ + float voice[3][2]; + /*! \brief ??? */ + int32_t lbve; + /*! \brief ??? */ + int32_t lbue; + /*! \brief ??? */ + int32_t fbve; + /*! \brief ??? */ + int32_t fbue; + /*! \brief ??? */ + int32_t ofbue; + /*! \brief ??? */ + int32_t sfbue; + /*! \brief ??? */ + int32_t olbue; + /*! \brief ??? */ + int32_t slbue; + + /* State used by function dynamic_pitch_tracking */ + /*! \brief ??? */ + float s[60]; + /*! \brief ??? */ + int32_t p[2][60]; + /*! \brief ??? */ + int32_t ipoint; + /*! \brief ??? */ + float alphax; + + /* State used by function lpc10_pack */ + /*! \brief ??? */ + int32_t isync; +}; + +/*! + LPC10 codec decoder state descriptor. This defines the state of + a single working instance of the LPC10 decoder. +*/ +struct lpc10_decode_state_s +{ + /*! \brief ??? */ + int error_correction; + + /* State used by function decode */ + /*! \brief Initial value 60 */ + int32_t iptold; + /*! \brief Initial value TRUE */ + int first; + /*! \brief ??? */ + int32_t ivp2h; + /*! \brief ??? */ + int32_t iovoic; + /*! \brief Initial value 60. */ + int32_t iavgp; + /*! \brief ??? */ + int32_t erate; + /*! \brief ??? */ + int32_t drc[10][3]; + /*! \brief ??? */ + int32_t dpit[3]; + /*! \brief ??? */ + int32_t drms[3]; + + /* State used by function synths */ + /*! \brief ??? */ + float buf[LPC10_SAMPLES_PER_FRAME*2]; + /*! \brief Initial value LPC10_SAMPLES_PER_FRAME */ + int32_t buflen; + + /* State used by function pitsyn */ + /*! \brief No initial value necessary as long as first_pitsyn is initially TRUE */ + int32_t ivoico; + /*! \brief No initial value necessary as long as first_pitsyn is initially TRUE */ + int32_t ipito; + /*! \brief Initial value 1.0f */ + float rmso; + /*! \brief No initial value necessary as long as first_pitsyn is initially TRUE */ + float rco[10]; + /*! \brief No initial value necessary as long as first_pitsyn is initially TRUE */ + int32_t jsamp; + /*! \brief Initial value TRUE */ + int first_pitsyn; + + /* State used by function bsynz */ + /*! \brief ??? */ + int32_t ipo; + /*! \brief ??? */ + float exc[166]; + /*! \brief ??? */ + float exc2[166]; + /*! \brief ??? */ + float lpi[3]; + /*! \brief ??? */ + float hpi[3]; + /*! \brief ??? */ + float rmso_bsynz; + + /* State used by function random */ + /*! \brief ??? */ + int32_t j; + /*! \brief ??? */ + int32_t k; + /*! \brief ??? */ + int16_t y[5]; + + /* State used by function deemp */ + /*! \brief ??? */ + float dei[2]; + /*! \brief ??? */ + float deo[3]; +}; + +#endif +/*- End of include ---------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/modem_connect_tones.h b/Libraries/spandsp/spandsp/spandsp/private/modem_connect_tones.h new file mode 100644 index 000000000..331402c1e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/modem_connect_tones.h @@ -0,0 +1,105 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/modem_connect_tones.c - Generation and detection of tones + * associated with modems calling and + * answering calls. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: modem_connect_tones.h,v 1.3 2009/11/02 13:25:20 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_MODEM_CONNECT_TONES_H_) +#define _SPANDSP_PRIVATE_MODEM_CONNECT_TONES_H_ + +/*! + Modem connect tones generator descriptor. This defines the state + of a single working instance of the tone generator. +*/ +struct modem_connect_tones_tx_state_s +{ + int tone_type; + + int32_t tone_phase_rate; + uint32_t tone_phase; + int16_t level; + /*! \brief Countdown to the next phase hop */ + int hop_timer; + /*! \brief Maximum duration timer */ + int duration_timer; + uint32_t mod_phase; + int32_t mod_phase_rate; + int16_t mod_level; +}; + +/*! + Modem connect tones receiver descriptor. This defines the state + of a single working instance of the tone detector. +*/ +struct modem_connect_tones_rx_state_s +{ + /*! \brief The tone type being detected. */ + int tone_type; + /*! \brief Callback routine, using to report detection of the tone. */ + tone_report_func_t tone_callback; + /*! \brief An opaque pointer passed to tone_callback. */ + void *callback_data; + + /*! \brief The notch filter state. */ + float znotch_1; + float znotch_2; + /*! \brief The 15Hz AM filter state. */ + float z15hz_1; + float z15hz_2; + /*! \brief The in notch power estimate */ + int32_t notch_level; + /*! \brief The total channel power estimate */ + int32_t channel_level; + /*! \brief The 15Hz AM power estimate */ + int32_t am_level; + /*! \brief Sample counter for the small chunks of samples, after which a test is conducted. */ + int chunk_remainder; + /*! \brief TRUE is the tone is currently confirmed present in the audio. */ + int tone_present; + /*! \brief */ + int tone_on; + /*! \brief A millisecond counter, to time the duration of tone sections. */ + int tone_cycle_duration; + /*! \brief A count of the number of good cycles of tone reversal seen. */ + int good_cycles; + /*! \brief TRUE if the tone has been seen since the last time the user tested for it */ + int hit; + /*! \brief A V.21 FSK modem context used when searching for FAX preamble. */ + fsk_rx_state_t v21rx; + /*! \brief The raw (stuffed) bit stream buffer. */ + unsigned int raw_bit_stream; + /*! \brief The current number of bits in the octet in progress. */ + int num_bits; + /*! \brief Number of consecutive flags seen so far. */ + int flags_seen; + /*! \brief TRUE if framing OK has been announced. */ + int framing_ok_announced; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/modem_echo.h b/Libraries/spandsp/spandsp/spandsp/private/modem_echo.h new file mode 100644 index 000000000..082cb8d56 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/modem_echo.h @@ -0,0 +1,58 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/modem_echo.h - An echo cancellor, suitable for electrical echos in GSTN modems + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2004 Steve Underwood + * + * Based on a bit from here, a bit from there, eye of toad, + * ear of bat, etc - plus, of course, my own 2 cents. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: modem_echo.h,v 1.1 2009/09/22 13:11:04 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_MODEM_ECHO_H_) +#define _SPANDSP_PRIVATE_MODEM_ECHO_H_ + +/*! + Modem line echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +struct modem_echo_can_state_s +{ + int adapt; + int taps; + + fir16_state_t fir_state; + /*! Echo FIR taps (16 bit version) */ + int16_t *fir_taps16; + /*! Echo FIR taps (32 bit version) */ + int32_t *fir_taps32; + + int tx_power; + int rx_power; + + int curr_pos; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/noise.h b/Libraries/spandsp/spandsp/spandsp/private/noise.h new file mode 100644 index 000000000..4f6ecc99e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/noise.h @@ -0,0 +1,48 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/noise.h - A low complexity audio noise generator, suitable for + * real time generation (current just approx AWGN) + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: noise.h,v 1.1 2008/11/30 12:45:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_NOISE_H_) +#define _SPANDSP_PRIVATE_NOISE_H_ + +/*! + Noise generator descriptor. This contains all the state information for an instance + of the noise generator. + */ +struct noise_state_s +{ + int class_of_noise; + int quality; + int32_t rms; + uint32_t rndnum; + int32_t state; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/oki_adpcm.h b/Libraries/spandsp/spandsp/spandsp/private/oki_adpcm.h new file mode 100644 index 000000000..4894775a4 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/oki_adpcm.h @@ -0,0 +1,60 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/oki_adpcm.h - Conversion routines between linear 16 bit PCM data + * and OKI (Dialogic) ADPCM format. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: oki_adpcm.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_OKI_ADPCM_H_) +#define _SPANDSP_PRIVATE_OKI_ADPCM_H_ + +/*! + Oki (Dialogic) ADPCM conversion state descriptor. This defines the state of + a single working instance of the Oki ADPCM converter. This is used for + either linear to ADPCM or ADPCM to linear conversion. +*/ +struct oki_adpcm_state_s +{ + /*! \brief The bit rate - 24000 or 32000. */ + int bit_rate; + /*! \brief The last state of the ADPCM algorithm. */ + int16_t last; + /*! \brief Current index into the step size table. */ + int16_t step_index; + /*! \brief The compressed data byte in progress. */ + uint8_t oki_byte; + /*! \brief The signal history for the sample rate converter. */ + int16_t history[32]; + /*! \brief Pointer into the history buffer. */ + int ptr; + /*! \brief Odd/even sample counter. */ + int mark; + /*! \brief Phase accumulator for the sample rate converter. */ + int phase; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/queue.h b/Libraries/spandsp/spandsp/spandsp/private/queue.h new file mode 100644 index 000000000..006ac6810 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/queue.h @@ -0,0 +1,52 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/queue.h - simple in process message queuing + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: queue.h,v 1.2 2009/01/31 08:48:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_QUEUE_H_) +#define _SPANDSP_PRIVATE_QUEUE_H_ + +/*! + Queue descriptor. This defines the working state for a single instance of + a byte stream or message oriented queue. +*/ +struct queue_state_s +{ + /*! \brief Flags indicating the mode of the queue. */ + int flags; + /*! \brief The length of the data buffer. */ + int len; + /*! \brief The buffer input pointer. */ + volatile int iptr; + /*! \brief The buffer output pointer. */ + volatile int optr; +#if defined(SPANDSP_FULLY_DEFINE_QUEUE_STATE_T) + /*! \brief The data buffer, sized at the time the structure is created. */ + uint8_t data[]; +#endif +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/schedule.h b/Libraries/spandsp/spandsp/spandsp/private/schedule.h new file mode 100644 index 000000000..8e059dad8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/schedule.h @@ -0,0 +1,50 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/schedule.h + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: schedule.h,v 1.1 2008/11/30 05:43:37 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SCHEDULE_H_) +#define _SPANDSP_PRIVATE_SCHEDULE_H_ + +/*! A scheduled event entry. */ +struct span_sched_s +{ + uint64_t when; + span_sched_callback_func_t callback; + void *user_data; +}; + +/*! A scheduled event queue. */ +struct span_sched_state_s +{ + uint64_t ticker; + int allocated; + int max_to_date; + span_sched_t *sched; + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/sig_tone.h b/Libraries/spandsp/spandsp/spandsp/private/sig_tone.h new file mode 100644 index 000000000..edf7a68bb --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/sig_tone.h @@ -0,0 +1,219 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/sig_tone.h - Signalling tone processing for the 2280Hz, 2400Hz, 2600Hz + * and similar signalling tone used in older protocols. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: sig_tone.h,v 1.4 2009/09/04 14:38:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SIG_TONE_H_) +#define _SPANDSP_PRIVATE_SIG_TONE_H_ + +/*! + Signaling tone descriptor. This defines the working state for a + single instance of the transmit and receive sides of a signaling + tone processor. +*/ +struct sig_tone_descriptor_s +{ + /*! \brief The tones used. */ + int tone_freq[2]; + /*! \brief The high and low tone amplitudes for each of the tones. */ + int tone_amp[2][2]; + + /*! \brief The delay, in audio samples, before the high level tone drops + to a low level tone. */ + int high_low_timeout; + + /*! \brief Some signaling tone detectors use a sharp initial filter, + changing to a broader band filter after some delay. This + parameter defines the delay. 0 means it never changes. */ + int sharp_flat_timeout; + + /*! \brief Parameters to control the behaviour of the notch filter, used + to remove the tone from the voice path in some protocols. */ + int notch_lag_time; + /*! \brief TRUE if the notch may be used in the media flow. */ + int notch_allowed; + + /*! \brief The tone on persistence check, in audio samples. */ + int tone_on_check_time; + /*! \brief The tone off persistence check, in audio samples. */ + int tone_off_check_time; + + /*! \brief ??? */ + int tones; + /*! \brief The coefficients for the cascaded bi-quads notch filter. */ + struct + { +#if defined(SPANDSP_USE_FIXED_POINT) + int32_t notch_a1[3]; + int32_t notch_b1[3]; + int32_t notch_a2[3]; + int32_t notch_b2[3]; + int notch_postscale; +#else + float notch_a1[3]; + float notch_b1[3]; + float notch_a2[3]; + float notch_b2[3]; +#endif + } tone[2]; + +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief Flat mode bandpass bi-quad parameters */ + int32_t broad_a[3]; + /*! \brief Flat mode bandpass bi-quad parameters */ + int32_t broad_b[3]; + /*! \brief Post filter scaling */ + int broad_postscale; +#else + /*! \brief Flat mode bandpass bi-quad parameters */ + float broad_a[3]; + /*! \brief Flat mode bandpass bi-quad parameters */ + float broad_b[3]; +#endif + /*! \brief The coefficients for the post notch leaky integrator. */ + int32_t notch_slugi; + /*! \brief ??? */ + int32_t notch_slugp; + + /*! \brief The coefficients for the post modulus leaky integrator in the + unfiltered data path. The prescale value incorporates the + detection ratio. This is called the guard ratio in some + protocols. */ + int32_t unfiltered_slugi; + /*! \brief ??? */ + int32_t unfiltered_slugp; + + /*! \brief The coefficients for the post modulus leaky integrator in the + bandpass filter data path. */ + int32_t broad_slugi; + /*! \brief ??? */ + int32_t broad_slugp; + + /*! \brief Masks which effectively threshold the notched, weighted and + bandpassed data. */ + int32_t notch_threshold; + /*! \brief ??? */ + int32_t unfiltered_threshold; + /*! \brief ??? */ + int32_t broad_threshold; +}; + +/*! + Signaling tone transmit state + */ +struct sig_tone_tx_state_s +{ + /*! \brief The callback function used to handle signaling changes. */ + tone_report_func_t sig_update; + /*! \brief A user specified opaque pointer passed to the callback function. */ + void *user_data; + + /*! \brief Tone descriptor */ + sig_tone_descriptor_t *desc; + + /*! The phase rates for the one or two tones */ + int32_t phase_rate[2]; + /*! The phase accumulators for the one or two tones */ + uint32_t phase_acc[2]; + + /*! The scaling values for the one or two tones, and the high and low level of each tone */ + int16_t tone_scaling[2][2]; + /*! The sample timer, used to switch between the high and low level tones. */ + int high_low_timer; + + /*! \brief Current transmit tone */ + int current_tx_tone; + /*! \brief Current transmit timeout */ + int current_tx_timeout; + /*! \brief Time in current signaling state, in samples. */ + int signaling_state_duration; +}; + +/*! + Signaling tone receive state + */ +struct sig_tone_rx_state_s +{ + /*! \brief The callback function used to handle signaling changes. */ + tone_report_func_t sig_update; + /*! \brief A user specified opaque pointer passed to the callback function. */ + void *user_data; + + /*! \brief Tone descriptor */ + sig_tone_descriptor_t *desc; + + /*! \brief The current receive tone */ + int current_rx_tone; + /*! \brief The timeout for switching from the high level to low level tone detector. */ + int high_low_timer; + + struct + { +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The z's for the notch filter */ + int32_t notch_z1[3]; + /*! \brief The z's for the notch filter */ + int32_t notch_z2[3]; +#else + /*! \brief The z's for the notch filter */ + float notch_z1[3]; + /*! \brief The z's for the notch filter */ + float notch_z2[3]; +#endif + + /*! \brief The z's for the notch integrators. */ + int32_t notch_zl; + } tone[2]; + +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The z's for the weighting/bandpass filter. */ + int32_t broad_z[3]; +#else + /*! \brief The z's for the weighting/bandpass filter. */ + float broad_z[3]; +#endif + /*! \brief The z for the broadband integrator. */ + int32_t broad_zl; + + /*! \brief ??? */ + int flat_mode; + /*! \brief ??? */ + int tone_present; + /*! \brief ??? */ + int notch_enabled; + /*! \brief ??? */ + int flat_mode_timeout; + /*! \brief ??? */ + int notch_insertion_timeout; + /*! \brief ??? */ + int tone_persistence_timeout; + + /*! \brief ??? */ + int signaling_state_duration; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/silence_gen.h b/Libraries/spandsp/spandsp/spandsp/private/silence_gen.h new file mode 100644 index 000000000..bae49defa --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/silence_gen.h @@ -0,0 +1,43 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/silence_gen.c - A silence generator, for inserting timed silences. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: silence_gen.h,v 1.1 2009/04/12 03:29:58 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SILENCE_GEN_H_) +#define _SPANDSP_PRIVATE_SILENCE_GEN_H_ + +struct silence_gen_state_s +{ + /*! \brief The callback function used to report status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + int remaining_samples; + int total_samples; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/super_tone_rx.h b/Libraries/spandsp/spandsp/spandsp/private/super_tone_rx.h new file mode 100644 index 000000000..09fbc694e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/super_tone_rx.h @@ -0,0 +1,67 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/super_tone_rx.h - Flexible telephony supervisory tone detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: super_tone_rx.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SUPER_TONE_RX_H_) +#define _SPANDSP_PRIVATE_SUPER_TONE_RX_H_ + +#define BINS 128 + +struct super_tone_rx_segment_s +{ + int f1; + int f2; + int recognition_duration; + int min_duration; + int max_duration; +}; + +struct super_tone_rx_descriptor_s +{ + int used_frequencies; + int monitored_frequencies; + int pitches[BINS/2][2]; + int tones; + super_tone_rx_segment_t **tone_list; + int *tone_segs; + goertzel_descriptor_t *desc; +}; + +struct super_tone_rx_state_s +{ + super_tone_rx_descriptor_t *desc; + float energy; + int detected_tone; + int rotation; + tone_report_func_t tone_callback; + void (*segment_callback)(void *data, int f1, int f2, int duration); + void *callback_data; + super_tone_rx_segment_t segments[11]; + goertzel_state_t state[]; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/super_tone_tx.h b/Libraries/spandsp/spandsp/spandsp/private/super_tone_tx.h new file mode 100644 index 000000000..007854483 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/super_tone_tx.h @@ -0,0 +1,52 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/super_tone_tx.h - Flexible telephony supervisory tone generation. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: super_tone_tx.h,v 1.1 2008/11/30 10:22:19 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SUPER_TONE_TX_H_) +#define _SPANDSP_PRIVATE_SUPER_TONE_TX_H_ + +struct super_tone_tx_step_s +{ + tone_gen_tone_descriptor_t tone[4]; + int tone_on; + int length; + int cycles; + super_tone_tx_step_t *next; + super_tone_tx_step_t *nest; +}; + +struct super_tone_tx_state_s +{ + tone_gen_tone_descriptor_t tone[4]; + uint32_t phase[4]; + int current_position; + int level; + super_tone_tx_step_t *levels[4]; + int cycles[4]; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/swept_tone.h b/Libraries/spandsp/spandsp/spandsp/private/swept_tone.h new file mode 100644 index 000000000..ce8e5a634 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/swept_tone.h @@ -0,0 +1,44 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/swept_tone.h - Swept tone generation + * + * Written by Steve Underwood + * + * Copyright (C) 2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: swept_tone.h,v 1.1 2009/09/22 12:54:33 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_SWEPT_TONE_H_) +#define _SPANDSP_PRIVATE_SWEPT_TONE_H_ + +struct swept_tone_state_s +{ + int32_t starting_phase_inc; + int32_t phase_inc_step; + int scale; + int duration; + int repeating; + int pos; + int32_t current_phase_inc; + uint32_t phase; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t30.h b/Libraries/spandsp/spandsp/spandsp/private/t30.h new file mode 100644 index 000000000..64ae2c5fd --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t30.h @@ -0,0 +1,286 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t30.h - definitions for T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30.h,v 1.5.4.1 2009/12/19 09:47:56 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_T30_H_) +#define _SPANDSP_PRIVATE_T30_H_ + +/*! + T.30 FAX channel descriptor. This defines the state of a single working + instance of a T.30 FAX channel. +*/ +struct t30_state_s +{ + /* This must be kept the first thing in the structure, so it can be pointed + to reliably as the structures change over time. */ + /*! \brief T.4 context for reading or writing image data. */ + t4_state_t t4; + + /*! \brief The type of FAX operation currently in progress */ + int operation_in_progress; + + /*! \brief TRUE if behaving as the calling party */ + int calling_party; + + /*! \brief The received DCS, formatted as an ASCII string, for inclusion + in the TIFF file. */ + char rx_dcs_string[T30_MAX_DIS_DTC_DCS_LEN*3 + 1]; + /*! \brief The text which will be used in FAX page header. No text results + in no header line. */ + char header_info[T30_MAX_PAGE_HEADER_INFO + 1]; + /*! \brief The information fields received. */ + t30_exchanged_info_t rx_info; + /*! \brief The information fields to be transmitted. */ + t30_exchanged_info_t tx_info; + /*! \brief The country of origin of the remote machine, if known, else NULL. */ + const char *country; + /*! \brief The vendor of the remote machine, if known, else NULL. */ + const char *vendor; + /*! \brief The model of the remote machine, if known, else NULL. */ + const char *model; + + /*! \brief A pointer to a callback routine to be called when phase B events + occur. */ + t30_phase_b_handler_t *phase_b_handler; + /*! \brief An opaque pointer supplied in event B callbacks. */ + void *phase_b_user_data; + /*! \brief A pointer to a callback routine to be called when phase D events + occur. */ + t30_phase_d_handler_t *phase_d_handler; + /*! \brief An opaque pointer supplied in event D callbacks. */ + void *phase_d_user_data; + /*! \brief A pointer to a callback routine to be called when phase E events + occur. */ + t30_phase_e_handler_t *phase_e_handler; + /*! \brief An opaque pointer supplied in event E callbacks. */ + void *phase_e_user_data; + /*! \brief A pointer to a callback routine to be called when frames are + exchanged. */ + t30_real_time_frame_handler_t *real_time_frame_handler; + /*! \brief An opaque pointer supplied in real time frame callbacks. */ + void *real_time_frame_user_data; + + /*! \brief A pointer to a callback routine to be called when document events + (e.g. end of transmitted document) occur. */ + t30_document_handler_t *document_handler; + /*! \brief An opaque pointer supplied in document callbacks. */ + void *document_user_data; + + /*! \brief The handler for changes to the receive mode */ + t30_set_handler_t *set_rx_type_handler; + /*! \brief An opaque pointer passed to the handler for changes to the receive mode */ + void *set_rx_type_user_data; + /*! \brief The handler for changes to the transmit mode */ + t30_set_handler_t *set_tx_type_handler; + /*! \brief An opaque pointer passed to the handler for changes to the transmit mode */ + void *set_tx_type_user_data; + + /*! \brief The transmitted HDLC frame handler. */ + t30_send_hdlc_handler_t *send_hdlc_handler; + /*! \brief An opaque pointer passed to the transmitted HDLC frame handler. */ + void *send_hdlc_user_data; + + /*! \brief The DIS code for the minimum scan row time we require. This is usually 0ms, + but if we are trying to simulate another type of FAX machine, we may need a non-zero + value here. */ + uint8_t local_min_scan_time_code; + + /*! \brief The current T.30 phase. */ + int phase; + /*! \brief The T.30 phase to change to when the current phase ends. */ + int next_phase; + /*! \brief The current state of the T.30 state machine. */ + int state; + /*! \brief The step in sending a sequence of HDLC frames. */ + int step; + + /*! \brief The preparation buffer for the DCS message to be transmitted. */ + uint8_t dcs_frame[T30_MAX_DIS_DTC_DCS_LEN]; + /*! \brief The length of the DCS message to be transmitted. */ + int dcs_len; + /*! \brief The preparation buffer for DIS or DTC message to be transmitted. */ + uint8_t local_dis_dtc_frame[T30_MAX_DIS_DTC_DCS_LEN]; + /*! \brief The length of the DIS or DTC message to be transmitted. */ + int local_dis_dtc_len; + /*! \brief The last DIS or DTC message received form the far end. */ + uint8_t far_dis_dtc_frame[T30_MAX_DIS_DTC_DCS_LEN]; + /*! \brief The length of the last DIS or DTC message received form the far end. */ + int far_dis_dtc_len; + /*! \brief TRUE if a valid DIS has been received from the far end. */ + int dis_received; + + /*! \brief A flag to indicate a message is in progress. */ + int in_message; + + /*! \brief TRUE if the short training sequence should be used. */ + int short_train; + + /*! \brief A count of the number of bits in the trainability test. This counts down to zero when + sending TCF, and counts up when receiving it. */ + int tcf_test_bits; + /*! \brief The current count of consecutive received zero bits, during the trainability test. */ + int tcf_current_zeros; + /*! \brief The maximum consecutive received zero bits seen to date, during the trainability test. */ + int tcf_most_zeros; + + /*! \brief The current fallback step for the fast message transfer modem. */ + int current_fallback; + /*! \brief The subset of supported modems allowed at the current time, allowing for negotiation. */ + int current_permitted_modems; + /*! \brief TRUE if a carrier is present. Otherwise FALSE. */ + int rx_signal_present; + /*! \brief TRUE if a modem has trained correctly. */ + int rx_trained; + /*! \brief TRUE if a valid HDLC frame has been received in the current reception period. */ + int rx_frame_received; + + /*! \brief Current reception mode. */ + int current_rx_type; + /*! \brief Current transmission mode. */ + int current_tx_type; + + /*! \brief T0 is the answer timeout when calling another FAX machine. + Placing calls is handled outside the FAX processing, but this timeout keeps + running until V.21 modulation is sent or received. + T1 is the remote terminal identification timeout (in audio samples). */ + int timer_t0_t1; + /*! \brief T2, T2A and T2B are the HDLC command timeouts. + T4, T4A and T4B are the HDLC response timeouts (in audio samples). */ + int timer_t2_t4; + /*! \brief A value specifying which of the possible timers is currently running in timer_t2_t4 */ + int timer_t2_t4_is; + /*! \brief Procedural interrupt timeout (in audio samples). */ + int timer_t3; + /*! \brief This is only used in error correcting mode. */ + int timer_t5; + /*! \brief This is only used in full duplex (e.g. ISDN) modes. */ + int timer_t6; + /*! \brief This is only used in full duplex (e.g. ISDN) modes. */ + int timer_t7; + /*! \brief This is only used in full duplex (e.g. ISDN) modes. */ + int timer_t8; + + /*! \brief TRUE once the far end FAX entity has been detected. */ + int far_end_detected; + + /*! \brief TRUE if a local T.30 interrupt is pending. */ + int local_interrupt_pending; + /*! \brief The image coding being used on the line. */ + int line_encoding; + /*! \brief The image coding being used for output files. */ + int output_encoding; + /*! \brief The current DCS message minimum scan time code. */ + uint8_t min_scan_time_code; + /*! \brief The X direction resolution of the current image, in pixels per metre. */ + int x_resolution; + /*! \brief The Y direction resolution of the current image, in pixels per metre. */ + int y_resolution; + /*! \brief The width of the current image, in pixels. */ + t4_image_width_t image_width; + /*! \brief Current number of retries of the action in progress. */ + int retries; + /*! \brief TRUE if error correcting mode is used. */ + int error_correcting_mode; + /*! \brief The number of HDLC frame retries, if error correcting mode is used. */ + int error_correcting_mode_retries; + /*! \brief The current count of consecutive T30_PPR messages. */ + int ppr_count; + /*! \brief The current count of consecutive T30_RNR messages. */ + int receiver_not_ready_count; + /*! \brief The number of octets to be used per ECM frame. */ + int octets_per_ecm_frame; + /*! \brief The ECM partial page buffer. */ + uint8_t ecm_data[256][260]; + /*! \brief The lengths of the frames in the ECM partial page buffer. */ + int16_t ecm_len[256]; + /*! \brief A bit map of the OK ECM frames, constructed as a PPR frame. */ + uint8_t ecm_frame_map[3 + 32]; + + /*! \brief The current page number for receiving, in ECM or non-ECM mode. This is reset at the start of a call. */ + int rx_page_number; + /*! \brief The current page number for sending, in ECM or non-ECM mode. This is reset at the start of a call. */ + int tx_page_number; + /*! \brief The current block number, in ECM mode */ + int ecm_block; + /*! \brief The number of frames in the current block number, in ECM mode */ + int ecm_frames; + /*! \brief The number of frames sent in the current burst of image transmission, in ECM mode */ + int ecm_frames_this_tx_burst; + /*! \brief The current ECM frame, during ECM transmission. */ + int ecm_current_tx_frame; + /*! \brief TRUE if we are at the end of an ECM page to se sent - i.e. there are no more + partial pages still to come. */ + int ecm_at_page_end; + + /*! \brief The transmission step queued to follow the one in progress. */ + int next_tx_step; + /*! \brief The FCF for the next receive step. */ + uint8_t next_rx_step; + /*! \brief Image file name for image reception. */ + char rx_file[256]; + /*! \brief The last page we are prepared accept for a received image file. -1 means no restriction. */ + int rx_stop_page; + /*! \brief Image file name to be sent. */ + char tx_file[256]; + /*! \brief The first page to be sent from the image file. -1 means no restriction. */ + int tx_start_page; + /*! \brief The last page to be sent from the image file. -1 means no restriction. */ + int tx_stop_page; + /*! \brief The current completion status. */ + int current_status; + /*! \brief Internet aware FAX mode bit mask. */ + int iaf; + /*! \brief A bit mask of the currently supported modem types. */ + int supported_modems; + /*! \brief A bit mask of the currently supported image compression modes. */ + int supported_compressions; + /*! \brief A bit mask of the currently supported image resolutions. */ + int supported_resolutions; + /*! \brief A bit mask of the currently supported image sizes. */ + int supported_image_sizes; + /*! \brief A bit mask of the currently supported T.30 special features. */ + int supported_t30_features; + /*! \brief TRUE is ECM mode handling is enabled. */ + int ecm_allowed; + + /*! \brief the FCF2 field of the last PPS message we received. */ + uint8_t last_pps_fcf2; + /*! \brief The number of the first ECM frame which we do not currently received correctly. For + a partial page received correctly, this will be one greater than the number of frames it + contains. */ + int ecm_first_bad_frame; + /*! \brief A count of successfully received ECM frames, to assess progress as a basis for + deciding whether to continue error correction when PPRs keep repeating. */ + int ecm_progress; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t30_dis_dtc_dcs_bits.h b/Libraries/spandsp/spandsp/spandsp/private/t30_dis_dtc_dcs_bits.h new file mode 100644 index 000000000..7db4df98e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t30_dis_dtc_dcs_bits.h @@ -0,0 +1,387 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t30_dis_dtc_dcs_bits.h - ITU T.30 fax control bits definitions + * + * Written by Steve Underwood + * + * Copyright (C) 2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30_dis_dtc_dcs_bits.h,v 1.1.4.1 2009/12/19 09:47:57 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_T30_DIS_DTC_DCS_BITS_H_) +#define _SPANDSP_PRIVATE_T30_DIS_DTC_DCS_BITS_H_ + +/* Indicates that the terminal has the Simple mode capability defined in ITU-T Rec. T.37. + Internet address signals CIA, TSA or CSA can be sent and received. The recipient terminal + may process or ignore this signal. */ +#define T30_DIS_BIT_T37 1 +#define T30_DCS_BIT_T37 1 + +/* Indicates that the terminal has the capability to communicate using ITU-T Rec. T.38. + Internet address signals CIA, TSA or CSA can be sent and received. The recipient terminal + may process or ignore this signal. */ +#define T30_DIS_BIT_T38 3 +#define T30_DCS_BIT_T38 3 + +/* Bit 4 set to "1" indicates 3rd Generation Mobile Network Access to the GSTN Connection. + Bit 4 set to "0" conveys no information about the type of connection. */ +#define T30_DIS_BIT_3G_MOBILE 4 +#define T30_DCS_BIT_3G_MOBILE 4 + +/* When ISDN mode is used, in DIS/DTC bit 6 shall be set to "0". */ +#define T30_DIS_BIT_V8_CAPABILITY 6 + +/* When ISDN mode is used, in DIS/DTC bit 7 shall be set to "0". */ +#define T30_DIS_BIT_64_OCTET_ECM_FRAMES_PREFERRED 7 + +/* Bit 9 indicates that there is a facsimile document ready to be polled from the answering + terrminal. It is not an indication of a capability. */ +#define T30_DIS_BIT_READY_TO_TRANSMIT_FAX_DOCUMENT 9 + +/* In DIS/DTC bit 10 indicates that the answering terminal has receiving capabilities. + In DCS it is a command to the receiving terminal to set itself in the receive mode. */ +#define T30_DIS_BIT_READY_TO_RECEIVE_FAX_DOCUMENT 10 +#define T30_DCS_BIT_RECEIVE_FAX_DOCUMENT 10 + +/* Bits 11, 12, 13, 14 - modem type */ + +#define T30_DIS_BIT_200_200_CAPABLE 15 +#define T30_DCS_BIT_200_200 15 + +#define T30_DIS_BIT_2D_CAPABLE 16 +#define T30_DCS_BIT_2D_CODING 16 + +/* Standard facsimile terminals conforming to ITU-T Rec. T.4 must have the following capability: + Paper length = 297 mm. */ + +/* Bits 17, 18 - recording width */ + +/* Bits 19, 20 - paper length */ + +/* Bits 21, 22, 23 - min scan line time */ + +/* When ISDN mode is used, in DIS/DTC bits 21 to 23 shall be set to "1". */ + +#define T30_DIS_BIT_UNCOMPRESSED_CAPABLE 26 +#define T30_DCS_BIT_UNCOMPRESSED_MODE 26 + +/* When ISDN mode is used, in DIS/DTC bit 27 shall be set to "1". */ +#define T30_DIS_BIT_ECM_CAPABLE 27 +#define T30_DCS_BIT_ECM 27 + +/* The value of bit 28 in the DCS command is only valid when ECM is selected. */ +#define T30_DCS_BIT_64_OCTET_ECM_FRAMES 28 + +/* The value of bit 31 in the DCS command is only valid when ECM is selected. */ +#define T30_DIS_BIT_T6_CAPABLE 31 +#define T30_DCS_BIT_T6_MODE 31 + +#define T30_DIS_BIT_FNV_CAPABLE 33 +#define T30_DCS_BIT_FNV_CAPABLE 33 + +#define T30_DIS_BIT_MULTIPLE_SELECTIVE_POLLING_CAPABLE 34 + +#define T30_DIS_BIT_POLLED_SUBADDRESSING_CAPABLE 35 + +#define T30_DIS_BIT_T43_CAPABLE 36 +#define T30_DCS_BIT_T43_CODING 36 + +#define T30_DIS_BIT_PLANE_INTERLEAVE_CAPABLE 37 +#define T30_DCS_BIT_PLANE_INTERLEAVE 37 + +#define T30_DIS_BIT_G726_CAPABLE 38 +#define T30_DCS_BIT_G726 38 + +#define T30_DIS_BIT_200_400_CAPABLE 41 +#define T30_DCS_BIT_200_400 41 + +#define T30_DIS_BIT_300_300_CAPABLE 42 +#define T30_DCS_BIT_300_300 42 + +#define T30_DIS_BIT_400_400_CAPABLE 43 +#define T30_DCS_BIT_400_400 43 + +/* Bits 44 and 45 are used only in conjunction with bits 15 and 43. Bit 44 in DCS, when used, + shall correctly indicate the resolution of the transmitted document, which means that bit 44 in DCS may + not always match the indication of bits 44 and 45 in DIS/DTC. Cross selection will cause the distortion + and reduction of reproducible area. + If a receiver indicates in DIS that it prefers to receive metric-based information, but the transmitter has + only the equivalent inch-based information (or vice versa), then communication shall still take place. + Bits 44 and 45 do not require the provision of any additional features on the terminal to + indicate to the sending or receiving user whether the information was transmitted or received on a metric-metric, + inch-inch, metric-inch, inch-metric basis. */ + +#define T30_DIS_BIT_INCH_RESOLUTION_PREFERRED 44 +#define T30_DCS_BIT_INCH_RESOLUTION 44 + +#define T30_DIS_BIT_METRIC_RESOLUTION_PREFERRED 45 + +#define T30_DIS_BIT_MIN_SCAN_TIME_HALVES 46 + +#define T30_DIS_BIT_SELECTIVE_POLLING_CAPABLE 47 + +#define T30_DIS_BIT_SUBADDRESSING_CAPABLE 49 +#define T30_DCS_BIT_SUBADDRESS_TRANSMISSION 49 + +#define T30_DIS_BIT_PASSWORD 50 +#define T30_DCS_BIT_SENDER_ID_TRANSMISSION 50 + +/* Bit 51 indicates that there is a data file ready to be polled from the answering terminal. It is + not an indication of a capability. This bit is used in conjunction with bits 53, 54, 55 and 57. */ +#define T30_DIS_BIT_READY_TO_TRANSMIT_DATA_FILE 51 + +/* The binary file transfer protocol is described in ITU-T Rec. T.434. */ +#define T30_DIS_BIT_BFT_CAPABLE 53 +#define T30_DCS_BIT_BFT 53 + +#define T30_DIS_BIT_DTM_CAPABLE 54 +#define T30_DCS_BIT_DTM 54 + +#define T30_DIS_BIT_EDI_CAPABLE 55 +#define T30_DCS_BIT_EDI 55 + +#define T30_DIS_BIT_BTM_CAPABLE 57 +#define T30_DCS_BIT_BTM 57 + +/* Bit 59 indicates that there is a character-coded or mixed-mode document ready to be polled + from the answering terminal. It is not an indication of a capability. This bit is used in + conjunction with bits 60, 62 and 65. */ +#define T30_DIS_BIT_READY_TO_TRANSMIT_MIXED_MODE_DOCUMENT 59 + +#define T30_DIS_BIT_CHARACTER_MODE 60 +#define T30_DCS_BIT_CHARACTER_MODE 60 + +#define T30_DIS_BIT_MIXED_MODE 62 +#define T30_DCS_BIT_MIXED_MODE 62 + +#define T30_DIS_BIT_PROCESSABLE_MODE_26 65 + +#define T30_DIS_BIT_DIGITAL_NETWORK_CAPABLE 66 +#define T30_DCS_BIT_DIGITAL_NETWORK_CAPABLE 66 + +#define T30_DIS_BIT_DUPLEX_CAPABLE 67 +#define T30_DCS_BIT_DUPLEX_CAPABLE 67 + +#define T30_DIS_BIT_T81_CAPABLE 68 +#define T30_DCS_BIT_FULL_COLOUR_MODEX 68 + +#define T30_DIS_BIT_FULL_COLOUR_CAPABLE 69 +#define T30_DCS_BIT_FULL_COLOUR_MODE 69 + +/* In a DIS/DTC frame, setting bit 71 to "0" indicates that the called terminal can only accept + image data which has been digitized to 8 bits/pel/component for JPEG mode. This is also true for T.43 + mode if bit 36 is also set to "1". Setting bit 71 to "1" indicates that the called terminal can also accept + image data that are digitized to 12 bits/pel/component for JPEG mode. This is also true for T.43 mode if + bit 36 is also set to "1". In a DCS frame, setting bit 71 to "0" indicates that the calling terminal's image + data are digitized to 8 bits/pel/component for JPEG mode. This is also true for T.43 mode if bit 36 is also + set to "1". Setting bit 71 to "1" indicates that the calling terminal transmits image data which has been + digitized to 12 bits/pel/component for JPEG mode. This is also true for T.43 mode if bit 36 is also set + to "1". */ +#define T30_DIS_BIT_12BIT_CAPABLE 71 +#define T30_DCS_BIT_12BIT_COMPONENT 71 + +#define T30_DIS_BIT_NO_SUBSAMPLING 73 +#define T30_DCS_BIT_NO_SUBSAMPLING 73 + +#define T30_DIS_BIT_CUSTOM_ILLUMINANT 74 +#define T30_DCS_BIT_CUSTOM_ILLUMINANT 74 + +#define T30_DIS_BIT_CUSTOM_GAMUT_RANGE 75 +#define T30_DCS_BIT_CUSTOM_GAMUT_RANGE 75 + +#define T30_DIS_BIT_NORTH_AMERICAN_LETTER_CAPABLE 76 +#define T30_DCS_BIT_NORTH_AMERICAN_LETTER 76 + +#define T30_DIS_BIT_NORTH_AMERICAN_LEGAL_CAPABLE 77 +#define T30_DCS_BIT_NORTH_AMERICAN_LEGAL 77 + +#define T30_DIS_BIT_T85_CAPABLE 78 +#define T30_DCS_BIT_T85_MODE 78 + +#define T30_DIS_BIT_T85_L0_CAPABLE 79 +#define T30_DCS_BIT_T85_L0_MODE 79 + +/* In a DIS/DTC frame, setting bit 97 to "0" indicates that the called terminal does not have the + capability to accept 300 pels/25.4 mm x 300 lines/25.4 mm or 400 pels/25.4 mm x 400 lines/25.4 mm + resolutions for colour/gray-scale images or T.44 Mixed Raster Content (MRC) mask layer. + + Setting bit 97 to "1" indicates that the called terminal does have the capability to accept + 300 pels/25.4 mm x 300 lines/25.4 mm or 400 pels/25.4 mm x 400 lines/25.4 mm resolutions for + colour/gray-scale images and MRC mask layer. Bit 97 is valid only when bits 68 and 42 or 43 + (300 pels/25.4 mm x 300 lines/25.4 mm or 400 pels/25.4 mm x 400 lines/25.4 mm) are set to "1". + + In a DCS frame, setting bit 97 to "0" indicates that the calling terminal does not use + 300 pels/25.4 mm x 300 lines/25.4 mm or 400 pels/25.4 mm x 400 lines/25.4 mm resolutions + for colour/gray-scale images and mask layer. + + Setting bit 97 to "1" indicates that the calling terminal uses 300 pels/25.4 mm x 300 lines/25.4 mm + or 400 pels/25.4 mm x 400 lines/25.4 mm resolutions for colour/gray-scale images and MRC mask layer. + Bit 97 is valid only when bits 68 and 42 or 43 (300 pels/25.4 mm x 300 lines/25.4 mm and + 400 pels/25.4 mm x 400 lines/25.4 mm) are set to "1". + + In a DIS/DTC frame, combinations of bit 42, bit 43 and bit 97 indicate that the called terminal + has higher resolution capabilities as follows: + + Resolution capabilities (pels/25.4 mm) + DIS/DTC Monochrome Colour/gray-scale + 42 43 97 300 x 300 400 x 400 300 x 300 400 x 400 + 0 0 0 no no no no + 1 0 0 yes no no no + 0 1 0 no yes no no + 1 1 0 yes yes no no + 0 0 1 (invalid) + 1 0 1 yes no yes no + 0 1 1 no yes no yes + 1 1 1 yes yes yes yes + "yes" means that the called terminal has the corresponding capability. + "no" means that the called terminal does not have the corresponding capability. */ +#define T30_DIS_BIT_COLOUR_GREY_300_300_400_400_CAPABLE 97 +#define T30_DCS_BIT_COLOUR_GREY_300_300_400_400 97 + +/* In a DIS/DTC frame, setting bit 98 to "0" indicates that the called terminal does not have the + capability to accept 100 pels/25.4 mm x 100 lines/25.4 mm spatial resolution for colour or gray-scale + images. Setting bit 98 to "1" indicates that the called terminal does have the capability to accept + 100 pels/25.4 mm x 100 lines/25.4 mm spatial resolution for colour or gray-scale images. Bit 98 is valid + only when bit 68 is set to "1". In a DCS frame, setting bit 98 to "0" indicates that the calling terminal does + not use 100 pels/25.4 mm x 100 lines/25.4 mm spatial resolution for colour or gray-scale images. Setting + bit 98 to "1" indicates that the calling terminal uses 100 pels/25.4 mm x 100 lines/25.4 mm spatial + resolution for colour or gray-scale images. */ +#define T30_DIS_BIT_COLOUR_GREY_100_100_CAPABLE 98 +#define T30_DCS_BIT_COLOUR_GREY_100_100 98 + +/* To provide an error recovery mechanism, when PWD/SEP/SUB/SID/PSA/IRA/ISP frames are sent with DCS or DTC, + bits 49, 102 and 50 in DCS or bits 47, 101, 50 and 35 in DTC shall be set to "1" with the following + meaning: + Bit DIS DTC DCS + 35 Polled SubAddress capability Polled SubAddress transmission Not allowed – set to "0" + 47 Selective polling capability Selective polling transmission Not allowed – set to "0" + 49 Subaddressing capability Not allowed (Set to "0") Subaddressing transmission + 50 Password Password transmission Sender Identification transmission + 101 Internet Selective Polling Address capability Internet Selective Polling Address transmission Not allowed – set to "0" + 102 Internet Routing Address capability Not allowed (Set to "0") Internet Routing Address transmission + + Terminals conforming to the 1993 version of T.30 may set the above bits to "0" even though PWD/SEP/SUB + frames are transmitted. */ +#define T30_DIS_BIT_INTERNET_SELECTIVE_POLLING_ADDRESS 101 + +#define T30_DIS_BIT_INTERNET_ROUTING_ADDRESS 102 +#define T30_DCS_BIT_INTERNET_ROUTING_ADDRESS_TRANSMISSION 102 + +#define T30_DIS_BIT_600_600_CAPABLE 105 +#define T30_DCS_BIT_600_600 105 + +#define T30_DIS_BIT_1200_1200_CAPABLE 106 +#define T30_DCS_BIT_1200_1200 106 + +#define T30_DIS_BIT_300_600_CAPABLE 107 +#define T30_DCS_BIT_300_600 107 + +#define T30_DIS_BIT_400_800_CAPABLE 108 +#define T30_DCS_BIT_400_800 108 + +#define T30_DIS_BIT_600_1200_CAPABLE 109 +#define T30_DCS_BIT_600_1200 109 + +#define T30_DIS_BIT_COLOUR_GREY_600_600_CAPABLE 110 +#define T30_DCS_BIT_COLOUR_GREY_600_600 110 + +#define T30_DIS_BIT_COLOUR_GREY_1200_1200_CAPABLE 111 +#define T30_DCS_BIT_COLOUR_GREY_1200_1200 111 + +#define T30_DIS_BIT_ALTERNATE_DOUBLE_SIDED_CAPABLE 113 +#define T30_DCS_BIT_ALTERNATE_DOUBLE_SIDED_CAPABLE 113 + +#define T30_DIS_BIT_CONTINUOUS_DOUBLE_SIDED_CAPABLE 114 +#define T30_DCS_BIT_CONTINUOUS_DOUBLE_SIDED_CAPABLE 114 + +#define T30_DIS_BIT_BLACK_AND_WHITE_MRC 115 + +#define T30_DIS_BIT_T45_CAPABLE 116 +#define T30_DCS_BIT_T45_MODE 116 + +/* This bit defines the available colour space, when bit 92, 93 or 94 is set to "1". + Available colour space for all combinations of bits 92, 93, 94 and 119 are shown in the following table. + It should be noted that terminals which conform to the 2003 and earlier versions of this Recommendation + will send LAB with "1" in bit 92, 93 or 94 even if bit 119 is set to "1". + + Available colour space for DIS/DTC bits 92, 93, 94 and 119 + + 92 93 94 119 Mode of T.44 Available colour space + 0 0 0 x Not available - + 1 0 0 0 Mode 1 LAB only + 1 0 0 1 Mode 1 YCC only + x 1 x 0 Mode 2 or higher LAB only + x x 1 0 Mode 2 or higher LAB only + x 1 x 1 Mode 2 or higher LAB and YCC + x x 1 1 Mode 2 or higher LAB and YCC + + Colour space for DCS bits 92, 93, 94 and 119 + + 92 93 94 119 Mode of T.44 Colour space + 0 0 0 x* Not available - + 1 0 0 0 Mode 1 LAB + 1 0 0 1 Mode 1 YCC + x 1 x 0 Mode 2 or higher LAB + x x 1 0 Mode 2 or higher LAB + x 1 x 1 Mode 2 or higher YCC or mixing of YCC and LAB + x x 1 1 Mode 2 or higher YCC or mixing of YCC and LAB */ +#define T30_DIS_BIT_T44_COLOUR_SPACE 119 +#define T30_DCS_BIT_T44_COLOUR_SPACE 119 + +/* Can only be set in the communication through the T.38 gateway, to cope with delay of network. + T.x timer (12+-1s) should be used after emitting RNR or TNR. However, after receiving + PPS signal in ECM mode, T.5 timer should be used. */ +#define T30_DIS_BIT_T38_FLOW_CONTROL_CAPABLE 121 +#define T30_DCS_BIT_T38_FLOW_CONTROL_CAPABLE 121 + +/* For resolutions greater than 200 lines/25.4 mm, 4.2.1.1/T.4 specifies the use of specific K + factors for each standardized vertical resolution. To ensure backward compatibility with earlier + versions of ITU-T Rec. T.4, bit 122 indicates when such K factors are being used. */ +#define T30_DIS_BIT_K_GREATER_THAN_4 122 + +/* This bit should be set to "1" if the fax device is an Internet-Aware Fax Device as defined in + ITU-T Rec. T.38 and if it is not affected by the data signal rate indicated by the DIS and DTC + signals when communicating with another Internet-Aware Fax Device operating in T.38 mode. This + bit shall not be used in GSTN mode. */ +#define T30_DIS_BIT_T38_FAX_CAPABLE 123 +/* This bit should be set to "1" if the fax device elects to operate in an Internet-Aware Fax mode + as defined in ITU-T Rec. T.38 in response to a device which has set the related DIS bit to "1". + When this bit is set to "1", the data signal rate of the modem (bits 11-14) should be set to "0". */ +#define T30_DCS_BIT_T38_FAX_MODE 123 + +/* When either bit of 31, 36, 38, 51, 53, 54, 55, 57, 59, 60, 62, 65, 68, 78, 79, 115, 116 and 127 is + set to "1", ECM must be used. If the value of bit field 92 to 94 is non-zero, then ECM must be used. + + Annex K describes the optional continuous-tone colour and gray scale images mode + (sYCC-JPEG mode) protocol. When bit 127 in DIS/DTC frame is set to "1", the called terminal has the + capability to accept sYCC-JPEG mode. This is defined with complete independent in the colour space + CIELAB. In addition, when bit 127 in DCS frame is set to "1", ECM must be used and bits 15, 17, 18, + 19, 20, 41, 42, 43, 45, 46, 68, 69, 71, 73, 74, 75, 76, 77, 97, 98, 105, 106, 107, 108, + 109, 110 and 111 in DCS frame are "Don't care", and should be set to "0". In the case of + transmission of multiple images, a post message signal PPS-MPS between pages, PPS-NULL between + partial pages and PPS-EOP following the last page should be sent from the calling terminal to the + called terminal. */ +#define T30_DIS_BIT_SYCC_T81_CAPABLE 127 +#define T30_DCS_BIT_SYCC_T81_MODE 127 + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t31.h b/Libraries/spandsp/spandsp/spandsp/private/t31.h new file mode 100644 index 000000000..c061e0121 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t31.h @@ -0,0 +1,199 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t31.h - A T.31 compatible class 1 FAX modem interface. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t31.h,v 1.7 2009/02/12 12:38:39 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_T31_H_) +#define _SPANDSP_PRIVATE_T31_H_ + +/*! + Analogue FAX front end channel descriptor. This defines the state of a single working + instance of an analogue line FAX front end. +*/ +typedef struct +{ + fax_modems_state_t modems; + + /*! The transmit signal handler to be used when the current one has finished sending. */ + span_tx_handler_t *next_tx_handler; + void *next_tx_user_data; + + /*! \brief No of data bits in current_byte. */ + int bit_no; + /*! \brief The current data byte in progress. */ + int current_byte; + + /*! \brief Rx power meter, used to detect silence. */ + power_meter_t rx_power; + /*! \brief Last sample, used for an elementary HPF for the power meter. */ + int16_t last_sample; + /*! \brief The current silence threshold. */ + int32_t silence_threshold_power; + + /*! \brief Samples of silence heard */ + int silence_heard; +} t31_audio_front_end_state_t; + +/*! + Analogue FAX front end channel descriptor. This defines the state of a single working + instance of an analogue line FAX front end. +*/ +typedef struct +{ + /*! \brief Internet Aware FAX mode bit mask. */ + int iaf; + /*! \brief Required time between T.38 transmissions, in ms. */ + int ms_per_tx_chunk; + /*! \brief Bit fields controlling the way data is packed into chunked for transmission. */ + int chunking_modes; + + /*! \brief Core T.38 IFP support */ + t38_core_state_t t38; + + /*! \brief The current transmit step being timed */ + int timed_step; + + /*! \brief TRUE is there has been some T.38 data missed */ + int rx_data_missing; + + /*! \brief The number of octets to send in each image packet (non-ECM or ECM) at the current + rate and the current specified packet interval. */ + int octets_per_data_packet; + + /*! \brief An HDLC context used when sending HDLC messages to the terminal port + (ECM mode support). */ + hdlc_tx_state_t hdlc_tx_term; + /*! \brief An HDLC context used when receiving HDLC messages from the terminal port. + (ECM mode support). */ + hdlc_rx_state_t hdlc_rx_term; + + struct + { + uint8_t buf[T31_T38_MAX_HDLC_LEN]; + int len; + } hdlc_rx; + + struct + { + /*! \brief The number of extra bits in a fully stuffed version of the + contents of the HDLC transmit buffer. This is needed to accurately + estimate the playout time for this frame, through an analogue modem. */ + int extra_bits; + } hdlc_tx; + + /*! \brief TRUE if we are using ECM mode. This is used to select HDLC faking, necessary + with clunky class 1 modems. */ + int ecm_mode; + + /*! \brief Counter for trailing non-ECM bytes, used to flush out the far end's modem. */ + int non_ecm_trailer_bytes; + + /*! \brief The next queued tramsit indicator */ + int next_tx_indicator; + /*! \brief The current T.38 data type being transmitted */ + int current_tx_data_type; + + /*! \brief The current operating mode of the receiver. */ + int current_rx_type; + /*! \brief The current operating mode of the transmitter. */ + int current_tx_type; + + /*! \brief Current transmission bit rate. */ + int tx_bit_rate; + /*! \brief A "sample" count, used to time events. */ + int32_t samples; + /*! \brief The value for samples at the next transmission point. */ + int32_t next_tx_samples; + /*! \brief The current receive timeout. */ + int32_t timeout_rx_samples; +} t31_t38_front_end_state_t; + +/*! + T.31 descriptor. This defines the working state for a single instance of + a T.31 FAX modem. +*/ +struct t31_state_s +{ + at_state_t at_state; + t31_modem_control_handler_t *modem_control_handler; + void *modem_control_user_data; + + t31_audio_front_end_state_t audio; + t31_t38_front_end_state_t t38_fe; + /*! TRUE if working in T.38 mode. */ + int t38_mode; + + /*! HDLC buffer, for composing an HDLC frame from the computer to the channel. */ + struct + { + uint8_t buf[T31_MAX_HDLC_LEN]; + int len; + int ptr; + /*! \brief TRUE when the end of HDLC data from the computer has been detected. */ + int final; + } hdlc_tx; + /*! Buffer for data from the computer to the channel. */ + struct + { + uint8_t data[T31_TX_BUF_LEN]; + /*! \brief The number of bytes stored in transmit buffer. */ + int in_bytes; + /*! \brief The number of bytes sent from the transmit buffer. */ + int out_bytes; + /*! \brief TRUE if the flow of real data has started. */ + int data_started; + /*! \brief TRUE if holding up further data into the buffer, for flow control. */ + int holding; + /*! \brief TRUE when the end of non-ECM data from the computer has been detected. */ + int final; + } tx; + + /*! TRUE if DLE prefix just used */ + int dled; + + /*! \brief Samples of silence awaited, as specified in a "wait for silence" command */ + int silence_awaited; + + /*! \brief The current bit rate for the FAX fast message transfer modem. */ + int bit_rate; + /*! \brief TRUE if a valid HDLC frame has been received in the current reception period. */ + int rx_frame_received; + + /*! \brief Samples elapsed in the current call */ + int64_t call_samples; + int64_t dte_data_timeout; + + /*! \brief The currently queued modem type. */ + int modem; + /*! \brief TRUE when short training mode has been selected by the computer. */ + int short_train; + queue_state_t *rx_queue; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t38_core.h b/Libraries/spandsp/spandsp/spandsp/private/t38_core.h new file mode 100644 index 000000000..2de5e8aa0 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t38_core.h @@ -0,0 +1,135 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t38_core.h - An implementation of T.38, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_core.h,v 1.4 2009/07/14 13:54:22 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_T38_CORE_H_) +#define _SPANDSP_PRIVATE_T38_CORE_H_ + +/*! + Core T.38 state, common to all modes of T.38. +*/ +struct t38_core_state_s +{ + /*! \brief Handler routine to transmit IFP packets generated by the T.38 protocol engine */ + t38_tx_packet_handler_t *tx_packet_handler; + /*! \brief An opaque pointer passed to tx_packet_handler */ + void *tx_packet_user_data; + + /*! \brief Handler routine to process received indicator packets */ + t38_rx_indicator_handler_t *rx_indicator_handler; + /*! \brief Handler routine to process received data packets */ + t38_rx_data_handler_t *rx_data_handler; + /*! \brief Handler routine to process the missing packet condition */ + t38_rx_missing_handler_t *rx_missing_handler; + /*! \brief An opaque pointer passed to any of the above receive handling routines */ + void *rx_user_data; + + /*! NOTE - Bandwidth reduction shall only be done on suitable Phase C data, i.e., MH, MR + and - in the case of transcoding to JBIG - MMR. MMR and JBIG require reliable data + transport such as that provided by TCP. When transcoding is selected, it shall be + applied to every suitable page in a call. */ + + /*! \brief Method 1: Local generation of TCF (required for use with TCP). + Method 2: Transfer of TCF is required for use with UDP (UDPTL or RTP). + Method 2 is not recommended for use with TCP. */ + int data_rate_management_method; + + /*! \brief The emitting gateway may indicate a preference for either UDP/UDPTL, or + UDP/RTP, or TCP for transport of T.38 IFP Packets. The receiving device + selects the transport protocol. */ + int data_transport_protocol; + + /*! \brief Indicates the capability to remove and insert fill bits in Phase C, non-ECM + data to reduce bandwidth in the packet network. */ + int fill_bit_removal; + + /*! \brief Indicates the ability to convert to/from MMR from/to the line format to + improve the compression of the data, and reduce the bandwidth, in the + packet network. */ + int mmr_transcoding; + + /*! \brief Indicates the ability to convert to/from JBIG to reduce bandwidth. */ + int jbig_transcoding; + + /*! \brief For UDP (UDPTL or RTP) modes, this option indicates the maximum + number of octets that can be stored on the remote device before an + overflow condition occurs. It is the responsibility of the transmitting + application to limit the transfer rate to prevent an overflow. The + negotiated data rate should be used to determine the rate at which + data is being removed from the buffer. */ + int max_buffer_size; + + /*! \brief This option indicates the maximum size of a UDPTL packet or the + maximum size of the payload within an RTP packet that can be accepted + by the remote device. */ + int max_datagram_size; + + /*! \brief This is the version number of ITU-T Rec. T.38. New versions shall be + compatible with previous versions. */ + int t38_version; + + /*! \brief Allow time for TEP playout */ + int allow_for_tep; + + /*! \brief The fastest data rate supported by the T.38 channel. */ + int fastest_image_data_rate; + + /*! \brief The number of times each packet type will be sent (low byte). The + depth of redundancy (2nd byte). Higher numbers may increase reliability + for UDP transmission. Zero is valid for the indicator packet category, + to suppress all indicator packets (typicaly for TCP transmission). */ + int category_control[5]; + + /*! \brief TRUE if IFP packet sequence numbers are relevant. For some transports, like TPKT + over TCP they are not relevent. */ + int check_sequence_numbers; + + /*! \brief The sequence number for the next packet to be transmitted */ + int tx_seq_no; + /*! \brief The sequence number expected in the next received packet */ + int rx_expected_seq_no; + + /*! \brief The current receive indicator - i.e. the last indicator received */ + int current_rx_indicator; + /*! \brief The current receive data type - i.e. the last data type received */ + int current_rx_data_type; + /*! \brief The current receive field type - i.e. the last field_type received */ + int current_rx_field_type; + /*! \brief The current transmit indicator - i.e. the last indicator transmitted */ + int current_tx_indicator; + /*! \brief The bit rate for V.34 operation */ + int v34_rate; + + /*! A count of missing receive packets. This count might not be accurate if the + received packet numbers jump wildly. */ + int missing_packets; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t38_gateway.h b/Libraries/spandsp/spandsp/spandsp/private/t38_gateway.h new file mode 100644 index 000000000..9f4eed57e --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t38_gateway.h @@ -0,0 +1,207 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t38_gateway.h - A T.38, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005, 2006, 2007 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_gateway.h,v 1.5 2009/11/07 08:58:58 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_T38_GATEWAY_H_) +#define _SPANDSP_PRIVATE_T38_GATEWAY_H_ + +/*! + T.38 gateway T.38 side channel descriptor. +*/ +typedef struct +{ + /*! Core T.38 IFP support */ + t38_core_state_t t38; + + /*! \brief TRUE if the NSF, NSC, and NSS are to be suppressed by altering + their contents to something the far end will not recognise. */ + int suppress_nsx_len[2]; + /*! \brief TRUE if we need to corrupt the HDLC frame in progress, so the receiver cannot + interpret it. The two values are for the two directions. */ + int corrupt_current_frame[2]; + + /*! \brief the current class of field being received - i.e. none, non-ECM or HDLC */ + int current_rx_field_class; + /*! \brief The T.38 indicator currently in use */ + int in_progress_rx_indicator; + + /*! \brief The current T.38 data type being sent. */ + int current_tx_data_type; +} t38_gateway_t38_state_t; + +/*! + T.38 gateway audio side channel descriptor. +*/ +typedef struct +{ + /*! \brief The FAX modem set for the audio side fo the gateway. */ + fax_modems_state_t modems; + /*! \brief The current receive signal handler. Actual receiving hop between this + and a dummy receive routine. */ + span_rx_handler_t *base_rx_handler; +} t38_gateway_audio_state_t; + +/*! + T.38 gateway T.38 side state. +*/ +typedef struct +{ + /*! \brief non-ECM and HDLC modem receive data buffer. */ + uint8_t data[T38_RX_BUF_LEN]; + /*! \brief Current pointer into the data buffer. */ + int data_ptr; + /*! \brief The current octet being received as non-ECM data. */ + unsigned int bit_stream; + /*! \brief The number of bits taken from the modem for the current scan row. This + is used during non-ECM transmission will fill bit removal to see that + T.38 packet transmissions do not stretch too far apart. */ + int bits_absorbed; + /*! \brief The current bit number in the current non-ECM octet. */ + int bit_no; + /*! \brief Progressively calculated CRC for HDLC messages received from a modem. */ + uint16_t crc; + /*! \brief TRUE if non-ECM fill bits are to be stripped when sending image data. */ + int fill_bit_removal; + /*! \brief The number of octets to send in each image packet (non-ECM or ECM) at the current + rate and the current specified packet interval. */ + int octets_per_data_packet; + + /*! \brief Bits into the non-ECM buffer */ + int in_bits; + /*! \brief Octets fed out from the non-ECM buffer */ + int out_octets; +} t38_gateway_to_t38_state_t; + +/*! + T.38 gateway HDLC buffer. +*/ +typedef struct +{ + /*! \brief HDLC message buffers. */ + uint8_t buf[T38_MAX_HDLC_LEN]; + /*! \brief HDLC message lengths. */ + int len; + /*! \brief HDLC message status flags. */ + int flags; + /*! \brief HDLC buffer contents. */ + int contents; +} t38_gateway_hdlc_buf_t; + +/*! + T.38 gateway HDLC state. +*/ +typedef struct +{ + /*! \brief HDLC message buffers. */ + t38_gateway_hdlc_buf_t buf[T38_TX_HDLC_BUFS]; +#if 0 + /*! \brief HDLC message buffers. */ + uint8_t buf[T38_TX_HDLC_BUFS][T38_MAX_HDLC_LEN]; + /*! \brief HDLC message lengths. */ + int len[T38_TX_HDLC_BUFS]; + /*! \brief HDLC message status flags. */ + int flags[T38_TX_HDLC_BUFS]; + /*! \brief HDLC buffer contents. */ + int contents[T38_TX_HDLC_BUFS]; +#endif + /*! \brief HDLC buffer number for input. */ + int in; + /*! \brief HDLC buffer number for output. */ + int out; +} t38_gateway_hdlc_state_t; + +/*! + T.38 gateway core descriptor. +*/ +typedef struct +{ + /*! \brief A bit mask of the currently supported modem types. */ + int supported_modems; + /*! \brief TRUE if ECM FAX mode is allowed through the gateway. */ + int ecm_allowed; + + /*! \brief TRUE if in image data modem is to use short training. This usually + follows image_data_mode, but in ECM mode T.30 defines recovery + conditions in which long training is used for image data. */ + int short_train; + /*! \brief TRUE if in image data mode, as opposed to TCF mode. */ + int image_data_mode; + /*! \brief The minimum permitted bits per FAX scan line row. */ + int min_row_bits; + + /*! \brief TRUE if we should count the next MCF as a page end, else FALSE */ + int count_page_on_mcf; + /*! \brief The number of pages for which a confirm (MCF) message was returned. */ + int pages_confirmed; + + /*! \brief TRUE if we are in error correcting (ECM) mode */ + int ecm_mode; + /*! \brief The current bit rate for the fast modem. */ + int fast_bit_rate; + /*! \brief The current fast receive modem type. */ + int fast_rx_modem; + /*! \brief The type of fast receive modem currently active, which may be T38_NONE */ + int fast_rx_active; + + /*! \brief The current timed operation. */ + int timed_mode; + /*! \brief The number of samples until the next timeout event */ + int samples_to_timeout; + + /*! Buffer for HDLC and non-ECM data going to the T.38 channel */ + t38_gateway_to_t38_state_t to_t38; + /*! Buffer for data going to an HDLC modem. */ + t38_gateway_hdlc_state_t hdlc_to_modem; + /*! Buffer for data going to a non-ECM mode modem. */ + t38_non_ecm_buffer_state_t non_ecm_to_modem; + + /*! \brief A pointer to a callback routine to be called when frames are + exchanged. */ + t38_gateway_real_time_frame_handler_t *real_time_frame_handler; + /*! \brief An opaque pointer supplied in real time frame callbacks. */ + void *real_time_frame_user_data; +} t38_gateway_core_state_t; + +/*! + T.38 gateway state. +*/ +struct t38_gateway_state_s +{ + /*! T.38 side state */ + t38_gateway_t38_state_t t38x; + /*! Audio side state */ + t38_gateway_audio_state_t audio; + /*! T.38 core state */ + t38_gateway_core_state_t core; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t38_non_ecm_buffer.h b/Libraries/spandsp/spandsp/spandsp/private/t38_non_ecm_buffer.h new file mode 100644 index 000000000..247233f58 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t38_non_ecm_buffer.h @@ -0,0 +1,86 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t38_non_ecm_buffer.h - A rate adapting buffer for T.38 non-ECM image + * and TCF data + * + * Written by Steve Underwood + * + * Copyright (C) 2005, 2006, 2007, 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_non_ecm_buffer.h,v 1.2.4.1 2009/12/19 06:43:28 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_T38_NON_ECM_BUFFER_H_) +#define _SPANDSP_PRIVATE_T38_NON_ECM_BUFFER_H_ + +/*! \brief A flow controlled non-ECM image data buffer, for buffering T.38 to analogue + modem data. +*/ +struct t38_non_ecm_buffer_state_s +{ + /*! \brief Minimum number of bits per row, used when fill bits are being deleted on the + link, and restored at the emitting gateway. */ + int min_bits_per_row; + + /*! \brief non-ECM modem transmit data buffer. */ + uint8_t data[T38_NON_ECM_TX_BUF_LEN]; + /*! \brief The current write point in the buffer. */ + int in_ptr; + /*! \brief The current read point in the buffer. */ + int out_ptr; + /*! \brief The location of the most recent EOL marker in the buffer. */ + int latest_eol_ptr; + /*! \brief The number of bits to date in the current row, used when min_row_bits is + to be applied. */ + int row_bits; + + /*! \brief The bit stream entering the buffer, used to detect EOLs */ + unsigned int bit_stream; + /*! \brief The non-ECM flow control fill octet (0xFF before the first data, and 0x00 + once data has started). */ + uint8_t flow_control_fill_octet; + /*! \brief A code for the phase of input buffering, from initial all ones to completion. */ + int input_phase; + /*! \brief TRUE is the end of non-ECM data indication has been received. */ + int data_finished; + /*! \brief The current octet being transmitted from the buffer. */ + unsigned int octet; + /*! \brief The current bit number in the current non-ECM octet. */ + int bit_no; + /*! \brief TRUE if in image data mode, as opposed to TCF mode. */ + int image_data_mode; + + /*! \brief The number of octets input to the buffer. */ + int in_octets; + /*! \brief The number of rows input to the buffer. */ + int in_rows; + /*! \brief The number of non-ECM fill octets generated for minimum row bits + purposes. */ + int min_row_bits_fill_octets; + /*! \brief The number of octets output from the buffer. */ + int out_octets; + /*! \brief The number of rows output from the buffer. */ + int out_rows; + /*! \brief The number of non-ECM fill octets generated for flow control + purposes. */ + int flow_control_fill_octets; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t38_terminal.h b/Libraries/spandsp/spandsp/spandsp/private/t38_terminal.h new file mode 100644 index 000000000..af5cbff80 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t38_terminal.h @@ -0,0 +1,120 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t38_terminal.h - T.38 termination, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_terminal.h,v 1.2 2008/12/31 13:57:13 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_T38_TERMINAL_H_) +#define _SPANDSP_PRIVATE_T38_TERMINAL_H_ + +typedef struct +{ + /*! \brief Internet Aware FAX mode bit mask. */ + int iaf; + /*! \brief Required time between T.38 transmissions, in ms. */ + int ms_per_tx_chunk; + /*! \brief Bit fields controlling the way data is packed into chunked for transmission. */ + int chunking_modes; + + /*! \brief Core T.38 IFP support */ + t38_core_state_t t38; + + /*! \brief The current transmit step being timed */ + int timed_step; + + /*! \brief TRUE is there has been some T.38 data missed (i.e. lost packets) in the current + reception period. */ + int rx_data_missing; + + /*! \brief The number of octets to send in each image packet (non-ECM or ECM) at the current + rate and the current specified packet interval. */ + int octets_per_data_packet; + + struct + { + /*! \brief HDLC receive buffer */ + uint8_t buf[T38_MAX_HDLC_LEN]; + /*! \brief The length of the contents of the HDLC receive buffer */ + int len; + } hdlc_rx; + + struct + { + /*! \brief HDLC transmit buffer */ + uint8_t buf[T38_MAX_HDLC_LEN]; + /*! \brief The length of the contents of the HDLC transmit buffer */ + int len; + /*! \brief Current pointer within the contents of the HDLC transmit buffer */ + int ptr; + /*! \brief The number of extra bits in a fully stuffed version of the + contents of the HDLC transmit buffer. This is needed to accurately + estimate the playout time for this frame, through an analogue modem. */ + int extra_bits; + } hdlc_tx; + + /*! \brief Counter for trailing non-ECM bytes, used to flush out the far end's modem. */ + int non_ecm_trailer_bytes; + + /*! \brief The next T.38 indicator queued for transmission. */ + int next_tx_indicator; + /*! \brief The current T.38 data type being transmitted. */ + int current_tx_data_type; + + /*! \brief TRUE if a carrier is present. Otherwise FALSE. */ + int rx_signal_present; + + /*! \brief The current operating mode of the receiver. */ + int current_rx_type; + /*! \brief The current operating mode of the transmitter. */ + int current_tx_type; + + /*! \brief Current transmission bit rate. */ + int tx_bit_rate; + /*! \brief A "sample" count, used to time events. */ + int32_t samples; + /*! \brief The value for samples at the next transmission point. */ + int32_t next_tx_samples; + /*! \brief The current receive timeout. */ + int32_t timeout_rx_samples; +} t38_terminal_front_end_state_t; + +/*! + T.38 terminal state. +*/ +struct t38_terminal_state_s +{ + /*! \brief The T.30 back-end */ + t30_state_t t30; + + /*! \brief The T.38 front-end */ + t38_terminal_front_end_state_t t38_fe; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t4_rx.h b/Libraries/spandsp/spandsp/spandsp/private/t4_rx.h new file mode 100644 index 000000000..6511ace1f --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t4_rx.h @@ -0,0 +1,131 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t4_rx.h - definitions for T.4 FAX receive processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t4_rx.h,v 1.6.2.8 2009/12/21 17:18:40 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_T4_RX_H_) +#define _SPANDSP_PRIVATE_T4_RX_H_ + +/*! + TIFF specific state information to go with T.4 compression or decompression handling. +*/ +typedef struct +{ + /*! \brief The current file name. */ + const char *file; + /*! \brief The libtiff context for the current TIFF file */ + TIFF *tiff_file; + + /*! \brief The number of pages in the current image file. */ + int pages_in_file; + + /*! \brief The compression type for output to the TIFF file. */ + int32_t output_compression; + /*! \brief The TIFF photometric setting for the current page. */ + uint16_t photo_metric; + /*! \brief The TIFF fill order setting for the current page. */ + uint16_t fill_order; + /*! \brief The TIFF G3 FAX options. */ + int32_t output_t4_options; + + /* "Background" information about the FAX, which can be stored in the image file. */ + /*! \brief The vendor of the machine which produced the file. */ + const char *vendor; + /*! \brief The model of machine which produced the file. */ + const char *model; + /*! \brief The local ident string. */ + const char *local_ident; + /*! \brief The remote end's ident string. */ + const char *far_ident; + /*! \brief The FAX sub-address. */ + const char *sub_address; + /*! \brief The FAX DCS information, as an ASCII string. */ + const char *dcs; + + /*! \brief The first page to transfer. -1 to start at the beginning of the file. */ + int start_page; + /*! \brief The last page to transfer. -1 to continue to the end of the file. */ + int stop_page; +} t4_tiff_state_t; + +typedef struct t4_t6_decode_state_s t4_t6_decode_state_t; + +/*! + T.4 1D, T4 2D and T6 decompressor state. +*/ +struct t4_t6_decode_state_s +{ + /*! \brief Callback function to write a row of pixels to the image destination. */ + t4_row_write_handler_t row_write_handler; + /*! \brief Opaque pointer passed to row_write_handler. */ + void *row_write_user_data; + + /*! \brief Incoming bit buffer for decompression. */ + uint32_t rx_bitstream; + /*! \brief The number of bits currently in rx_bitstream. */ + int rx_bits; + /*! \brief The number of bits to be skipped before trying to match the next code word. */ + int rx_skip_bits; + + /*! \brief This variable is used to count the consecutive EOLS we have seen. If it + reaches six, this is the end of the image. It is initially set to -1 for + 1D and 2D decoding, as an indicator that we must wait for the first EOL, + before decoding any image data. */ + int consecutive_eols; + + /*! \brief The reference or starting changing element on the coding line. At the + start of the coding line, a0 is set on an imaginary white changing element + situated just before the first element on the line. During the coding of + the coding line, the position of a0 is defined by the previous coding mode. + (See T.4/4.2.1.3.2.). */ + int a0; + /*! \brief The first changing element on the reference line to the right of a0 and of + opposite colour to a0. */ + int b1; + /*! \brief The length of the in-progress run of black or white. */ + int run_length; + /*! \brief 2D horizontal mode control. */ + int black_white; + /*! \brief TRUE if the current run is black */ + int its_black; + + /*! \brief The current step into the current row run-lengths buffer. */ + int a_cursor; + /*! \brief The current step into the reference row run-lengths buffer. */ + int b_cursor; + + /*! \brief A pointer into the image buffer indicating where the last row begins */ + int last_row_starts_at; + + /*! \brief The current number of consecutive bad rows. */ + int curr_bad_row_run; + /*! \brief The longest run of consecutive bad rows seen in the current page. */ + int longest_bad_row_run; + /*! \brief The total number of bad rows in the current page. */ + int bad_rows; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/t4_tx.h b/Libraries/spandsp/spandsp/spandsp/private/t4_tx.h new file mode 100644 index 000000000..a65d0b415 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/t4_tx.h @@ -0,0 +1,142 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/t4_tx.h - definitions for T.4 FAX transmit processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t4_tx.h,v 1.7.2.4 2009/12/21 17:18:40 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_T4_TX_H_) +#define _SPANDSP_PRIVATE_T4_TX_H_ + +typedef struct t4_t6_encode_state_s t4_t6_encode_state_t; + +/*! + T.4 1D, T4 2D and T6 compressor state. +*/ +struct t4_t6_encode_state_s +{ + /*! \brief The minimum number of encoded bits per row. This is a timing thing + for hardware FAX machines. */ + int min_bits_per_row; + /*! \brief The current maximum contiguous rows that may be 2D encoded. */ + int max_rows_to_next_1d_row; + + /*! \brief The text which will be used in FAX page header. No text results + in no header line. */ + const char *header_info; + + /*! \brief Number of rows left that can be 2D encoded, before a 1D encoded row + must be used. */ + int rows_to_next_1d_row; + + /*! \brief The number of runs currently in the reference row. */ + int ref_steps; + + /*! \brief Pointer to the byte containing the next image bit to transmit. */ + int bit_pos; + /*! \brief Pointer to the bit within the byte containing the next image bit to transmit. */ + int bit_ptr; + + /*! \brief Callback function to read a row of pixels from the image source. */ + t4_row_read_handler_t row_read_handler; + /*! \brief Opaque pointer passed to row_read_handler. */ + void *row_read_user_data; +}; + +/*! + T.4 FAX compression/decompression descriptor. This defines the working state + for a single instance of a T.4 FAX compression or decompression channel. +*/ +struct t4_state_s +{ + /*! \brief The same structure is used for T.4 transmit and receive. This variable + records which mode is in progress. */ + int rx; + + /*! \brief The type of compression used between the FAX machines. */ + int line_encoding; + + /*! \brief The time at which handling of the current page began. */ + time_t page_start_time; + + /*! \brief The size of the compressed image on the line side, in bits. */ + int line_image_size; + + /*! \brief The current number of bytes per row of uncompressed image data. */ + int bytes_per_row; + /*! \brief The size of the image in the image buffer, in bytes. */ + int image_size; + /*! \brief The current size of the image buffer. */ + int image_buffer_size; + /*! \brief A point to the image buffer. */ + uint8_t *image_buffer; + + /*! \brief The number of pages transferred to date. */ + int current_page; + /*! \brief Column-to-column (X) resolution in pixels per metre. */ + int x_resolution; + /*! \brief Row-to-row (Y) resolution in pixels per metre. */ + int y_resolution; + /*! \brief Width of the current page, in pixels. */ + int image_width; + /*! \brief Length of the current page, in pixels. */ + int image_length; + /*! \brief Current pixel row number. */ + int row; + + /*! \brief This variable is set if we are treating the current row as a 2D encoded + one. */ + int row_is_2d; + /*! \brief The current length of the current row. */ + int row_len; + + /*! \brief Black and white run-lengths for the current row. */ + uint32_t *cur_runs; + /*! \brief Black and white run-lengths for the reference row. */ + uint32_t *ref_runs; + /*! \brief Pointer to the buffer for the current pixel row. */ + uint8_t *row_buf; + + /*! \brief Encoded data bits buffer. */ + uint32_t tx_bitstream; + /*! \brief The number of bits currently in tx_bitstream. */ + int tx_bits; + + /*! \brief The current number of bits in the current encoded row. */ + int row_bits; + /*! \brief The minimum bits in any row of the current page. For monitoring only. */ + int min_row_bits; + /*! \brief The maximum bits in any row of the current page. For monitoring only. */ + int max_row_bits; + + /*! \brief Error and flow logging control */ + logging_state_t logging; + + /*! \brief All TIFF file specific state information for the T.4 context. */ + t4_tiff_state_t tiff; + t4_t6_decode_state_t t4_t6_rx; + t4_t6_encode_state_t t4_t6_tx; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/time_scale.h b/Libraries/spandsp/spandsp/spandsp/private/time_scale.h new file mode 100644 index 000000000..85d3975ad --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/time_scale.h @@ -0,0 +1,52 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/time_scale.h - Time scaling for linear speech data + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: time_scale.h,v 1.1 2008/11/15 14:27:29 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_TIME_SCALE_H_) +#define _SPANDSP_PRIVATE_TIME_SCALE_H_ + +#define TIME_SCALE_MAX_SAMPLE_RATE 48000 +#define TIME_SCALE_MIN_PITCH 60 +#define TIME_SCALE_MAX_PITCH 250 +#define TIME_SCALE_BUF_LEN (2*TIME_SCALE_MAX_SAMPLE_RATE/TIME_SCALE_MIN_PITCH) + +/*! Audio time scaling descriptor. */ +struct time_scale_state_s +{ + int sample_rate; + int min_pitch; + int max_pitch; + int buf_len; + float playout_rate; + double rcomp; + double rate_nudge; + int fill; + int lcp; + int16_t buf[TIME_SCALE_BUF_LEN]; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/tone_detect.h b/Libraries/spandsp/spandsp/spandsp/private/tone_detect.h new file mode 100644 index 000000000..3d67e5239 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/tone_detect.h @@ -0,0 +1,32 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/tone_detect.h - General telephony tone detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: tone_detect.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_TONE_DETECT_H_) +#define _SPANDSP_PRIVATE_TONE_DETECT_H_ + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/tone_generate.h b/Libraries/spandsp/spandsp/spandsp/private/tone_generate.h new file mode 100644 index 000000000..a1443be87 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/tone_generate.h @@ -0,0 +1,68 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/tone_generate.h - General telephony tone generation. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: tone_generate.h,v 1.1 2008/11/30 10:17:31 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_TONE_GENERATE_H_) +#define _SPANDSP_PRIVATE_TONE_GENERATE_H_ + +struct tone_gen_tone_descriptor_s +{ + int32_t phase_rate; +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t gain; +#else + float gain; +#endif +}; + +/*! + Cadenced multi-tone generator descriptor. +*/ +struct tone_gen_descriptor_s +{ + tone_gen_tone_descriptor_t tone[4]; + int duration[4]; + int repeat; +}; + +/*! + Cadenced multi-tone generator state descriptor. This defines the state of + a single working instance of a generator. +*/ +struct tone_gen_state_s +{ + tone_gen_tone_descriptor_t tone[4]; + + uint32_t phase[4]; + int duration[4]; + int repeat; + + int current_section; + int current_position; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v17rx.h b/Libraries/spandsp/spandsp/spandsp/private/v17rx.h new file mode 100644 index 000000000..87931b1d8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v17rx.h @@ -0,0 +1,232 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v17rx.h - ITU V.17 modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v17rx.h,v 1.2.4.1 2009/12/24 16:52:30 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V17RX_H_) +#define _SPANDSP_PRIVATE_V17RX_H_ + +/* Target length for the equalizer is about 63 taps, to deal with the worst stuff + in V.56bis. */ +/*! Samples before the target position in the equalizer buffer */ +#define V17_EQUALIZER_PRE_LEN 8 +/*! Samples after the target position in the equalizer buffer */ +#define V17_EQUALIZER_POST_LEN 8 + +/*! The number of taps in the pulse shaping/bandpass filter */ +#define V17_RX_FILTER_STEPS 27 + +/* We can store more trellis depth that we look back over, so that we can push out a group + of symbols in one go, giving greater processing efficiency, at the expense of a bit more + latency through the modem. */ +/* Right now we don't take advantage of this optimisation. */ +/*! The depth of the trellis buffer */ +#define V17_TRELLIS_STORAGE_DEPTH 16 +/*! How far we look back into history for trellis decisions */ +#define V17_TRELLIS_LOOKBACK_DEPTH 16 + +/*! + V.17 modem receive side descriptor. This defines the working state for a + single instance of a V.17 modem receiver. +*/ +struct v17_rx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 7200 9600, 12000 and 14400. */ + int bit_rate; + /*! \brief The callback function used to put each bit received. */ + put_bit_func_t put_bit; + /*! \brief A user specified opaque pointer passed to the put_but routine. */ + void *put_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_rx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + /*! \brief A callback function which may be enabled to report every symbol's + constellation position. */ + qam_report_handler_t qam_report; + /*! \brief A user specified opaque pointer passed to the qam_report callback + routine. */ + void *qam_user_data; + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t rrc_filter[V17_RX_FILTER_STEPS]; +#else + float rrc_filter[V17_RX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The state of the differential decoder */ + int diff; + /*! \brief The register for the data scrambler. */ + uint32_t scramble_reg; + /*! \brief Scrambler tap */ + //int scrambler_tap; + + /*! \brief TRUE if the short training sequence is to be used. */ + int short_train; + /*! \brief The section of the training data we are currently in. */ + int training_stage; + /*! \brief A count of how far through the current training step we are. */ + int training_count; + /*! \brief A measure of how much mismatch there is between the real constellation, + and the decoded symbol positions. */ + float training_error; + /*! \brief The value of the last signal sample, using the a simple HPF for signal power estimation. */ + int16_t last_sample; + /*! \brief >0 if a signal above the minimum is present. It may or may not be a V.17 signal. */ + int signal_present; + /*! \brief Whether or not a carrier drop was detected and the signal delivery is pending. */ + int carrier_drop_pending; + /*! \brief A count of the current consecutive samples below the carrier off threshold. */ + int low_samples; + /*! \brief A highest magnitude sample seen. */ + int16_t high_sample; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The carrier update rate saved for reuse when using short training. */ + int32_t carrier_phase_rate_save; +#if defined(SPANDSP_USE_FIXED_POINTx) + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; +#else + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; +#endif + + /*! \brief A power meter, to measure the HPF'ed signal power in the channel. */ + power_meter_t power; + /*! \brief The power meter level at which carrier on is declared. */ + int32_t carrier_on_power; + /*! \brief The power meter level at which carrier off is declared. */ + int32_t carrier_off_power; + + /*! \brief Current read offset into the equalizer buffer. */ + int eq_step; + /*! \brief Current write offset into the equalizer buffer. */ + int eq_put_step; + /*! \brief Symbol counter to the next equalizer update. */ + int eq_skip; + + /*! \brief The current half of the baud. */ + int baud_half; + +#if defined(SPANDSP_USE_FIXED_POINTx) + /*! \brief The scaling factor accessed by the AGC algorithm. */ + float agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + float agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + complexi16_t eq_coeff[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + complexi16_t eq_coeff_save[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + complexi16_t eq_buf[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + + /*! Low band edge filter for symbol sync. */ + int32_t symbol_sync_low[2]; + /*! High band edge filter for symbol sync. */ + int32_t symbol_sync_high[2]; + /*! DC filter for symbol sync. */ + int32_t symbol_sync_dc_filter[2]; + /*! Baud phase for symbol sync. */ + int32_t baud_phase; +#else + /*! \brief The scaling factor accessed by the AGC algorithm. */ + float agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + float agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + complexf_t eq_coeff[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + complexf_t eq_coeff_save[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + complexf_t eq_buf[V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN]; + + /*! Low band edge filter for symbol sync. */ + float symbol_sync_low[2]; + /*! High band edge filter for symbol sync. */ + float symbol_sync_high[2]; + /*! DC filter for symbol sync. */ + float symbol_sync_dc_filter[2]; + /*! Baud phase for symbol sync. */ + float baud_phase; +#endif + + /*! \brief The total symbol timing correction since the carrier came up. + This is only for performance analysis purposes. */ + int total_baud_timing_correction; + + /*! \brief Starting phase angles for the coarse carrier aquisition step. */ + int32_t start_angles[2]; + /*! \brief History list of phase angles for the coarse carrier aquisition step. */ + int32_t angles[16]; + /*! \brief A pointer to the current constellation. */ +#if defined(SPANDSP_USE_FIXED_POINTx) + const complexi16_t *constellation; +#else + const complexf_t *constellation; +#endif + /*! \brief A pointer to the current space map. There is a space map for + each trellis state. */ + int space_map; + /*! \brief The number of bits in each symbol at the current bit rate. */ + int bits_per_symbol; + + /*! \brief Current pointer to the trellis buffers */ + int trellis_ptr; + /*! \brief The trellis. */ + int full_path_to_past_state_locations[V17_TRELLIS_STORAGE_DEPTH][8]; + /*! \brief The trellis. */ + int past_state_locations[V17_TRELLIS_STORAGE_DEPTH][8]; + /*! \brief Euclidean distances (actually the squares of the distances) + from the last states of the trellis. */ +#if defined(SPANDSP_USE_FIXED_POINTx) + uint32_t distances[8]; +#else + float distances[8]; +#endif + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v17tx.h b/Libraries/spandsp/spandsp/spandsp/private/v17tx.h new file mode 100644 index 000000000..c93ba08c3 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v17tx.h @@ -0,0 +1,111 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v17tx.h - ITU V.17 modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v17tx.h,v 1.2.4.1 2009/12/24 16:52:30 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_PRIVATE_V17TX_H_) +#define _SPANDSP_PRIVATE_V17TX_H_ + +/*! The number of taps in the pulse shaping/bandpass filter */ +#define V17_TX_FILTER_STEPS 9 + +/*! + V.17 modem transmit side descriptor. This defines the working state for a + single instance of a V.17 modem transmitter. +*/ +struct v17_tx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 4800, 7200 and 9600. */ + int bit_rate; + /*! \brief The callback function used to get the next bit to be transmitted. */ + get_bit_func_t get_bit; + /*! \brief A user specified opaque pointer passed to the get_bit function. */ + void *get_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + /*! \brief The gain factor needed to achieve the specified output power. */ +#if defined(SPANDSP_USE_FIXED_POINT) + int32_t gain; +#else + float gain; +#endif + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + complexi16_t rrc_filter[2*V17_TX_FILTER_STEPS]; +#else + complexf_t rrc_filter[2*V17_TX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The current state of the differential encoder. */ + int diff; + /*! \brief The current state of the convolutional encoder. */ + int convolution; + /*! \brief The code number for the current position in the constellation. */ + int constellation_state; + + /*! \brief The register for the data scrambler. */ + uint32_t scramble_reg; + /*! \brief Scrambler tap */ + //int scrambler_tap; + /*! \brief TRUE if transmitting the training sequence. FALSE if transmitting user data. */ + int in_training; + /*! \brief TRUE if the short training sequence is to be used. */ + int short_train; + /*! \brief A counter used to track progress through sending the training sequence. */ + int training_step; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The current fractional phase of the baud timing. */ + int baud_phase; + + /*! \brief A pointer to the constellation currently in use. */ +#if defined(SPANDSP_USE_FIXED_POINT) + const complexi16_t *constellation; +#else + const complexf_t *constellation; +#endif + /*! \brief The current number of data bits per symbol. This does not include + the redundant bit. */ + int bits_per_symbol; + /*! \brief The get_bit function in use at any instant. */ + get_bit_func_t current_get_bit; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v18.h b/Libraries/spandsp/spandsp/spandsp/private/v18.h new file mode 100644 index 000000000..79471fd68 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v18.h @@ -0,0 +1,67 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v18.h - V.18 text telephony for the deaf. + * + * Written by Steve Underwood + * + * Copyright (C) 2004-2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v18.h,v 1.5 2009/11/04 15:52:06 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V18_H_) +#define _SPANDSP_PRIVATE_V18_H_ + +struct v18_state_s +{ + /*! \brief TRUE if we are the calling modem */ + int calling_party; + int mode; + put_msg_func_t put_msg; + void *user_data; + + union + { + queue_state_t queue; + uint8_t buf[QUEUE_STATE_T_SIZE(128)]; + } queue; + tone_gen_descriptor_t alert_tone_desc; + tone_gen_state_t alert_tone_gen; + fsk_tx_state_t fsktx; + dtmf_tx_state_t dtmftx; + async_tx_state_t asynctx; + int baudot_tx_shift; + int tx_signal_on; + int byte_no; + + fsk_rx_state_t fskrx; + dtmf_rx_state_t dtmfrx; + int baudot_rx_shift; + int consecutive_ones; + uint8_t rx_msg[256 + 1]; + int rx_msg_len; + int bit_pos; + int in_progress; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v22bis.h b/Libraries/spandsp/spandsp/spandsp/private/v22bis.h new file mode 100644 index 000000000..a2e07d539 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v22bis.h @@ -0,0 +1,244 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v22bis.h - ITU V.22bis modem + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v22bis.h,v 1.12 2009/11/04 15:52:06 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V22BIS_H_) +#define _SPANDSP_PRIVATE_V22BIS_H_ + +/*! The number of steps to the left and to the right of the target position in the equalizer buffer. */ +#define V22BIS_EQUALIZER_LEN 7 +/*! One less than a power of 2 >= (2*V22BIS_EQUALIZER_LEN + 1) */ +#define V22BIS_EQUALIZER_MASK 15 + +/*! The number of taps in the transmit pulse shaping filter */ +#define V22BIS_TX_FILTER_STEPS 9 + +/*! The number of taps in the receive pulse shaping/bandpass filter */ +#define V22BIS_RX_FILTER_STEPS 37 + +/*! Segments of the training sequence on the receive side */ +enum +{ + V22BIS_RX_TRAINING_STAGE_NORMAL_OPERATION, + V22BIS_RX_TRAINING_STAGE_SYMBOL_ACQUISITION, + V22BIS_RX_TRAINING_STAGE_LOG_PHASE, + V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES, + V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES_SUSTAINING, + V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200, + V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200_SUSTAINING, + V22BIS_RX_TRAINING_STAGE_WAIT_FOR_SCRAMBLED_ONES_AT_2400, + V22BIS_RX_TRAINING_STAGE_PARKED +}; + +/*! Segments of the training sequence on the transmit side */ +enum +{ + V22BIS_TX_TRAINING_STAGE_NORMAL_OPERATION = 0, + V22BIS_TX_TRAINING_STAGE_INITIAL_TIMED_SILENCE, + V22BIS_TX_TRAINING_STAGE_INITIAL_SILENCE, + V22BIS_TX_TRAINING_STAGE_U11, + V22BIS_TX_TRAINING_STAGE_U0011, + V22BIS_TX_TRAINING_STAGE_S11, + V22BIS_TX_TRAINING_STAGE_TIMED_S11, + V22BIS_TX_TRAINING_STAGE_S1111, + V22BIS_TX_TRAINING_STAGE_PARKED +}; + +/*! + V.22bis modem descriptor. This defines the working state for a single instance + of a V.22bis modem. +*/ +struct v22bis_state_s +{ + /*! \brief The maximum permitted bit rate of the modem. Valid values are 1200 and 2400. */ + int bit_rate; + /*! \brief TRUE is this is the calling side modem. */ + int calling_party; + /*! \brief The callback function used to get the next bit to be transmitted. */ + get_bit_func_t get_bit; + /*! \brief A user specified opaque pointer passed to the get_bit callback routine. */ + void *get_bit_user_data; + /*! \brief The callback function used to put each bit received. */ + put_bit_func_t put_bit; + /*! \brief A user specified opaque pointer passed to the put_bit callback routine. */ + void *put_bit_user_data; + /*! \brief The callback function used to report modem status changes. */ + modem_rx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + int negotiated_bit_rate; + + /* Receive section */ + struct + { + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ + float rrc_filter[2*V22BIS_RX_FILTER_STEPS]; + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the data scrambler. */ + unsigned int scramble_reg; + /*! \brief A counter for the number of consecutive bits of repeating pattern through + the scrambler. */ + int scrambler_pattern_count; + + /*! \brief 0 if receiving user data. A training stage value during training */ + int training; + /*! \brief A count of how far through the current training step we are. */ + int training_count; + + /*! \brief >0 if a signal above the minimum is present. It may or may not be a V.22bis signal. */ + int signal_present; + + /*! \brief A measure of how much mismatch there is between the real constellation, + and the decoded symbol positions. */ + float training_error; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; + + /*! \brief A callback function which may be enabled to report every symbol's + constellation position. */ + qam_report_handler_t qam_report; + /*! \brief A user specified opaque pointer passed to the qam_report callback + routine. */ + void *qam_user_data; + + /*! \brief A power meter, to measure the HPF'ed signal power in the channel. */ + power_meter_t rx_power; + /*! \brief The power meter level at which carrier on is declared. */ + int32_t carrier_on_power; + /*! \brief The power meter level at which carrier off is declared. */ + int32_t carrier_off_power; + /*! \brief The scaling factor accessed by the AGC algorithm. */ + float agc_scaling; + + int constellation_state; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; +#if defined(SPANDSP_USE_FIXED_POINTx) + /*! \brief The adaptive equalizer coefficients. */ + complexi_t eq_coeff[2*V22BIS_EQUALIZER_LEN + 1]; + /*! \brief The equalizer signal buffer. */ + complexi_t eq_buf[V22BIS_EQUALIZER_MASK + 1]; +#else + complexf_t eq_coeff[2*V22BIS_EQUALIZER_LEN + 1]; + complexf_t eq_buf[V22BIS_EQUALIZER_MASK + 1]; +#endif + /*! \brief Current offset into the equalizer buffer. */ + int eq_step; + /*! \brief Current write offset into the equalizer buffer. */ + int eq_put_step; + + /*! \brief Integration variable for damping the Gardner algorithm tests. */ + int gardner_integrate; + /*! \brief Current step size of Gardner algorithm integration. */ + int gardner_step; + /*! \brief The total symbol timing correction since the carrier came up. + This is only for performance analysis purposes. */ + int total_baud_timing_correction; + /*! \brief The current fractional phase of the baud timing. */ + int baud_phase; + + int sixteen_way_decisions; + + int pattern_repeats; + int last_raw_bits; + } rx; + + /* Transmit section */ + struct + { + /*! \brief The gain factor needed to achieve the specified output power. */ + float gain; + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ + complexf_t rrc_filter[2*V22BIS_TX_FILTER_STEPS]; + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the data scrambler. */ + unsigned int scramble_reg; + /*! \brief A counter for the number of consecutive bits of repeating pattern through + the scrambler. */ + int scrambler_pattern_count; + + /*! \brief 0 if transmitting user data. A training stage value during training */ + int training; + /*! \brief A counter used to track progress through sending the training sequence. */ + int training_count; + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The current phase of the guard tone (i.e. the DDS parameter). */ + uint32_t guard_phase; + /*! \brief The update rate for the phase of the guard tone (i.e. the DDS increment). */ + int32_t guard_phase_rate; + float guard_level; + /*! \brief The current fractional phase of the baud timing. */ + int baud_phase; + /*! \brief The code number for the current position in the constellation. */ + int constellation_state; + /*! \brief An indicator to mark that we are tidying up to stop transmission. */ + int shutdown; + /*! \brief The get_bit function in use at any instant. */ + get_bit_func_t current_get_bit; + } tx; + + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Reinitialise an existing V.22bis modem receive context. + \brief Reinitialise an existing V.22bis modem receive context. + \param s The modem context. + \return 0 for OK, -1 for bad parameter */ +int v22bis_rx_restart(v22bis_state_t *s); + +void v22bis_report_status_change(v22bis_state_t *s, int status); + +void v22bis_equalizer_coefficient_reset(v22bis_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v27ter_rx.h b/Libraries/spandsp/spandsp/spandsp/private/v27ter_rx.h new file mode 100644 index 000000000..63566de62 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v27ter_rx.h @@ -0,0 +1,196 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v27ter_rx.h - ITU V.27ter modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v27ter_rx.h,v 1.2 2009/07/09 13:52:09 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V27TER_RX_H_) +#define _SPANDSP_PRIVATE_V27TER_RX_H_ + +/* Target length for the equalizer is about 43 taps for 4800bps and 32 taps for 2400bps + to deal with the worst stuff in V.56bis. */ +/*! Samples before the target position in the equalizer buffer */ +#define V27TER_EQUALIZER_PRE_LEN 16 /* This much before the real event */ +/*! Samples after the target position in the equalizer buffer */ +#define V27TER_EQUALIZER_POST_LEN 14 /* This much after the real event (must be even) */ + +/*! The number of taps in the 4800bps pulse shaping/bandpass filter */ +#define V27TER_RX_4800_FILTER_STEPS 27 +/*! The number of taps in the 2400bps pulse shaping/bandpass filter */ +#define V27TER_RX_2400_FILTER_STEPS 27 + +#if V27TER_RX_4800_FILTER_STEPS > V27TER_RX_2400_FILTER_STEPS +#define V27TER_RX_FILTER_STEPS V27TER_RX_4800_FILTER_STEPS +#else +#define V27TER_RX_FILTER_STEPS V27TER_RX_2400_FILTER_STEPS +#endif + +/*! + V.27ter modem receive side descriptor. This defines the working state for a + single instance of a V.27ter modem receiver. +*/ +struct v27ter_rx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 2400 and 4800. */ + int bit_rate; + /*! \brief The callback function used to put each bit received. */ + put_bit_func_t put_bit; + /*! \brief A user specified opaque pointer passed to the put_bit routine. */ + void *put_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_rx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + /*! \brief A callback function which may be enabled to report every symbol's + constellation position. */ + qam_report_handler_t qam_report; + /*! \brief A user specified opaque pointer passed to the qam_report callback + routine. */ + void *qam_user_data; + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t rrc_filter[V27TER_RX_FILTER_STEPS]; +#else + float rrc_filter[V27TER_RX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the training and data scrambler. */ + unsigned int scramble_reg; + /*! \brief A counter for the number of consecutive bits of repeating pattern through + the scrambler. */ + int scrambler_pattern_count; + /*! \brief The current step in the table of BC constellation positions. */ + int training_bc; + /*! \brief TRUE if the previous trained values are to be reused. */ + int old_train; + /*! \brief The section of the training data we are currently in. */ + int training_stage; + /*! \brief A count of how far through the current training step we are. */ + int training_count; + /*! \brief A measure of how much mismatch there is between the real constellation, + and the decoded symbol positions. */ + float training_error; + /*! \brief The value of the last signal sample, using the a simple HPF for signal power estimation. */ + int16_t last_sample; + /*! \brief >0 if a signal above the minimum is present. It may or may not be a V.27ter signal. */ + int signal_present; + /*! \brief Whether or not a carrier drop was detected and the signal delivery is pending. */ + int carrier_drop_pending; + /*! \brief A count of the current consecutive samples below the carrier off threshold. */ + int low_samples; + /*! \brief A highest magnitude sample seen. */ + int16_t high_sample; + + /*! \brief The position of the current symbol in the constellation, used for + differential decoding. */ + int constellation_state; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The carrier update rate saved for reuse when using short training. */ + int32_t carrier_phase_rate_save; +#if defined(SPANDSP_USE_FIXED_POINTx) + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; +#else + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; +#endif + + /*! \brief A power meter, to measure the HPF'ed signal power in the channel. */ + power_meter_t power; + /*! \brief The power meter level at which carrier on is declared. */ + int32_t carrier_on_power; + /*! \brief The power meter level at which carrier off is declared. */ + int32_t carrier_off_power; + + /*! \brief Current read offset into the equalizer buffer. */ + int eq_step; + /*! \brief Current write offset into the equalizer buffer. */ + int eq_put_step; + /*! \brief Symbol counter to the next equalizer update. */ + int eq_skip; + + /*! \brief The current half of the baud. */ + int baud_half; + +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The scaling factor accessed by the AGC algorithm. */ + int16_t agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + int16_t agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + /*complexi16_t*/ complexf_t eq_coeff[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + /*complexi16_t*/ complexf_t eq_coeff_save[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + /*complexi16_t*/ complexf_t eq_buf[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; +#else + /*! \brief The scaling factor accessed by the AGC algorithm. */ + float agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + float agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + complexf_t eq_coeff[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + complexf_t eq_coeff_save[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + complexf_t eq_buf[V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN]; +#endif + + /*! \brief Integration variable for damping the Gardner algorithm tests. */ + int gardner_integrate; + /*! \brief Current step size of Gardner algorithm integration. */ + int gardner_step; + /*! \brief The total symbol timing correction since the carrier came up. + This is only for performance analysis purposes. */ + int total_baud_timing_correction; + + /*! \brief Starting phase angles for the coarse carrier aquisition step. */ + int32_t start_angles[2]; + /*! \brief History list of phase angles for the coarse carrier aquisition step. */ + int32_t angles[16]; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v27ter_tx.h b/Libraries/spandsp/spandsp/spandsp/private/v27ter_tx.h new file mode 100644 index 000000000..28e455cd8 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v27ter_tx.h @@ -0,0 +1,98 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v27ter_tx.h - ITU V.27ter modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v27ter_tx.h,v 1.3 2009/07/09 13:52:09 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V27TER_TX_H_) +#define _SPANDSP_PRIVATE_V27TER_TX_H_ + +/*! The number of taps in the pulse shaping/bandpass filter */ +#define V27TER_TX_FILTER_STEPS 9 + +/*! + V.27ter modem transmit side descriptor. This defines the working state for a + single instance of a V.27ter modem transmitter. +*/ +struct v27ter_tx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 2400 and 4800. */ + int bit_rate; + /*! \brief The callback function used to get the next bit to be transmitted. */ + get_bit_func_t get_bit; + /*! \brief A user specified opaque pointer passed to the get_bit function. */ + void *get_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The gain factor needed to achieve the specified output power at 2400bps. */ + int32_t gain_2400; + /*! \brief The gain factor needed to achieve the specified output power at 4800bps. */ + int32_t gain_4800; +#else + /*! \brief The gain factor needed to achieve the specified output power at 2400bps. */ + float gain_2400; + /*! \brief The gain factor needed to achieve the specified output power at 4800bps. */ + float gain_4800; +#endif + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + complexi16_t rrc_filter[2*V27TER_TX_FILTER_STEPS]; +#else + complexf_t rrc_filter[2*V27TER_TX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the training and data scrambler. */ + unsigned int scramble_reg; + /*! \brief A counter for the number of consecutive bits of repeating pattern through + the scrambler. */ + int scrambler_pattern_count; + /*! \brief TRUE if transmitting the training sequence, or shutting down transmission. + FALSE if transmitting user data. */ + int in_training; + /*! \brief A counter used to track progress through sending the training sequence. */ + int training_step; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The current fractional phase of the baud timing. */ + int baud_phase; + /*! \brief The code number for the current position in the constellation. */ + int constellation_state; + /*! \brief The get_bit function in use at any instant. */ + get_bit_func_t current_get_bit; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v29rx.h b/Libraries/spandsp/spandsp/spandsp/private/v29rx.h new file mode 100644 index 000000000..6dbb3a727 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v29rx.h @@ -0,0 +1,201 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v29rx.h - ITU V.29 modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v29rx.h,v 1.2 2009/07/09 13:52:09 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V29RX_H_) +#define _SPANDSP_PRIVATE_V29RX_H_ + +/* Target length for the equalizer is about 63 taps, to deal with the worst stuff + in V.56bis. */ +/*! Samples before the target position in the equalizer buffer */ +#define V29_EQUALIZER_PRE_LEN 16 +/*! Samples after the target position in the equalizer buffer */ +#define V29_EQUALIZER_POST_LEN 14 + +/*! The number of taps in the pulse shaping/bandpass filter */ +#define V29_RX_FILTER_STEPS 27 + +/*! + V.29 modem receive side descriptor. This defines the working state for a + single instance of a V.29 modem receiver. +*/ +struct v29_rx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 4800, 7200 and 9600. */ + int bit_rate; + /*! \brief The callback function used to put each bit received. */ + put_bit_func_t put_bit; + /*! \brief A user specified opaque pointer passed to the put_bit routine. */ + void *put_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_rx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + /*! \brief A callback function which may be enabled to report every symbol's + constellation position. */ + qam_report_handler_t qam_report; + /*! \brief A user specified opaque pointer passed to the qam_report callback + routine. */ + void *qam_user_data; + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t rrc_filter[V29_RX_FILTER_STEPS]; +#else + float rrc_filter[V29_RX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the data scrambler. */ + unsigned int scramble_reg; + /*! \brief The register for the training scrambler. */ + uint8_t training_scramble_reg; + /*! \brief The current step in the table of CD constellation positions. */ + int training_cd; + /*! \brief TRUE if the previous trained values are to be reused. */ + int old_train; + /*! \brief The section of the training data we are currently in. */ + int training_stage; + /*! \brief A count of how far through the current training step we are. */ + int training_count; + /*! \brief A measure of how much mismatch there is between the real constellation, + and the decoded symbol positions. */ + float training_error; + /*! \brief The value of the last signal sample, using the a simple HPF for signal power estimation. */ + int16_t last_sample; + /*! \brief >0 if a signal above the minimum is present. It may or may not be a V.29 signal. */ + int signal_present; + /*! \brief Whether or not a carrier drop was detected and the signal delivery is pending. */ + int carrier_drop_pending; + /*! \brief A count of the current consecutive samples below the carrier off threshold. */ + int low_samples; + /*! \brief A highest magnitude sample seen. */ + int16_t high_sample; + + /*! \brief The position of the current symbol in the constellation, used for + differential decoding. */ + int constellation_state; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The carrier update rate saved for reuse when using short training. */ + int32_t carrier_phase_rate_save; +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The proportional part of the carrier tracking filter. */ + int32_t carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + int32_t carrier_track_i; +#else + /*! \brief The proportional part of the carrier tracking filter. */ + float carrier_track_p; + /*! \brief The integral part of the carrier tracking filter. */ + float carrier_track_i; +#endif + + /*! \brief A power meter, to measure the HPF'ed signal power in the channel. */ + power_meter_t power; + /*! \brief The power meter level at which carrier on is declared. */ + int32_t carrier_on_power; + /*! \brief The power meter level at which carrier off is declared. */ + int32_t carrier_off_power; + + /*! \brief Current read offset into the equalizer buffer. */ + int eq_step; + /*! \brief Current write offset into the equalizer buffer. */ + int eq_put_step; + /*! \brief Symbol counter to the next equalizer update. */ + int eq_skip; + + /*! \brief The current half of the baud. */ + int baud_half; + +#if defined(SPANDSP_USE_FIXED_POINT) + /*! \brief The scaling factor accessed by the AGC algorithm. */ + int16_t agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + int16_t agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + int16_t eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + complexi16_t eq_coeff[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + complexi16_t eq_coeff_save[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + complexi16_t eq_buf[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + + /*! Low band edge filter for symbol sync. */ + int32_t symbol_sync_low[2]; + /*! High band edge filter for symbol sync. */ + int32_t symbol_sync_high[2]; + /*! DC filter for symbol sync. */ + int32_t symbol_sync_dc_filter[2]; + /*! Baud phase for symbol sync. */ + int32_t baud_phase; +#else + /*! \brief The scaling factor accessed by the AGC algorithm. */ + float agc_scaling; + /*! \brief The previous value of agc_scaling, needed to reuse old training. */ + float agc_scaling_save; + + /*! \brief The current delta factor for updating the equalizer coefficients. */ + float eq_delta; + /*! \brief The adaptive equalizer coefficients. */ + complexf_t eq_coeff[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + /*! \brief A saved set of adaptive equalizer coefficients for use after restarts. */ + complexf_t eq_coeff_save[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + /*! \brief The equalizer signal buffer. */ + complexf_t eq_buf[V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN]; + + /*! Low band edge filter for symbol sync. */ + float symbol_sync_low[2]; + /*! High band edge filter for symbol sync. */ + float symbol_sync_high[2]; + /*! DC filter for symbol sync. */ + float symbol_sync_dc_filter[2]; + /*! Baud phase for symbol sync. */ + float baud_phase; +#endif + + /*! \brief The total symbol timing correction since the carrier came up. + This is only for performance analysis purposes. */ + int total_baud_timing_correction; + + /*! \brief Starting phase angles for the coarse carrier aquisition step. */ + int32_t start_angles[2]; + /*! \brief History list of phase angles for the coarse carrier aquisition step. */ + int32_t angles[16]; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v29tx.h b/Libraries/spandsp/spandsp/spandsp/private/v29tx.h new file mode 100644 index 000000000..129f68c09 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v29tx.h @@ -0,0 +1,100 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v29tx.h - ITU V.29 modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v29tx.h,v 1.2 2009/07/09 13:52:09 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V29TX_H_) +#define _SPANDSP_PRIVATE_V29TX_H_ + +/*! The number of taps in the pulse shaping/bandpass filter */ +#define V29_TX_FILTER_STEPS 9 + +/*! + V.29 modem transmit side descriptor. This defines the working state for a + single instance of a V.29 modem transmitter. +*/ +struct v29_tx_state_s +{ + /*! \brief The bit rate of the modem. Valid values are 4800, 7200 and 9600. */ + int bit_rate; + /*! \brief The callback function used to get the next bit to be transmitted. */ + get_bit_func_t get_bit; + /*! \brief A user specified opaque pointer passed to the get_bit function. */ + void *get_bit_user_data; + + /*! \brief The callback function used to report modem status changes. */ + modem_tx_status_func_t status_handler; + /*! \brief A user specified opaque pointer passed to the status function. */ + void *status_user_data; + + /*! \brief Gain required to achieve the specified output power, not allowing + for the size of the current constellation. */ + float base_gain; + /*! \brief Gain required to achieve the specified output power, allowing + for the size of the current constellation. */ +#if defined(SPANDSP_USE_FIXED_POINT) + int32_t gain; +#else + float gain; +#endif + + /*! \brief The route raised cosine (RRC) pulse shaping filter buffer. */ +#if defined(SPANDSP_USE_FIXED_POINT) + complexi16_t rrc_filter[2*V29_TX_FILTER_STEPS]; +#else + complexf_t rrc_filter[2*V29_TX_FILTER_STEPS]; +#endif + /*! \brief Current offset into the RRC pulse shaping filter buffer. */ + int rrc_filter_step; + + /*! \brief The register for the data scrambler. */ + unsigned int scramble_reg; + /*! \brief The register for the training scrambler. */ + uint8_t training_scramble_reg; + /*! \brief TRUE if transmitting the training sequence, or shutting down transmission. + FALSE if transmitting user data. */ + int in_training; + /*! \brief A counter used to track progress through sending the training sequence. */ + int training_step; + /*! \brief An offset value into the table of training parameters, used to match the + training pattern to the bit rate. */ + int training_offset; + + /*! \brief The current phase of the carrier (i.e. the DDS parameter). */ + uint32_t carrier_phase; + /*! \brief The update rate for the phase of the carrier (i.e. the DDS increment). */ + int32_t carrier_phase_rate; + /*! \brief The current fractional phase of the baud timing. */ + int baud_phase; + /*! \brief The code number for the current position in the constellation. */ + int constellation_state; + /*! \brief The get_bit function in use at any instant. */ + get_bit_func_t current_get_bit; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v42.h b/Libraries/spandsp/spandsp/spandsp/private/v42.h new file mode 100644 index 000000000..1a1347c74 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v42.h @@ -0,0 +1,119 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v42.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v42.h,v 1.2 2009/11/04 15:52:06 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V42_H_) +#define _SPANDSP_PRIVATE_V42_H_ + +/*! + LAP-M descriptor. This defines the working state for a single instance of LAP-M. +*/ +struct lapm_state_s +{ + int handle; + hdlc_rx_state_t hdlc_rx; + hdlc_tx_state_t hdlc_tx; + + v42_frame_handler_t iframe_receive; + void *iframe_receive_user_data; + + v42_status_func_t status_callback; + void *status_callback_user_data; + + int state; + int tx_waiting; + int debug; + /*! TRUE if originator. FALSE if answerer */ + int we_are_originator; + /*! Remote network type (unknown, answerer. originator) */ + int peer_is_originator; + /*! Next N(S) for transmission */ + int next_tx_frame; + /*! The last of our frames which the peer acknowledged */ + int last_frame_peer_acknowledged; + /*! Next N(R) for reception */ + int next_expected_frame; + /*! The last of the peer's frames which we acknowledged */ + int last_frame_we_acknowledged; + /*! TRUE if we sent an I or S frame with the F-bit set */ + int solicit_f_bit; + /*! Retransmission count */ + int retransmissions; + /*! TRUE if peer is busy */ + int busy; + + /*! Acknowledgement timer */ + int t401_timer; + /*! Reply delay timer - optional */ + int t402_timer; + /*! Inactivity timer - optional */ + int t403_timer; + /*! Maximum number of octets in an information field */ + int n401; + /*! Window size */ + int window_size_k; + + lapm_frame_queue_t *txqueue; + lapm_frame_queue_t *tx_next; + lapm_frame_queue_t *tx_last; + queue_state_t *tx_queue; + + span_sched_state_t sched; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +/*! + V.42 descriptor. This defines the working state for a single instance of V.42. +*/ +struct v42_state_s +{ + /*! TRUE if we are the calling party, otherwise FALSE */ + int calling_party; + /*! TRUE if we should detect whether the far end is V.42 capable. FALSE if we go + directly to protocol establishment */ + int detect; + + /*! Stage in negotiating V.42 support */ + int rx_negotiation_step; + int rxbits; + int rxstream; + int rxoks; + int odp_seen; + int txbits; + int txstream; + int txadps; + /*! The LAP.M context */ + lapm_state_t lapm; + + /*! V.42 support detection timer */ + int t400_timer; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v42bis.h b/Libraries/spandsp/spandsp/spandsp/private/v42bis.h new file mode 100644 index 000000000..96538f2e0 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v42bis.h @@ -0,0 +1,151 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v42bis.h + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v42bis.h,v 1.1 2008/11/15 14:43:08 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V42BIS_H_) +#define _SPANDSP_PRIVATE_V42BIS_H_ + +/*! + V.42bis dictionary node. +*/ +typedef struct +{ + /*! \brief The prior code for each defined code. */ + uint16_t parent_code; + /*! \brief The number of leaf nodes this node has */ + int16_t leaves; + /*! \brief This leaf octet for each defined code. */ + uint8_t node_octet; + /*! \brief Bit map of the children which exist */ + uint32_t children[8]; +} v42bis_dict_node_t; + +/*! + V.42bis compression. This defines the working state for a single instance + of V.42bis compression. +*/ +typedef struct +{ + /*! \brief Compression mode. */ + int compression_mode; + /*! \brief Callback function to handle received frames. */ + v42bis_frame_handler_t handler; + /*! \brief An opaque pointer passed in calls to frame_handler. */ + void *user_data; + /*! \brief The maximum frame length allowed */ + int max_len; + + uint32_t string_code; + uint32_t latest_code; + int string_length; + uint32_t output_bit_buffer; + int output_bit_count; + int output_octet_count; + uint8_t output_buf[1024]; + v42bis_dict_node_t dict[V42BIS_MAX_CODEWORDS]; + /*! \brief TRUE if we are in transparent (i.e. uncompressable) mode */ + int transparent; + int change_transparency; + /*! \brief IIR filter state, used in assessing compressibility. */ + int compressibility_filter; + int compressibility_persistence; + + /*! \brief Next empty dictionary entry */ + uint32_t v42bis_parm_c1; + /*! \brief Current codeword size */ + int v42bis_parm_c2; + /*! \brief Threshold for codeword size change */ + uint32_t v42bis_parm_c3; + + /*! \brief Mark that this is the first octet/code to be processed */ + int first; + uint8_t escape_code; +} v42bis_compress_state_t; + +/*! + V.42bis decompression. This defines the working state for a single instance + of V.42bis decompression. +*/ +typedef struct +{ + /*! \brief Callback function to handle decompressed data. */ + v42bis_data_handler_t handler; + /*! \brief An opaque pointer passed in calls to data_handler. */ + void *user_data; + /*! \brief The maximum decompressed data block length allowed */ + int max_len; + + uint32_t old_code; + uint32_t last_old_code; + uint32_t input_bit_buffer; + int input_bit_count; + int octet; + int last_length; + int output_octet_count; + uint8_t output_buf[1024]; + v42bis_dict_node_t dict[V42BIS_MAX_CODEWORDS]; + /*! \brief TRUE if we are in transparent (i.e. uncompressable) mode */ + int transparent; + + int last_extra_octet; + + /*! \brief Next empty dictionary entry */ + uint32_t v42bis_parm_c1; + /*! \brief Current codeword size */ + int v42bis_parm_c2; + /*! \brief Threshold for codeword size change */ + uint32_t v42bis_parm_c3; + + /*! \brief Mark that this is the first octet/code to be processed */ + int first; + uint8_t escape_code; + int escaped; +} v42bis_decompress_state_t; + +/*! + V.42bis compression/decompression descriptor. This defines the working state for a + single instance of V.42bis compress/decompression. +*/ +struct v42bis_state_s +{ + /*! \brief V.42bis data compression directions. */ + int v42bis_parm_p0; + + /*! \brief Compression state. */ + v42bis_compress_state_t compress; + /*! \brief Decompression state. */ + v42bis_decompress_state_t decompress; + + /*! \brief Maximum codeword size (bits) */ + int v42bis_parm_n1; + /*! \brief Total number of codewords */ + uint32_t v42bis_parm_n2; + /*! \brief Maximum string length */ + int v42bis_parm_n7; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/private/v8.h b/Libraries/spandsp/spandsp/spandsp/private/v8.h new file mode 100644 index 000000000..45d56aab9 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/private/v8.h @@ -0,0 +1,78 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * private/v8.h - V.8 modem negotiation processing. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v8.h,v 1.3.4.1 2009/12/28 12:20:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_PRIVATE_V8_H_) +#define _SPANDSP_PRIVATE_V8_H_ + +struct v8_state_s +{ + /*! \brief TRUE if we are the calling modem */ + int calling_party; + + v8_result_handler_t *result_handler; + void *result_handler_user_data; + + /*! \brief The current state of the V.8 protocol */ + int state; + int fsk_tx_on; + int modem_connect_tone_tx_on; + int negotiation_timer; + int ci_timer; + int ci_count; + fsk_tx_state_t v21tx; + fsk_rx_state_t v21rx; + queue_state_t *tx_queue; + modem_connect_tones_tx_state_t ansam_tx; + modem_connect_tones_rx_state_t ansam_rx; + + /*! \brief Modulation schemes available at this end. */ + unsigned int far_end_modulations; + + v8_parms_t parms; + v8_parms_t result; + + /* V.8 data parsing */ + uint32_t bit_stream; + int bit_cnt; + /* Indicates the type of message coming up */ + int preamble_type; + uint8_t rx_data[64]; + int rx_data_ptr; + + /*! \brief a reference copy of the last CM or JM message, used when + testing for matches. */ + uint8_t cm_jm_data[64]; + int cm_jm_len; + int got_cm_jm; + int got_cj; + int zero_byte_count; + /*! \brief Error and flow logging control */ + logging_state_t logging; +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/queue.h b/Libraries/spandsp/spandsp/spandsp/queue.h new file mode 100644 index 000000000..d5e58d449 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/queue.h @@ -0,0 +1,180 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * queue.h - simple in process message queuing + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: queue.h,v 1.21 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +/*! \page queue_page Queuing +\section queue_page_sec_1 What does it do? +This module provides lock free queuing for either octet streams or messages. +Specifically, lock free means one thread can write and another can read without +locking the queue. It does NOT means a free-for-all is possible, with many +threads writing or many threads reading. Those things would require locking, +to avoid conflicts between the multiple threads acting on one end of the queue. + +\section queue_page_sec_2 How does it work? +???. +*/ + +#if !defined(_SPANDSP_QUEUE_H_) +#define _SPANDSP_QUEUE_H_ + +/*! Flag bit to indicate queue reads are atomic operations. This must be set + if the queue is to be used with the message oriented functions. */ +#define QUEUE_READ_ATOMIC 0x0001 +/*! Flag bit to indicate queue writes are atomic operations. This must be set + if the queue is to be used with the message oriented functions. */ +#define QUEUE_WRITE_ATOMIC 0x0002 + +/*! + Queue descriptor. This defines the working state for a single instance of + a byte stream or message oriented queue. +*/ +typedef struct queue_state_s queue_state_t; + +#define QUEUE_STATE_T_SIZE(len) (sizeof(queue_state_t) + len + 1) + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Check if a queue is empty. + \brief Check if a queue is empty. + \param s The queue context. + \return TRUE if empty, else FALSE. */ +SPAN_DECLARE(int) queue_empty(queue_state_t *s); + +/*! Check the available free space in a queue's buffer. + \brief Check available free space. + \param s The queue context. + \return The number of bytes of free space. */ +SPAN_DECLARE(int) queue_free_space(queue_state_t *s); + +/*! Check the contents of a queue. + \brief Check the contents of a queue. + \param s The queue context. + \return The number of bytes in the queue. */ +SPAN_DECLARE(int) queue_contents(queue_state_t *s); + +/*! Flush the contents of a queue. + \brief Flush the contents of a queue. + \param s The queue context. */ +SPAN_DECLARE(void) queue_flush(queue_state_t *s); + +/*! Copy bytes from a queue. This is similar to queue_read, but + the data remains in the queue. + \brief Copy bytes from a queue. + \param s The queue context. + \param buf The buffer into which the bytes will be read. + \param len The length of the buffer. + \return the number of bytes returned. */ +SPAN_DECLARE(int) queue_view(queue_state_t *s, uint8_t *buf, int len); + +/*! Read bytes from a queue. + \brief Read bytes from a queue. + \param s The queue context. + \param buf The buffer into which the bytes will be read. + \param len The length of the buffer. + \return the number of bytes returned. */ +SPAN_DECLARE(int) queue_read(queue_state_t *s, uint8_t *buf, int len); + +/*! Read a byte from a queue. + \brief Read a byte from a queue. + \param s The queue context. + \return the byte, or -1 if the queue is empty. */ +SPAN_DECLARE(int) queue_read_byte(queue_state_t *s); + +/*! Write bytes to a queue. + \brief Write bytes to a queue. + \param s The queue context. + \param buf The buffer containing the bytes to be written. + \param len The length of the buffer. + \return the number of bytes actually written. */ +SPAN_DECLARE(int) queue_write(queue_state_t *s, const uint8_t *buf, int len); + +/*! Write a byte to a queue. + \brief Write a byte to a queue. + \param s The queue context. + \param byte The byte to be written. + \return the number of bytes actually written. */ +SPAN_DECLARE(int) queue_write_byte(queue_state_t *s, uint8_t byte); + +/*! Test the length of the message at the head of a queue. + \brief Test message length. + \param s The queue context. + \return The length of the next message, in byte. If there are + no messages in the queue, -1 is returned. */ +SPAN_DECLARE(int) queue_state_test_msg(queue_state_t *s); + +/*! Read a message from a queue. If the message is longer than the buffer + provided, only the first len bytes of the message will be returned. The + remainder of the message will be discarded. + \brief Read a message from a queue. + \param s The queue context. + \param buf The buffer into which the message will be read. + \param len The length of the buffer. + \return The number of bytes returned. If there are + no messages in the queue, -1 is returned. */ +SPAN_DECLARE(int) queue_read_msg(queue_state_t *s, uint8_t *buf, int len); + +/*! Write a message to a queue. + \brief Write a message to a queue. + \param s The queue context. + \param buf The buffer from which the message will be written. + \param len The length of the message. + \return The number of bytes actually written. */ +SPAN_DECLARE(int) queue_write_msg(queue_state_t *s, const uint8_t *buf, int len); + +/*! Initialise a queue. + \brief Initialise a queue. + \param s The queue context. If is imperative that the context this + points to is immediately followed by a buffer of the required + size + 1 octet. + \param len The length of the queue's buffer. + \param flags Flags controlling the operation of the queue. + Valid flags are QUEUE_READ_ATOMIC and QUEUE_WRITE_ATOMIC. + \return A pointer to the context if OK, else NULL. */ +SPAN_DECLARE(queue_state_t *) queue_init(queue_state_t *s, int len, int flags); + +/*! Release a queue. + \brief Release a queue. + \param s The queue context. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) queue_release(queue_state_t *s); + +/*! Free a queue. + \brief Delete a queue. + \param s The queue context. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) queue_free(queue_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/saturated.h b/Libraries/spandsp/spandsp/spandsp/saturated.h new file mode 100644 index 000000000..0ef6bea9d --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/saturated.h @@ -0,0 +1,224 @@ +#include +/* + * SpanDSP - a series of DSP components for telephony + * + * saturated.h - General saturated arithmetic routines. + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: saturated.h,v 1.4 2009/02/05 12:21:36 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_SATURATED_H_) +#define _SPANDSP_SATURATED_H_ + +/*! \page saturated_page Saturated arithmetic + +\section saturated_page_sec_1 What does it do? + + +\section saturated_page_sec_2 How does it work? + +*/ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ int16_t saturate(int32_t amp) +{ + int16_t amp16; + + /* Hopefully this is optimised for the common case - not clipping */ + amp16 = (int16_t) amp; + if (amp == amp16) + return amp16; + if (amp > INT16_MAX) + return INT16_MAX; + return INT16_MIN; +} +/*- End of function --------------------------------------------------------*/ + +/*! Saturate to 15 bits, rather than the usual 16 bits. This is often a useful function. */ +static __inline__ int16_t saturate15(int32_t amp) +{ + if (amp > 16383) + return 16383; + if (amp < -16384) + return -16384; + return (int16_t) amp; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t fsaturatef(float famp) +{ + if (famp > (float) INT16_MAX) + return INT16_MAX; + if (famp < (float) INT16_MIN) + return INT16_MIN; + return (int16_t) lrintf(famp); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t fsaturate(double damp) +{ + if (damp > (double) INT16_MAX) + return INT16_MAX; + if (damp < (double) INT16_MIN) + return INT16_MIN; + return (int16_t) lrint(damp); +} +/*- End of function --------------------------------------------------------*/ + +/* Saturate to a 16 bit integer, using the fastest float to int conversion */ +static __inline__ int16_t ffastsaturatef(float famp) +{ + if (famp > (float) INT16_MAX) + return INT16_MAX; + if (famp < (float) INT16_MIN) + return INT16_MIN; + return (int16_t) lfastrintf(famp); +} +/*- End of function --------------------------------------------------------*/ + +/* Saturate to a 16 bit integer, using the fastest double to int conversion */ +static __inline__ int16_t ffastsaturate(double damp) +{ + if (damp > (double) INT16_MAX) + return INT16_MAX; + if (damp < (double) INT16_MIN) + return INT16_MIN; + return (int16_t) lfastrint(damp); +} +/*- End of function --------------------------------------------------------*/ + +/* Saturate to a 16 bit integer, using the closest float to int conversion */ +static __inline__ float ffsaturatef(float famp) +{ + if (famp > (float) INT16_MAX) + return (float) INT16_MAX; + if (famp < (float) INT16_MIN) + return (float) INT16_MIN; + return famp; +} +/*- End of function --------------------------------------------------------*/ + +/* Saturate to a 16 bit integer, using the closest double to int conversion */ +static __inline__ double ffsaturate(double famp) +{ + if (famp > (double) INT16_MAX) + return (double) INT16_MAX; + if (famp < (double) INT16_MIN) + return (double) INT16_MIN; + return famp; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t saturated_add16(int16_t a, int16_t b) +{ +#if defined(__GNUC__) && defined(__i386__) + __asm__ __volatile__( + " addw %2,%0;\n" + " jno 0f;\n" + " movw $0x7fff,%0;\n" + " adcw $0,%0;\n" + "0:" + : "=r" (a) + : "0" (a), "ir" (b) + : "cc" + ); + return a; +#else + return saturate((int32_t) a + (int32_t) b); +#endif +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int32_t saturated_add32(int32_t a, int32_t b) +{ +#if defined(__GNUC__) && defined(__i386__) + __asm__ __volatile__( + " addl %2,%0;\n" + " jno 0f;\n" + " movl $0x7fffffff,%0;\n" + " adcl $0,%0;\n" + "0:" + : "=r" (a) + : "0" (a), "ir" (b) + : "cc" + ); + return a; +#else + uint32_t A; + + if (a < 0) + { + if (b >= 0) + return a + b; + /*endif*/ + A = (uint32_t) -(a + 1) + (uint32_t) -(b + 1); + return (A >= INT32_MAX) ? INT32_MIN : -(int32_t) A - 2; + } + /*endif*/ + if (b <= 0) + return a + b; + /*endif*/ + A = (uint32_t) a + (uint32_t) b; + return (A > INT32_MAX) ? INT32_MAX : A; +#endif +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t saturated_sub16(int16_t a, int16_t b) +{ + return saturate((int32_t) a - (int32_t) b); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t saturated_mul16(int16_t a, int16_t b) +{ + if (a == INT16_MIN && b == INT16_MIN) + return INT16_MAX; + /*endif*/ + return (int16_t) (((int32_t) a*(int32_t) b) >> 15); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int32_t saturated_mul_16_32(int16_t a, int16_t b) +{ + return ((int32_t) a*(int32_t) b) << 1; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int16_t saturated_abs16(int16_t a) +{ + return (a == INT16_MIN) ? INT16_MAX : (int16_t) abs(a); +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/schedule.h b/Libraries/spandsp/spandsp/spandsp/schedule.h new file mode 100644 index 000000000..20c99f157 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/schedule.h @@ -0,0 +1,70 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * schedule.h + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: schedule.h,v 1.20 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +/*! \page schedule_page Scheduling +\section schedule_page_sec_1 What does it do? +???. + +\section schedule_page_sec_2 How does it work? +???. +*/ + +#if !defined(_SPANDSP_SCHEDULE_H_) +#define _SPANDSP_SCHEDULE_H_ + +/*! A scheduled event entry. */ +typedef struct span_sched_s span_sched_t; + +/*! A scheduled event queue. */ +typedef struct span_sched_state_s span_sched_state_t; + +typedef void (*span_sched_callback_func_t)(span_sched_state_t *s, void *user_data); + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(uint64_t) span_schedule_next(span_sched_state_t *s); +SPAN_DECLARE(uint64_t) span_schedule_time(span_sched_state_t *s); + +SPAN_DECLARE(int) span_schedule_event(span_sched_state_t *s, int us, span_sched_callback_func_t function, void *user_data); +SPAN_DECLARE(void) span_schedule_update(span_sched_state_t *s, int us); +SPAN_DECLARE(void) span_schedule_del(span_sched_state_t *s, int id); + +SPAN_DECLARE(span_sched_state_t *) span_schedule_init(span_sched_state_t *s); +SPAN_DECLARE(int) span_schedule_release(span_sched_state_t *s); +SPAN_DECLARE(int) span_schedule_free(span_sched_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/sig_tone.h b/Libraries/spandsp/spandsp/spandsp/sig_tone.h new file mode 100644 index 000000000..64bcd6f42 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/sig_tone.h @@ -0,0 +1,187 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * sig_tone.h - Signalling tone processing for the 2280Hz, 2400Hz, 2600Hz + * and similar signalling tone used in older protocols. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: sig_tone.h,v 1.20 2009/09/04 14:38:46 steveu Exp $ + */ + +/*! \file */ + +/*! \page sig_tone_page The signaling tone processor +\section sig_tone_sec_1 What does it do? +The signaling tone processor handles the 2280Hz, 2400Hz and 2600Hz tones, used +in many analogue signaling procotols, and digital ones derived from them. + +\section sig_tone_sec_2 How does it work? +Most single and two voice frequency signalling systems share many features, as these +features have developed in similar ways over time, to address the limitations of +early tone signalling systems. + +The usual practice is to start the generation of tone at a high energy level, so a +strong signal is available at the receiver, for crisp tone detection. If the tone +remains on for a significant period, the energy level is reduced, to minimise crosstalk. +During the signalling transitions, only the tone is sent through the channel, and the media +signal is suppressed. This means the signalling receiver has a very clean signal to work with, +allowing for crisp detection of the signalling tone. However, when the signalling tone is on +for extended periods, there may be supervisory information in the media signal, such as voice +announcements. To allow these to pass through the system, the signalling tone is mixed with +the media signal. It is the job of the signalling receiver to separate the signalling tone +and the media. The necessary filtering may degrade the quality of the voice signal, but at +least supervisory information may be heard. +*/ + +#if !defined(_SPANDSP_SIG_TONE_H_) +#define _SPANDSP_SIG_TONE_H_ + +/* The optional tone sets */ +enum +{ + /*! European 2280Hz signaling tone. Tone 1 is 2280Hz. Tone 2 is not used. */ + SIG_TONE_2280HZ = 1, + /*! US 2600Hz signaling tone. Tone 1 is 2600Hz. Tone 2 is not used. */ + SIG_TONE_2600HZ, + /*! US 2400Hz + 2600Hz signaling tones. Tone 1 is 2600Hz. Tone 2 is 2400Hz. */ + SIG_TONE_2400HZ_2600HZ +}; + +/* Mode control and report bits for transmit and receive */ +enum +{ + /*! Signaling tone 1 is present */ + SIG_TONE_1_PRESENT = 0x001, + /*! Signaling tone 1 has changed state (ignored when setting tx mode) */ + SIG_TONE_1_CHANGE = 0x002, + /*! Signaling tone 2 is present */ + SIG_TONE_2_PRESENT = 0x004, + /*! Signaling tone 2 has changed state (ignored when setting tx mode) */ + SIG_TONE_2_CHANGE = 0x008, + /*! The media signal is passing through. Tones might be added to it. */ + SIG_TONE_TX_PASSTHROUGH = 0x010, + /*! The media signal is passing through. Tones might be extracted from it, if detected. */ + SIG_TONE_RX_PASSTHROUGH = 0x040, + /*! Force filtering of the signaling tone, whether signaling is being detected or not. + This is mostly useful for test purposes. */ + SIG_TONE_RX_FILTER_TONE = 0x080, + /*! Request an update of the transmit status, upon timeout of the previous status. */ + SIG_TONE_TX_UPDATE_REQUEST = 0x100, + /*! Request an update of the receiver status, upon timeout of the previous status. */ + SIG_TONE_RX_UPDATE_REQUEST = 0x200 +}; + +/*! + Signaling tone descriptor. This defines the working state for a + single instance of the transmit and receive sides of a signaling + tone processor. +*/ +typedef struct sig_tone_descriptor_s sig_tone_descriptor_t; + +typedef struct sig_tone_tx_state_s sig_tone_tx_state_t; + +typedef struct sig_tone_rx_state_s sig_tone_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Process a block of received audio samples. + \brief Process a block of received audio samples. + \param s The signaling tone context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len); + +/*! Set the receive mode. + \brief Set the receive mode. + \param s The signaling tone context. + \param mode The new mode for the receiver. + \param duration The duration for this mode, before an update is requested. + A duration of zero means forever. */ +SPAN_DECLARE(void) sig_tone_rx_set_mode(sig_tone_rx_state_t *s, int mode, int duration); + +/*! Initialise a signaling tone receiver context. + \brief Initialise a signaling tone context. + \param s The signaling tone context. + \param tone_type The type of signaling tone. + \param sig_update Callback function to handle signaling updates. + \param user_data An opaque pointer. + \return A pointer to the signalling tone context, or NULL if there was a problem. */ +SPAN_DECLARE(sig_tone_rx_state_t *) sig_tone_rx_init(sig_tone_rx_state_t *s, int tone_type, tone_report_func_t sig_update, void *user_data); + +/*! Release a signaling tone receiver context. + \brief Release a signaling tone receiver context. + \param s The signaling tone context. + \return 0 for OK */ +SPAN_DECLARE(int) sig_tone_rx_release(sig_tone_rx_state_t *s); + +/*! Free a signaling tone receiver context. + \brief Free a signaling tone receiver context. + \param s The signaling tone context. + \return 0 for OK */ +SPAN_DECLARE(int) sig_tone_rx_free(sig_tone_rx_state_t *s); + +/*! Generate a block of signaling tone audio samples. + \brief Generate a block of signaling tone audio samples. + \param s The signaling tone context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. */ +SPAN_DECLARE(int) sig_tone_tx(sig_tone_tx_state_t *s, int16_t amp[], int len); + +/*! Set the tone mode. + \brief Set the tone mode. + \param s The signaling tone context. + \param mode The new mode for the transmitted tones. + \param duration The duration for this mode, before an update is requested. + A duration of zero means forever. */ +SPAN_DECLARE(void) sig_tone_tx_set_mode(sig_tone_tx_state_t *s, int mode, int duration); + +/*! Initialise a signaling tone transmitter context. + \brief Initialise a signaling tone context. + \param s The signaling tone context. + \param tone_type The type of signaling tone. + \param sig_update Callback function to handle signaling updates. + \param user_data An opaque pointer. + \return A pointer to the signalling tone context, or NULL if there was a problem. */ +SPAN_DECLARE(sig_tone_tx_state_t *) sig_tone_tx_init(sig_tone_tx_state_t *s, int tone_type, tone_report_func_t sig_update, void *user_data); + +/*! Release a signaling tone transmitter context. + \brief Release a signaling tone transmitter context. + \param s The signaling tone context. + \return 0 for OK */ +SPAN_DECLARE(int) sig_tone_tx_release(sig_tone_tx_state_t *s); + +/*! Free a signaling tone transmitter context. + \brief Free a signaling tone transmitter context. + \param s The signaling tone context. + \return 0 for OK */ +SPAN_DECLARE(int) sig_tone_tx_free(sig_tone_tx_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/silence_gen.h b/Libraries/spandsp/spandsp/spandsp/silence_gen.h new file mode 100644 index 000000000..467408d75 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/silence_gen.h @@ -0,0 +1,143 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * silence_gen.c - A silence generator, for inserting timed silences. + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: silence_gen.h,v 1.19 2009/09/04 14:38:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_SILENCE_GEN_H_) +#define _SPANDSP_SILENCE_GEN_H_ + +typedef struct silence_gen_state_s silence_gen_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Generate a block of silent audio samples. + \brief Generate a block of silent audio samples. + \param s The silence generator context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. This will be zero when + there is nothing to send. +*/ +SPAN_DECLARE_NONSTD(int) silence_gen(silence_gen_state_t *s, int16_t *amp, int max_len); + +/*! Set a silence generator context to output continuous silence. + \brief Set a silence generator context to output continuous silence. + \param s The silence generator context. +*/ +SPAN_DECLARE(void) silence_gen_always(silence_gen_state_t *s); + +/*! Set a silence generator context to output a specified period of silence. + \brief Set a silence generator context to output a specified period of silence. + \param s The silence generator context. + \param silent_samples The number of samples to be generated. +*/ +SPAN_DECLARE(void) silence_gen_set(silence_gen_state_t *s, int silent_samples); + +/*! Alter the period of a silence generator context by a specified amount. + \brief Alter the period of a silence generator context by a specified amount. + \param s The silence generator context. + \param silent_samples The number of samples to change the setting by. A positive number + increases the duration. A negative number reduces it. The duration + is prevented from going negative. +*/ +SPAN_DECLARE(void) silence_gen_alter(silence_gen_state_t *s, int silent_samples); + +/*! Find how long a silence generator context has to run. + \brief Find how long a silence generator context has to run. + \param s The silence generator context. + \return The number of samples remaining. +*/ +SPAN_DECLARE(int) silence_gen_remainder(silence_gen_state_t *s); + +/*! Find the total silence generated to date by a silence generator context. + \brief Find the total silence generated to date. + \param s The silence generator context. + \return The number of samples generated. +*/ +SPAN_DECLARE(int) silence_gen_generated(silence_gen_state_t *s); + +/*! Change the status reporting function associated with a silence generator context. + \brief Change the status reporting function associated with a silence generator context. + \param s The silence generator context. + \param handler The callback routine used to report status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) silence_gen_status_handler(silence_gen_state_t *s, modem_tx_status_func_t handler, void *user_data); + +/*! Initialise a timed silence generator context. + \brief Initialise a timed silence generator context. + \param s The silence generator context. + \param silent_samples The initial number of samples to set the silence to. + \return A pointer to the silence generator context. +*/ +SPAN_DECLARE(silence_gen_state_t *) silence_gen_init(silence_gen_state_t *s, int silent_samples); + +SPAN_DECLARE(int) silence_gen_release(silence_gen_state_t *s); + +SPAN_DECLARE(int) silence_gen_free(silence_gen_state_t *s); + +/* The following dummy routines, to absorb data, don't really have a proper home, + so they have been put here. */ + +/*! A dummy routine to use as a receive callback, when we aren't really + trying to process what is received. It just absorbs and ignores the + data. + \brief Dummy receive callback. + \param user_data The context. + \param amp The signal.buffer + \param len The length of the signal buffer + \return 0. +*/ +SPAN_DECLARE_NONSTD(int) span_dummy_rx(void *user_data, const int16_t amp[], int len); + +/*! A dummy routine to use as a signal modifier callback, when we aren't + really trying to process the signal. It just returns without affecting + anything. + \brief Dummy signal modifier callback. + \param user_data The context. + \param amp The signal.buffer + \param len The length of the signal buffer + \return 0. +*/ +SPAN_DECLARE(int) span_dummy_mod(void *user_data, int16_t amp[], int len); + +/*! A dummy routine to use as a receive fillin callback, when we aren't really + trying to process what is received. It just absorbs and ignores the + request. + \brief Dummy receive fillin callback. + \param user_data The context. + \param len The length of the signal buffer + \return 0. +*/ +SPAN_DECLARE_NONSTD(int) span_dummy_rx_fillin(void *user_data, int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/super_tone_rx.h b/Libraries/spandsp/spandsp/spandsp/super_tone_rx.h new file mode 100644 index 000000000..790c694e6 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/super_tone_rx.h @@ -0,0 +1,152 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * super_tone_rx.h - Flexible telephony supervisory tone detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: super_tone_rx.h,v 1.21 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_SUPER_TONE_RX_H_) +#define _SPANDSP_SUPER_TONE_RX_H_ + +/*! \page super_tone_rx_page Supervisory tone detection + +\section super_tone_rx_page_sec_1 What does it do? + +The supervisory tone detector may be configured to detect most of the world's +telephone supervisory tones - things like ringback, busy, number unobtainable, +and so on. + +\section super_tone_rx_page_sec_2 How does it work? + +The supervisory tone detector is passed a series of data structures describing +the tone patterns - the frequencies and cadencing - of the tones to be searched +for. It constructs one or more Goertzel filters to monitor the required tones. +If tones are close in frequency a single Goertzel set to the centre of the +frequency range will be used. This optimises the efficiency of the detector. The +Goertzel filters are applied without applying any special window functional +(i.e. they use a rectangular window), so they have a sinc like response. +However, for most tone patterns their rejection qualities are adequate. + +The detector aims to meet the need of the standard call progress tones, to +ITU-T E.180/Q.35 (busy, dial, ringback, reorder). Also, the extended tones, +to ITU-T E.180, Supplement 2 and EIA/TIA-464-A (recall dial tone, special +ringback tone, intercept tone, call waiting tone, busy verification tone, +executive override tone, confirmation tone). +*/ + +/*! Tone detection indication callback routine */ +typedef void (*tone_report_func_t)(void *user_data, int code, int level, int delay); + +typedef struct super_tone_rx_segment_s super_tone_rx_segment_t; + +typedef struct super_tone_rx_descriptor_s super_tone_rx_descriptor_t; + +typedef struct super_tone_rx_state_s super_tone_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Create a new supervisory tone detector descriptor. + \param desc The supervisory tone set desciptor. If NULL, the routine will allocate space for a + descriptor. + \return The supervisory tone set descriptor. +*/ +SPAN_DECLARE(super_tone_rx_descriptor_t *) super_tone_rx_make_descriptor(super_tone_rx_descriptor_t *desc); + +/*! Free a supervisory tone detector descriptor. + \param desc The supervisory tone set desciptor. + \return 0 for OK, -1 for fail. +*/ +SPAN_DECLARE(int) super_tone_rx_free_descriptor(super_tone_rx_descriptor_t *desc); + +/*! Add a new tone pattern to a supervisory tone detector set. + \param desc The supervisory tone set descriptor. + \return The new tone ID. */ +SPAN_DECLARE(int) super_tone_rx_add_tone(super_tone_rx_descriptor_t *desc); + +/*! Add a new tone pattern element to a tone pattern in a supervisory tone detector. + \param desc The supervisory tone set desciptor. + \param tone The tone ID within the descriptor. + \param f1 Frequency 1 (-1 for a silent period). + \param f2 Frequency 2 (-1 for a silent period, or only one frequency). + \param min The minimum duration, in ms. + \param max The maximum duration, in ms. + \return The new number of elements in the tone description. +*/ +SPAN_DECLARE(int) super_tone_rx_add_element(super_tone_rx_descriptor_t *desc, + int tone, + int f1, + int f2, + int min, + int max); + +/*! Initialise a supervisory tone detector. + \param s The supervisory tone detector context. + \param desc The tone descriptor. + \param callback The callback routine called to report the valid detection or termination of + one of the monitored tones. + \param user_data An opaque pointer passed when calling the callback routine. + \return The supervisory tone detector context. +*/ +SPAN_DECLARE(super_tone_rx_state_t *) super_tone_rx_init(super_tone_rx_state_t *s, + super_tone_rx_descriptor_t *desc, + tone_report_func_t callback, + void *user_data); + +/*! Release a supervisory tone detector. + \param s The supervisory tone context. + \return 0 for OK, -1 for fail. +*/ +SPAN_DECLARE(int) super_tone_rx_release(super_tone_rx_state_t *s); + +/*! Free a supervisory tone detector. + \param s The supervisory tone context. + \return 0 for OK, -1 for fail. +*/ +SPAN_DECLARE(int) super_tone_rx_free(super_tone_rx_state_t *s); + +/*! Define a callback routine to be called each time a tone pattern element is complete. This is + mostly used when analysing a tone. + \param s The supervisory tone context. + \param callback The callback routine. +*/ +SPAN_DECLARE(void) super_tone_rx_segment_callback(super_tone_rx_state_t *s, + void (*callback)(void *data, int f1, int f2, int duration)); + +/*! Apply supervisory tone detection processing to a block of audio samples. + \brief Apply supervisory tone detection processing to a block of audio samples. + \param super The supervisory tone context. + \param amp The audio sample buffer. + \param samples The number of samples in the buffer. + \return The number of samples processed. +*/ +SPAN_DECLARE(int) super_tone_rx(super_tone_rx_state_t *super, const int16_t amp[], int samples); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/super_tone_tx.h b/Libraries/spandsp/spandsp/spandsp/super_tone_tx.h new file mode 100644 index 000000000..b2de99fbb --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/super_tone_tx.h @@ -0,0 +1,87 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * super_tone_tx.h - Flexible telephony supervisory tone generation. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: super_tone_tx.h,v 1.17 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_SUPER_TONE_TX_H_) +#define _SPANDSP_SUPER_TONE_TX_H_ + +/*! \page super_tone_tx_page Supervisory tone generation + +\section super_tone_tx_page_sec_1 What does it do? + +The supervisory tone generator may be configured to generate most of the world's +telephone supervisory tones - things like ringback, busy, number unobtainable, +and so on. It uses tree structure tone descriptions, which can deal with quite +complex cadence patterns. + +\section super_tone_tx_page_sec_2 How does it work? + +*/ + +typedef struct super_tone_tx_step_s super_tone_tx_step_t; + +typedef struct super_tone_tx_state_s super_tone_tx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(super_tone_tx_step_t *) super_tone_tx_make_step(super_tone_tx_step_t *s, + float f1, + float l1, + float f2, + float l2, + int length, + int cycles); + +SPAN_DECLARE(int) super_tone_tx_free_tone(super_tone_tx_step_t *s); + +/*! Initialise a supervisory tone generator. + \brief Initialise a supervisory tone generator. + \param s The supervisory tone generator context. + \param tree The supervisory tone tree to be generated. + \return The supervisory tone generator context. */ +SPAN_DECLARE(super_tone_tx_state_t *) super_tone_tx_init(super_tone_tx_state_t *s, super_tone_tx_step_t *tree); + +SPAN_DECLARE(int) super_tone_tx_release(super_tone_tx_state_t *s); + +SPAN_DECLARE(int) super_tone_tx_free(super_tone_tx_state_t *s); + +/*! Generate a block of audio samples for a supervisory tone pattern. + \brief Generate a block of audio samples for a supervisory tone pattern. + \param s The supervisory tone context. + \param amp The audio sample buffer. + \param max_samples The maximum number of samples to be generated. + \return The number of samples generated. */ +SPAN_DECLARE(int) super_tone_tx(super_tone_tx_state_t *s, int16_t amp[], int max_samples); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/swept_tone.h b/Libraries/spandsp/spandsp/spandsp/swept_tone.h new file mode 100644 index 000000000..414f951fb --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/swept_tone.h @@ -0,0 +1,59 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * swept_tone.h - Swept tone generation + * + * Written by Steve Underwood + * + * Copyright (C) 2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: swept_tone.h,v 1.1 2009/09/22 12:54:33 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_SWEPT_TONE_H_) +#define _SPANDSP_SWEPT_TONE_H_ + +/*! \page swept_tone_page The swept tone generator +\section swept_tone_page_sec_1 What does it do? +*/ + +typedef struct swept_tone_state_s swept_tone_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(swept_tone_state_t *) swept_tone_init(swept_tone_state_t *s, float start, float end, float level, int duration, int repeating); + +SPAN_DECLARE(int) swept_tone(swept_tone_state_t *s, int16_t amp[], int len); + +SPAN_DECLARE(float) swept_tone_current_frequency(swept_tone_state_t *s); + +SPAN_DECLARE(int) swept_tone_release(swept_tone_state_t *s); + +SPAN_DECLARE(int) swept_tone_free(swept_tone_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t30.h b/Libraries/spandsp/spandsp/spandsp/t30.h new file mode 100644 index 000000000..3c1b44103 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t30.h @@ -0,0 +1,683 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t30.h - definitions for T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30.h,v 1.126.4.1 2009/12/19 09:47:56 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T30_H_) +#define _SPANDSP_T30_H_ + +/*! \page t30_page T.30 FAX protocol handling + +\section t30_page_sec_1 What does it do? +The T.30 protocol is the core protocol used for FAX transmission. This module +implements most of its key featrues. It does not interface to the outside work. +Seperate modules do that for T.38, analogue line, and other forms of FAX +communication. + +Current features of this module include: + + - FAXing to and from multi-page TIFF/F files, whose images are one of the standard + FAX sizes. + - V.27ter, V.29 and V.17 modes (2400bps, to 14,400bps). + - T.4 1D (MH), T.4 2D,(MR) and T.6 (MMR) compression. + - Error correction mode (ECM). + - All standard horizonal resolutions (R8, R16, 300dpi, 600dpi, 800dpi, 1200dpi). + - All standard vertical resolutions (standard, fine, superfine, 300dpi, 600dpi, 800dpi, 1200dpi). + - All standard page widths (A4, B4, A3). + - All standard page lengths (A4, B4, North American letter, North American legal, continuous). + - Monitoring and sending identifier strings (CSI, TSI, and CIG). + - Monitoring and sending sub-address strings (SUB). + - Monitoring and sending polling sub-addresses (SEP). + - Monitoring and sending polled sub-addresses (PSA). + - Monitoring and sending sender identifications (SID). + - Monitoring and sending passwords (PWD). + - Monitoring of non-standard facility frames (NSF, NSC, and NSS). + - Sending custom non-standard facility frames (NSF, NSC, and NSS). + - Analogue modem and T.38 operation. + +\section t30_page_sec_2 How does it work? + +Some of the following is paraphrased from some notes found a while ago on the Internet. +I cannot remember exactly where they came from, but they are useful. + +\subsection t30_page_sec_2a The answer (CED) tone + +The T.30 standard says an answering fax device must send CED (a 2100Hz tone) for +approximately 3 seconds before sending the first handshake message. Some machines +send an 1100Hz or 1850Hz tone, and some send no tone at all. In fact, this answer +tone is so unpredictable, it cannot really be used. It should, however, always be +generated according to the specification. + +\subsection t30_page_sec_2b Common Timing Deviations + +The T.30 spec. specifies a number of time-outs. For example, after dialing a number, +a calling fax system should listen for a response for 35 seconds before giving up. +These time-out periods are as follows: + + - T1 - 35+-5s: the maximum time for which two fax system will attempt to identify each other + - T2 - 6+-1s: a time-out used to start the sequence for changing transmit parameters + - T3 - 10+-5s: a time-out used in handling operator interrupts + - T5 - 60+-5s: a time-out used in error correction mode + +These time-outs are sometimes misinterpreted. In addition, they are routinely +ignored, sometimes with good reason. For example, after placing a call, the +calling fax system is supposed to wait for 35 seconds before giving up. If the +answering unit does not answer on the first ring or if a voice answering machine +is connected to the line, or if there are many delays through the network, +the delay before answer can be much longer than 35 seconds. + +Fax units that support error correction mode (ECM) can respond to a post-image +handshake message with a receiver not ready (RNR) message. The calling unit then +queries the receiving fax unit with a receiver ready (RR) message. If the +answering unit is still busy (printing for example), it will repeat the RNR +message. According to the T.30 standard, this sequence (RR/RNR RR/RNR) can be +repeated for up to the end of T5 (60+-5s). However, many fax systems +ignore the time-out and will continue the sequence indefinitely, unless the user +manually overrides. + +All the time-outs are subject to alteration, and sometimes misuse. Good T.30 +implementations must do the right thing, and tolerate others doing the wrong thing. + +\subsection t30_page_sec_2c Variations in the inter-carrier gap + +T.30 specifies 75+-20ms of silence between signals using different modulation +schemes. Examples are between the end of a DCS signal and the start of a TCF signal, +and between the end of an image and the start of a post-image signal. Many fax systems +violate this requirement, especially for the silent period between DCS and TCF. +This may be stretched to well over 100ms. If this period is too long, it can interfere with +handshake signal error recovery, should a packet be corrupted on the line. Systems +should ensure they stay within the prescribed T.30 limits, and be tolerant of others +being out of spec.. + +\subsection t30_page_sec_2d Other timing variations + +Testing is required to determine the ability of a fax system to handle +variations in the duration of pauses between unacknowledged handshake message +repetitions, and also in the pauses between the receipt of a handshake command and +the start of a response to that command. In order to reduce the total +transmission time, many fax systems start sending a response message before the +end of the command has been received. + +\subsection t30_page_sec_2e Other deviations from the T.30 standard + +There are many other commonly encountered variations between machines, including: + + - frame sequence deviations + - preamble and flag sequence variations + - improper EOM usage + - unusual data rate fallback sequences + - common training pattern detection algorithms + - image transmission deviations + - use of the talker echo protect tone + - image padding and short lines + - RTP/RTN handshake message usage + - long duration lines + - nonstandard disconnect sequences + - DCN usage +*/ + +/*! The maximum length of a DIS, DTC or DCS frame */ +#define T30_MAX_DIS_DTC_DCS_LEN 22 +/*! The maximum length of the body of an ident string */ +#define T30_MAX_IDENT_LEN 20 +/*! The maximum length of the user string to insert in page headers */ +#define T30_MAX_PAGE_HEADER_INFO 50 + +typedef struct t30_state_s t30_state_t; + +/*! + T.30 phase B callback handler. This handler can be used to process addition + information available in some FAX calls, such as passwords. The callback handler + can access whatever additional information might have been received, using + t30_get_received_info(). + \brief T.30 phase B callback handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param result The phase B event code. + \return The new status. Normally, T30_ERR_OK is returned. +*/ +typedef int (t30_phase_b_handler_t)(t30_state_t *s, void *user_data, int result); + +/*! + T.30 phase D callback handler. + \brief T.30 phase D callback handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param result The phase D event code. + \return The new status. Normally, T30_ERR_OK is returned. +*/ +typedef int (t30_phase_d_handler_t)(t30_state_t *s, void *user_data, int result); + +/*! + T.30 phase E callback handler. + \brief T.30 phase E callback handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param completion_code The phase E completion code. +*/ +typedef void (t30_phase_e_handler_t)(t30_state_t *s, void *user_data, int completion_code); + +/*! + T.30 real time frame handler. + \brief T.30 real time frame handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param direction TRUE for incoming, FALSE for outgoing. + \param msg The HDLC message. + \param len The length of the message. +*/ +typedef void (t30_real_time_frame_handler_t)(t30_state_t *s, + void *user_data, + int direction, + const uint8_t msg[], + int len); + +/*! + T.30 document handler. + \brief T.30 document handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param result The document event code. +*/ +typedef int (t30_document_handler_t)(t30_state_t *s, void *user_data, int status); + +/*! + T.30 set a receive or transmit type handler. + \brief T.30 set a receive or transmit type handler. + \param user_data An opaque pointer. + \param type The modem, tone or silence to be sent or received. + \param bit_rate The bit rate of the modem to be sent or received. + \param short_train TRUE if the short training sequence should be used (where one exists). + \param use_hdlc FALSE for bit stream, TRUE for HDLC framing. +*/ +typedef void (t30_set_handler_t)(void *user_data, int type, int bit_rate, int short_train, int use_hdlc); + +/*! + T.30 send HDLC handler. + \brief T.30 send HDLC handler. + \param user_data An opaque pointer. + \param msg The HDLC message. + \param len The length of the message. +*/ +typedef void (t30_send_hdlc_handler_t)(void *user_data, const uint8_t msg[], int len); + +/*! + T.30 protocol completion codes, at phase E. +*/ +enum +{ + T30_ERR_OK = 0, /*! OK */ + + /* Link problems */ + T30_ERR_CEDTONE, /*! The CED tone exceeded 5s */ + T30_ERR_T0_EXPIRED, /*! Timed out waiting for initial communication */ + T30_ERR_T1_EXPIRED, /*! Timed out waiting for the first message */ + T30_ERR_T3_EXPIRED, /*! Timed out waiting for procedural interrupt */ + T30_ERR_HDLC_CARRIER, /*! The HDLC carrier did not stop in a timely manner */ + T30_ERR_CANNOT_TRAIN, /*! Failed to train with any of the compatible modems */ + T30_ERR_OPER_INT_FAIL, /*! Operator intervention failed */ + T30_ERR_INCOMPATIBLE, /*! Far end is not compatible */ + T30_ERR_RX_INCAPABLE, /*! Far end is not able to receive */ + T30_ERR_TX_INCAPABLE, /*! Far end is not able to transmit */ + T30_ERR_NORESSUPPORT, /*! Far end cannot receive at the resolution of the image */ + T30_ERR_NOSIZESUPPORT, /*! Far end cannot receive at the size of image */ + T30_ERR_UNEXPECTED, /*! Unexpected message received */ + + /* Phase E status values returned to a transmitter */ + T30_ERR_TX_BADDCS, /*! Received bad response to DCS or training */ + T30_ERR_TX_BADPG, /*! Received a DCN from remote after sending a page */ + T30_ERR_TX_ECMPHD, /*! Invalid ECM response received from receiver */ + T30_ERR_TX_GOTDCN, /*! Received a DCN while waiting for a DIS */ + T30_ERR_TX_INVALRSP, /*! Invalid response after sending a page */ + T30_ERR_TX_NODIS, /*! Received other than DIS while waiting for DIS */ + T30_ERR_TX_PHBDEAD, /*! Received no response to DCS, training or TCF */ + T30_ERR_TX_PHDDEAD, /*! No response after sending a page */ + T30_ERR_TX_T5EXP, /*! Timed out waiting for receiver ready (ECM mode) */ + + /* Phase E status values returned to a receiver */ + T30_ERR_RX_ECMPHD, /*! Invalid ECM response received from transmitter */ + T30_ERR_RX_GOTDCS, /*! DCS received while waiting for DTC */ + T30_ERR_RX_INVALCMD, /*! Unexpected command after page received */ + T30_ERR_RX_NOCARRIER, /*! Carrier lost during fax receive */ + T30_ERR_RX_NOEOL, /*! Timed out while waiting for EOL (end Of line) */ + T30_ERR_RX_NOFAX, /*! Timed out while waiting for first line */ + T30_ERR_RX_T2EXPDCN, /*! Timer T2 expired while waiting for DCN */ + T30_ERR_RX_T2EXPD, /*! Timer T2 expired while waiting for phase D */ + T30_ERR_RX_T2EXPFAX, /*! Timer T2 expired while waiting for fax page */ + T30_ERR_RX_T2EXPMPS, /*! Timer T2 expired while waiting for next fax page */ + T30_ERR_RX_T2EXPRR, /*! Timer T2 expired while waiting for RR command */ + T30_ERR_RX_T2EXP, /*! Timer T2 expired while waiting for NSS, DCS or MCF */ + T30_ERR_RX_DCNWHY, /*! Unexpected DCN while waiting for DCS or DIS */ + T30_ERR_RX_DCNDATA, /*! Unexpected DCN while waiting for image data */ + T30_ERR_RX_DCNFAX, /*! Unexpected DCN while waiting for EOM, EOP or MPS */ + T30_ERR_RX_DCNPHD, /*! Unexpected DCN after EOM or MPS sequence */ + T30_ERR_RX_DCNRRD, /*! Unexpected DCN after RR/RNR sequence */ + T30_ERR_RX_DCNNORTN, /*! Unexpected DCN after requested retransmission */ + + /* TIFF file problems */ + T30_ERR_FILEERROR, /*! TIFF/F file cannot be opened */ + T30_ERR_NOPAGE, /*! TIFF/F page not found */ + T30_ERR_BADTIFF, /*! TIFF/F format is not compatible */ + T30_ERR_BADPAGE, /*! TIFF/F page number tag missing */ + T30_ERR_BADTAG, /*! Incorrect values for TIFF/F tags */ + T30_ERR_BADTIFFHDR, /*! Bad TIFF/F header - incorrect values in fields */ + T30_ERR_NOMEM, /*! Cannot allocate memory for more pages */ + + /* General problems */ + T30_ERR_RETRYDCN, /*! Disconnected after permitted retries */ + T30_ERR_CALLDROPPED, /*! The call dropped prematurely */ + + /* Feature negotiation issues */ + T30_ERR_NOPOLL, /*! Poll not accepted */ + T30_ERR_IDENT_UNACCEPTABLE, /*! Far end's ident is not acceptable */ + T30_ERR_SUB_UNACCEPTABLE, /*! Far end's sub-address is not acceptable */ + T30_ERR_SEP_UNACCEPTABLE, /*! Far end's selective polling address is not acceptable */ + T30_ERR_PSA_UNACCEPTABLE, /*! Far end's polled sub-address is not acceptable */ + T30_ERR_SID_UNACCEPTABLE, /*! Far end's sender identification is not acceptable */ + T30_ERR_PWD_UNACCEPTABLE, /*! Far end's password is not acceptable */ + T30_ERR_TSA_UNACCEPTABLE, /*! Far end's transmitting subscriber internet address is not acceptable */ + T30_ERR_IRA_UNACCEPTABLE, /*! Far end's internet routing address is not acceptable */ + T30_ERR_CIA_UNACCEPTABLE, /*! Far end's calling subscriber internet address is not acceptable */ + T30_ERR_ISP_UNACCEPTABLE, /*! Far end's internet selective polling address is not acceptable */ + T30_ERR_CSA_UNACCEPTABLE /*! Far end's called subscriber internet address is not acceptable */ +}; + +/*! + I/O modes for the T.30 protocol. + These are allocated such that the lower 4 bits represents the variant of the modem - e.g. the + particular bit rate selected. +*/ +enum +{ + T30_MODEM_NONE = 0, + T30_MODEM_PAUSE, + T30_MODEM_CED, + T30_MODEM_CNG, + T30_MODEM_V21, + T30_MODEM_V27TER, + T30_MODEM_V29, + T30_MODEM_V17, + T30_MODEM_DONE +}; + +enum +{ + T30_FRONT_END_SEND_STEP_COMPLETE = 0, + /*! The current receive has completed. This is only needed to report an + unexpected end of the receive operation, as might happen with T.38 + dying. */ + T30_FRONT_END_RECEIVE_COMPLETE, + T30_FRONT_END_SIGNAL_PRESENT, + T30_FRONT_END_SIGNAL_ABSENT, + T30_FRONT_END_CED_PRESENT, + T30_FRONT_END_CNG_PRESENT +}; + +enum +{ + /*! Support the V.27ter modem (2400, and 4800bps) for image transfer. */ + T30_SUPPORT_V27TER = 0x01, + /*! Support the V.29 modem (9600, and 7200bps) for image transfer. */ + T30_SUPPORT_V29 = 0x02, + /*! Support the V.17 modem (14400, 12000, 9600 and 7200bps) for image transfer. */ + T30_SUPPORT_V17 = 0x04, + /*! Support the V.34 modem (up to 33,600bps) for image transfer. */ + T30_SUPPORT_V34 = 0x08, + /*! Support the Internet aware FAX mode (no bit rate limit) for image transfer. */ + T30_SUPPORT_IAF = 0x10 +}; + +enum +{ + /*! No compression */ + T30_SUPPORT_NO_COMPRESSION = 0x01, + /*! T.1 1D compression */ + T30_SUPPORT_T4_1D_COMPRESSION = 0x02, + /*! T.4 2D compression */ + T30_SUPPORT_T4_2D_COMPRESSION = 0x04, + /*! T.6 2D compression */ + T30_SUPPORT_T6_COMPRESSION = 0x08, + /*! T.85 monochrome JBIG compression */ + T30_SUPPORT_T85_COMPRESSION = 0x10, + /*! T.43 colour JBIG compression */ + T30_SUPPORT_T43_COMPRESSION = 0x20, + /*! T.45 run length colour compression */ + T30_SUPPORT_T45_COMPRESSION = 0x40, + /*! T.81 + T.30 Annex E colour JPEG compression */ + T30_SUPPORT_T81_COMPRESSION = 0x80, + /*! T.81 + T.30 Annex K colour sYCC-JPEG compression */ + T30_SUPPORT_SYCC_T81_COMPRESSION = 0x100, + /*! T.88 monochrome JBIG2 compression */ + T30_SUPPORT_T88_COMPRESSION = 0x200 +}; + +enum +{ + /*! Support standard FAX Y-resolution 98/100dpi */ + T30_SUPPORT_STANDARD_RESOLUTION = 0x01, + /*! Support fine FAX Y-resolution 196/200dpi */ + T30_SUPPORT_FINE_RESOLUTION = 0x02, + /*! Support super-fine FAX Y-resolution 392/400dpi */ + T30_SUPPORT_SUPERFINE_RESOLUTION = 0x04, + + /*! Support half FAX X-resolution 100/102dpi */ + T30_SUPPORT_R4_RESOLUTION = 0x10000, + /*! Support standard FAX X-resolution 200/204dpi */ + T30_SUPPORT_R8_RESOLUTION = 0x20000, + /*! Support double FAX X-resolution 400dpi */ + T30_SUPPORT_R16_RESOLUTION = 0x40000, + + /*! Support 300dpi x 300 dpi */ + T30_SUPPORT_300_300_RESOLUTION = 0x100000, + /*! Support 400dpi x 400 dpi */ + T30_SUPPORT_400_400_RESOLUTION = 0x200000, + /*! Support 600dpi x 600 dpi */ + T30_SUPPORT_600_600_RESOLUTION = 0x400000, + /*! Support 1200dpi x 1200 dpi */ + T30_SUPPORT_1200_1200_RESOLUTION = 0x800000, + /*! Support 300dpi x 600 dpi */ + T30_SUPPORT_300_600_RESOLUTION = 0x1000000, + /*! Support 400dpi x 800 dpi */ + T30_SUPPORT_400_800_RESOLUTION = 0x2000000, + /*! Support 600dpi x 1200 dpi */ + T30_SUPPORT_600_1200_RESOLUTION = 0x4000000 +}; + +enum +{ + T30_SUPPORT_215MM_WIDTH = 0x01, + T30_SUPPORT_255MM_WIDTH = 0x02, + T30_SUPPORT_303MM_WIDTH = 0x04, + + T30_SUPPORT_UNLIMITED_LENGTH = 0x10000, + T30_SUPPORT_A4_LENGTH = 0x20000, + T30_SUPPORT_B4_LENGTH = 0x40000, + T30_SUPPORT_US_LETTER_LENGTH = 0x80000, + T30_SUPPORT_US_LEGAL_LENGTH = 0x100000 +}; + +enum +{ + /*! Enable support of identification, through the SID and/or PWD frames. */ + T30_SUPPORT_IDENTIFICATION = 0x01, + /*! Enable support of selective polling, through the SEP frame. */ + T30_SUPPORT_SELECTIVE_POLLING = 0x02, + /*! Enable support of polling sub-addressing, through the PSA frame. */ + T30_SUPPORT_POLLED_SUB_ADDRESSING = 0x04, + /*! Enable support of multiple selective polling, through repeated used of the SEP and PSA frames. */ + T30_SUPPORT_MULTIPLE_SELECTIVE_POLLING = 0x08, + /*! Enable support of sub-addressing, through the SUB frame. */ + T30_SUPPORT_SUB_ADDRESSING = 0x10, + /*! Enable support of transmitting subscriber internet address, through the TSA frame. */ + T30_SUPPORT_TRANSMITTING_SUBSCRIBER_INTERNET_ADDRESS = 0x20, + /*! Enable support of internet routing address, through the IRA frame. */ + T30_SUPPORT_INTERNET_ROUTING_ADDRESS = 0x40, + /*! Enable support of calling subscriber internet address, through the CIA frame. */ + T30_SUPPORT_CALLING_SUBSCRIBER_INTERNET_ADDRESS = 0x80, + /*! Enable support of internet selective polling address, through the ISP frame. */ + T30_SUPPORT_INTERNET_SELECTIVE_POLLING_ADDRESS = 0x100, + /*! Enable support of called subscriber internet address, through the CSA frame. */ + T30_SUPPORT_CALLED_SUBSCRIBER_INTERNET_ADDRESS = 0x200, + /*! Enable support of the field not valid (FNV) frame. */ + T30_SUPPORT_FIELD_NOT_VALID = 0x400, + /*! Enable support of the command repeat (CRP) frame. */ + T30_SUPPORT_COMMAND_REPEAT = 0x800 +}; + +enum +{ + T30_IAF_MODE_T37 = 0x01, + T30_IAF_MODE_T38 = 0x02, + T30_IAF_MODE_FLOW_CONTROL = 0x04, + /*! Continuous flow mode means data is sent as fast as possible, usually across + the Internet, where speed is not constrained by a PSTN modem. */ + T30_IAF_MODE_CONTINUOUS_FLOW = 0x08, + /*! No TCF means TCF is not exchanged. The end points must sort out usable speed + issues locally. */ + T30_IAF_MODE_NO_TCF = 0x10, + /*! No fill bits means do not insert fill bits, even if the T.30 messages request + them. */ + T30_IAF_MODE_NO_FILL_BITS = 0x20, + /*! No indicators means do not send indicator messages when using T.38. */ + T30_IAF_MODE_NO_INDICATORS = 0x40, + /*! Use relaxed timers for T.38. This is appropriate when using TCP/TPKT for T.38, + as there is no point in anything but a long backstop timeout in such a mode. */ + T30_IAF_MODE_RELAXED_TIMERS = 0x80 +}; + +typedef struct +{ + /*! \brief The identifier string (CSI, TSI, CIG). */ + char ident[T30_MAX_IDENT_LEN + 1]; + /*! \brief The sub-address string (SUB). */ + char sub_address[T30_MAX_IDENT_LEN + 1]; + /*! \brief The selective polling sub-address (SEP). */ + char selective_polling_address[T30_MAX_IDENT_LEN + 1]; + /*! \brief The polled sub-address (PSA). */ + char polled_sub_address[T30_MAX_IDENT_LEN + 1]; + /*! \brief The sender identification (SID). */ + char sender_ident[T30_MAX_IDENT_LEN + 1]; + /*! \brief The password (PWD). */ + char password[T30_MAX_IDENT_LEN + 1]; + /*! \brief Non-standard facilities (NSF). */ + uint8_t *nsf; + size_t nsf_len; + /*! \brief Non-standard facilities command (NSC). */ + uint8_t *nsc; + size_t nsc_len; + /*! \brief Non-standard facilities set-up (NSS). */ + uint8_t *nss; + size_t nss_len; + /*! \brief Transmitting subscriber internet address (TSA). */ + int tsa_type; + char *tsa; + size_t tsa_len; + /*! \brief Internet routing address (IRA). */ + int ira_type; + char *ira; + size_t ira_len; + /*! \brief Calling subscriber internet address (CIA). */ + int cia_type; + char *cia; + size_t cia_len; + /*! \brief Internet selective polling address (ISP). */ + int isp_type; + char *isp; + size_t isp_len; + /*! \brief Called subscriber internet address (CSA). */ + int csa_type; + char *csa; + size_t csa_len; +} t30_exchanged_info_t; + +typedef struct +{ + /*! \brief The current bit rate for image transfer. */ + int bit_rate; + /*! \brief TRUE if error correcting mode is used. */ + int error_correcting_mode; + /*! \brief The number of pages sent so far. */ + int pages_tx; + /*! \brief The number of pages received so far. */ + int pages_rx; + /*! \brief The number of pages in the file (<0 if not known). */ + int pages_in_file; + /*! \brief The horizontal column-to-column resolution of the most recent page, in pixels per metre */ + int x_resolution; + /*! \brief The vertical row-to-row resolution of the most recent page, in pixels per metre */ + int y_resolution; + /*! \brief The number of horizontal pixels in the most recent page. */ + int width; + /*! \brief The number of vertical pixels in the most recent page. */ + int length; + /*! \brief The size of the image, in bytes */ + int image_size; + /*! \brief The type of compression used between the FAX machines */ + int encoding; + /*! \brief The number of bad pixel rows in the most recent page. */ + int bad_rows; + /*! \brief The largest number of bad pixel rows in a block in the most recent page. */ + int longest_bad_row_run; + /*! \brief The number of HDLC frame retries, if error correcting mode is used. */ + int error_correcting_mode_retries; + /*! \brief Current status */ + int current_status; +} t30_stats_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a T.30 context. + \brief Initialise a T.30 context. + \param s The T.30 context. + \param calling_party TRUE if the context is for a calling party. FALSE if the + context is for an answering party. + \param set_rx_type_handler + \param set_rx_type_user_data + \param set_tx_type_handler + \param set_tx_type_user_data + \param send_hdlc_handler + \param send_hdlc_user_data + \return A pointer to the context, or NULL if there was a problem. */ +SPAN_DECLARE(t30_state_t *) t30_init(t30_state_t *s, + int calling_party, + t30_set_handler_t *set_rx_type_handler, + void *set_rx_type_user_data, + t30_set_handler_t *set_tx_type_handler, + void *set_tx_type_user_data, + t30_send_hdlc_handler_t *send_hdlc_handler, + void *send_hdlc_user_data); + +/*! Release a T.30 context. + \brief Release a T.30 context. + \param s The T.30 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_release(t30_state_t *s); + +/*! Free a T.30 context. + \brief Free a T.30 context. + \param s The T.30 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_free(t30_state_t *s); + +/*! Restart a T.30 context. + \brief Restart a T.30 context. + \param s The T.30 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_restart(t30_state_t *s); + +/*! Check if a T.30 call is still active. This may be used to regularly poll + if the job has finished. + \brief Check if a T.30 call is still active. + \param s The T.30 context. + \return TRUE for call still active, or FALSE for call completed. */ +SPAN_DECLARE(int) t30_call_active(t30_state_t *s); + +/*! Cleanup a T.30 context if the call terminates. + \brief Cleanup a T.30 context if the call terminates. + \param s The T.30 context. */ +SPAN_DECLARE(void) t30_terminate(t30_state_t *s); + +/*! Inform the T.30 engine of a status change in the front end (end of tx, rx signal change, etc.). + \brief Inform the T.30 engine of a status change in the front end (end of tx, rx signal change, etc.). + \param user_data The T.30 context. + \param status The type of status change which occured. */ +SPAN_DECLARE(void) t30_front_end_status(void *user_data, int status); + +/*! Get a bit of received non-ECM image data. + \brief Get a bit of received non-ECM image data. + \param user_data An opaque pointer, which must point to the T.30 context. + \return The next bit to transmit. */ +SPAN_DECLARE_NONSTD(int) t30_non_ecm_get_bit(void *user_data); + +/*! Get a byte of received non-ECM image data. + \brief Get a byte of received non-ECM image data. + \param user_data An opaque pointer, which must point to the T.30 context. + \return The next byte to transmit. */ +SPAN_DECLARE(int) t30_non_ecm_get_byte(void *user_data); + +/*! Get a chunk of received non-ECM image data. + \brief Get a bit of received non-ECM image data. + \param user_data An opaque pointer, which must point to the T.30 context. + \param buf The buffer to contain the data. + \param max_len The maximum length of the chunk. + \return The actual length of the chunk. */ +SPAN_DECLARE(int) t30_non_ecm_get_chunk(void *user_data, uint8_t buf[], int max_len); + +/*! Process a bit of received non-ECM image data. + \brief Process a bit of received non-ECM image data + \param user_data An opaque pointer, which must point to the T.30 context. + \param bit The received bit. */ +SPAN_DECLARE_NONSTD(void) t30_non_ecm_put_bit(void *user_data, int bit); + +/*! Process a byte of received non-ECM image data. + \brief Process a byte of received non-ECM image data + \param user_data An opaque pointer, which must point to the T.30 context. + \param byte The received byte. */ +SPAN_DECLARE(void) t30_non_ecm_put_byte(void *user_data, int byte); + +/*! Process a chunk of received non-ECM image data. + \brief Process a chunk of received non-ECM image data + \param user_data An opaque pointer, which must point to the T.30 context. + \param buf The buffer containing the received data. + \param len The length of the data in buf. */ +SPAN_DECLARE(void) t30_non_ecm_put_chunk(void *user_data, const uint8_t buf[], int len); + +/*! Process a received HDLC frame. + \brief Process a received HDLC frame. + \param user_data The T.30 context. + \param msg The HDLC message. + \param len The length of the message, in octets. + \param ok TRUE if the frame was received without error. */ +SPAN_DECLARE_NONSTD(void) t30_hdlc_accept(void *user_data, const uint8_t msg[], int len, int ok); + +/*! Report the passage of time to the T.30 engine. + \brief Report the passage of time to the T.30 engine. + \param s The T.30 context. + \param samples The time change in 1/8000th second steps. */ +SPAN_DECLARE(void) t30_timer_update(t30_state_t *s, int samples); + +/*! Get the current transfer statistics for the file being sent or received. + \brief Get the current transfer statistics. + \param s The T.30 context. + \param t A pointer to a buffer for the statistics. */ +SPAN_DECLARE(void) t30_get_transfer_statistics(t30_state_t *s, t30_stats_t *t); + +/*! Request a local interrupt of FAX exchange. + \brief Request a local interrupt of FAX exchange. + \param s The T.30 context. + \param state TRUE to enable interrupt request, else FALSE. */ +SPAN_DECLARE(void) t30_local_interrupt_request(t30_state_t *s, int state); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t30_api.h b/Libraries/spandsp/spandsp/spandsp/t30_api.h new file mode 100644 index 000000000..9151e9b55 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t30_api.h @@ -0,0 +1,547 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t30_api.h - definitions for T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30_api.h,v 1.10 2009/04/12 09:12:10 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T30_API_H_) +#define _SPANDSP_T30_API_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Set the transmitted NSF frame to be associated with a T.30 context. + \brief Set the transmitted NSF frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsf A pointer to the frame. + \param len The length of the frame. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_nsf(t30_state_t *s, const uint8_t *nsf, int len); + +/*! Get an NSF frame to be associated with a T.30 context. + \brief Set an NSF frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsf A pointer to the frame. + \return the length of the NSF message. */ +SPAN_DECLARE(size_t) t30_get_tx_nsf(t30_state_t *s, const uint8_t *nsf[]); + +/*! Get an NSF frame to be associated with a T.30 context. + \brief Set an NSF frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsf A pointer to the frame. + \return the length of the NSF message. */ +SPAN_DECLARE(size_t) t30_get_rx_nsf(t30_state_t *s, const uint8_t *nsf[]); + +/*! Set the transmitted NSC frame to be associated with a T.30 context. + \brief Set the transmitted NSC frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsc A pointer to the frame. + \param len The length of the frame. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_nsc(t30_state_t *s, const uint8_t *nsc, int len); + +/*! Get an NSC frame to be associated with a T.30 context. + \brief Set an NSC frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsc A pointer to the frame. + \return the length of the NSC message. */ +SPAN_DECLARE(size_t) t30_get_tx_nsc(t30_state_t *s, const uint8_t *nsc[]); + +/*! Get an NSC frame to be associated with a T.30 context. + \brief Set an NSC frame to be associated with a T.30 context. + \param s The T.30 context. + \param nsc A pointer to the frame. + \return the length of the NSC message. */ +SPAN_DECLARE(size_t) t30_get_rx_nsc(t30_state_t *s, const uint8_t *nsc[]); + +/*! Set the transmitted NSS frame to be associated with a T.30 context. + \brief Set the transmitted NSS frame to be associated with a T.30 context. + \param s The T.30 context. + \param nss A pointer to the frame. + \param len The length of the frame. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_nss(t30_state_t *s, const uint8_t *nss, int len); + +/*! Get an NSS frame to be associated with a T.30 context. + \brief Set an NSS frame to be associated with a T.30 context. + \param s The T.30 context. + \param nss A pointer to the frame. + \return the length of the NSS message. */ +SPAN_DECLARE(size_t) t30_get_tx_nss(t30_state_t *s, const uint8_t *nss[]); + +/*! Get an NSS frame to be associated with a T.30 context. + \brief Set an NSS frame to be associated with a T.30 context. + \param s The T.30 context. + \param nss A pointer to the frame. + \return the length of the NSS message. */ +SPAN_DECLARE(size_t) t30_get_rx_nss(t30_state_t *s, const uint8_t *nss[]); + +/*! Set the transmitted identifier associated with a T.30 context. + \brief Set the transmitted identifier associated with a T.30 context. + \param s The T.30 context. + \param id A pointer to the identifier. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_ident(t30_state_t *s, const char *id); + +/*! Get the transmitted identifier associated with a T.30 context. + \brief Set the transmitted identifier associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the identifier. */ +SPAN_DECLARE(const char *) t30_get_tx_ident(t30_state_t *s); + +/*! Get the transmitted identifier associated with a T.30 context. + \brief Set the transmitted identifier associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the identifier. */ +SPAN_DECLARE(const char *) t30_get_rx_ident(t30_state_t *s); + +/*! Set the transmitted sub-address associated with a T.30 context. + \brief Set the transmitted sub-address associated with a T.30 context. + \param s The T.30 context. + \param sub_address A pointer to the sub-address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_sub_address(t30_state_t *s, const char *sub_address); + +/*! Get the received sub-address associated with a T.30 context. + \brief Get the received sub-address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the sub-address. */ +SPAN_DECLARE(const char *) t30_get_tx_sub_address(t30_state_t *s); + +/*! Get the received sub-address associated with a T.30 context. + \brief Get the received sub-address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the sub-address. */ +SPAN_DECLARE(const char *) t30_get_rx_sub_address(t30_state_t *s); + +/*! Set the transmitted selective polling address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted selective polling address associated with a T.30 context. + \param s The T.30 context. + \param selective_polling_address A pointer to the selective polling address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_selective_polling_address(t30_state_t *s, const char *selective_polling_address); + +/*! Get the received selective polling address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received selective polling address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the selective polling address. */ +SPAN_DECLARE(const char *) t30_get_tx_selective_polling_address(t30_state_t *s); + +/*! Get the received selective polling address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received selective polling address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the selective polling address. */ +SPAN_DECLARE(const char *) t30_get_rx_selective_polling_address(t30_state_t *s); + +/*! Set the transmitted polled sub-address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted polled sub-address associated with a T.30 context. + \param s The T.30 context. + \param polled_sub_address A pointer to the polled sub-address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_polled_sub_address(t30_state_t *s, const char *polled_sub_address); + +/*! Get the received polled sub-address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received polled sub-address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the polled sub-address. */ +SPAN_DECLARE(const char *) t30_get_tx_polled_sub_address(t30_state_t *s); + +/*! Get the received polled sub-address (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received polled sub-address associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the polled sub-address. */ +SPAN_DECLARE(const char *) t30_get_rx_polled_sub_address(t30_state_t *s); + +/*! Set the transmitted sender ident (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted sender ident associated with a T.30 context. + \param s The T.30 context. + \param sender_ident A pointer to the sender ident. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_sender_ident(t30_state_t *s, const char *sender_ident); + +/*! Get the received sender ident (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received sender ident associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the sender ident. */ +SPAN_DECLARE(const char *) t30_get_tx_sender_ident(t30_state_t *s); + +/*! Get the received sender ident (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received sender ident associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the sender ident. */ +SPAN_DECLARE(const char *) t30_get_rx_sender_ident(t30_state_t *s); + +/*! Set the transmitted password (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted password associated with a T.30 context. + \param s The T.30 context. + \param password A pointer to the password. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_password(t30_state_t *s, const char *password); + +/*! Get the received password (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received password associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the password. */ +SPAN_DECLARE(const char *) t30_get_tx_password(t30_state_t *s); + +/*! Get the received password (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received password associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the password. */ +SPAN_DECLARE(const char *) t30_get_rx_password(t30_state_t *s); + +/*! Set the transmitted ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \param len The length of the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_tsa(t30_state_t *s, int type, const char *address, int len); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_tx_tsa(t30_state_t *s, int *type, const char *address[]); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_rx_tsa(t30_state_t *s, int *type, const char *address[]); + +/*! Set the transmitted ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \param len The length of the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_ira(t30_state_t *s, int type, const char *address, int len); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_tx_ira(t30_state_t *s, int *type, const char *address[]); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_rx_ira(t30_state_t *s, int *type, const char *address[]); + +/*! Set the transmitted ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \param len The length of the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_cia(t30_state_t *s, int type, const char *address, int len); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_tx_cia(t30_state_t *s, int *type, const char *address[]); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(size_t) t30_get_rx_cia(t30_state_t *s, int *type, const char *address[]); + +/*! Set the transmitted ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \param len The length of the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_isp(t30_state_t *s, int type, const char *address, int len); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(size_t) t30_get_tx_isp(t30_state_t *s, int *type, const char *address[]); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(size_t) t30_get_rx_isp(t30_state_t *s, int *type, const char *address[]); + +/*! Set the transmitted ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Set the transmitted ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \param len The length of the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_csa(t30_state_t *s, int type, const char *address, int len); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return The length of the address. */ +SPAN_DECLARE(size_t) t30_get_tx_csa(t30_state_t *s, int *type, const char *address[]); + +/*! Get the received ??? (i.e. the one we will send to the far + end) associated with a T.30 context. + \brief Get the received ??? associated with a T.30 context. + \param s The T.30 context. + \param type The type of address. + \param address A pointer to the address. + \return 0 for OK, else -1. */ +SPAN_DECLARE(size_t) t30_get_rx_csa(t30_state_t *s, int *type, const char *address[]); + +/*! Set the transmitted header information associated with a T.30 context. + \brief Set the transmitted header information associated with a T.30 context. + \param s The T.30 context. + \param info A pointer to the information string. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t30_set_tx_page_header_info(t30_state_t *s, const char *info); + +/*! Get the header information associated with a T.30 context. + \brief Get the header information associated with a T.30 context. + \param s The T.30 context. + \param info A pointer to a buffer for the header information. The buffer + should be at least 51 bytes long. + \return the length of the string. */ +SPAN_DECLARE(size_t) t30_get_tx_page_header_info(t30_state_t *s, char *info); + +/*! Get the country of origin of the remote FAX machine associated with a T.30 context. + \brief Get the country of origin of the remote FAX machine associated with a T.30 context. + \param s The T.30 context. + \return a pointer to the country name, or NULL if the country is not known. */ +SPAN_DECLARE(const char *) t30_get_rx_country(t30_state_t *s); + +/*! Get the name of the vendor of the remote FAX machine associated with a T.30 context. + \brief Get the name of the vendor of the remote FAX machine associated with a T.30 context. + \param s The T.30 context. + \return a pointer to the vendor name, or NULL if the vendor is not known. */ +SPAN_DECLARE(const char *) t30_get_rx_vendor(t30_state_t *s); + +/*! Get the name of the model of the remote FAX machine associated with a T.30 context. + \brief Get the name of the model of the remote FAX machine associated with a T.30 context. + \param s The T.30 context. + \return a pointer to the model name, or NULL if the model is not known. */ +SPAN_DECLARE(const char *) t30_get_rx_model(t30_state_t *s); + +/*! Specify the file name of the next TIFF file to be received by a T.30 + context. + \brief Set next receive file name. + \param s The T.30 context. + \param file The file name + \param stop_page The maximum page to receive. -1 for no restriction. */ +SPAN_DECLARE(void) t30_set_rx_file(t30_state_t *s, const char *file, int stop_page); + +/*! Specify the file name of the next TIFF file to be transmitted by a T.30 + context. + \brief Set next transmit file name. + \param s The T.30 context. + \param file The file name + \param start_page The first page to send. -1 for no restriction. + \param stop_page The last page to send. -1 for no restriction. */ +SPAN_DECLARE(void) t30_set_tx_file(t30_state_t *s, const char *file, int start_page, int stop_page); + +/*! Set Internet aware FAX (IAF) mode. + \brief Set Internet aware FAX (IAF) mode. + \param s The T.30 context. + \param iaf TRUE for IAF, or FALSE for non-IAF. */ +SPAN_DECLARE(void) t30_set_iaf_mode(t30_state_t *s, int iaf); + +/*! Specify if error correction mode (ECM) is allowed by a T.30 context. + \brief Select ECM capability. + \param s The T.30 context. + \param enabled TRUE for ECM capable, FALSE for not ECM capable. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_ecm_capability(t30_state_t *s, int enabled); + +/*! Specify the output encoding for TIFF files created during FAX reception. + \brief Specify the output encoding for TIFF files created during FAX reception. + \param s The T.30 context. + \param encoding The coding required. The options are T4_COMPRESSION_ITU_T4_1D, + T4_COMPRESSION_ITU_T4_2D, T4_COMPRESSION_ITU_T6. T6 is usually the + densest option, but support for it is broken in a number of software + packages. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_rx_encoding(t30_state_t *s, int encoding); + +/*! Specify the minimum scan line time supported by a T.30 context. + \brief Specify minimum scan line time. + \param s The T.30 context. + \param min_time The minimum permitted scan line time, in milliseconds. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_minimum_scan_line_time(t30_state_t *s, int min_time); + +/*! Specify which modem types are supported by a T.30 context. + \brief Specify supported modems. + \param s The T.30 context. + \param supported_modems Bit field list of the supported modems. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_supported_modems(t30_state_t *s, int supported_modems); + +/*! Specify which compression types are supported by a T.30 context. + \brief Specify supported compression types. + \param s The T.30 context. + \param supported_compressions Bit field list of the supported compression types. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_supported_compressions(t30_state_t *s, int supported_compressions); + +/*! Specify which resolutions are supported by a T.30 context. + \brief Specify supported resolutions. + \param s The T.30 context. + \param supported_resolutions Bit field list of the supported resolutions. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_supported_resolutions(t30_state_t *s, int supported_resolutions); + +/*! Specify which images sizes are supported by a T.30 context. + \brief Specify supported image sizes. + \param s The T.30 context. + \param supported_image_sizes Bit field list of the supported widths and lengths. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_supported_image_sizes(t30_state_t *s, int supported_image_sizes); + +/*! Specify which special T.30 features are supported by a T.30 context. + \brief Specify supported T.30 features. + \param s The T.30 context. + \param supported_t30_features Bit field list of the supported features. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_supported_t30_features(t30_state_t *s, int supported_t30_features); + +/*! Set T.30 status. This may be used to adjust the status from within + the phase B and phase D callbacks. + \brief Set T.30 status. + \param s The T.30 context. + \param status The new status. */ +SPAN_DECLARE(void) t30_set_status(t30_state_t *s, int status); + +/*! Specify a period of responding with receiver not ready. + \brief Specify a period of responding with receiver not ready. + \param s The T.30 context. + \param count The number of times to report receiver not ready. + \return 0 if OK, else -1. */ +SPAN_DECLARE(int) t30_set_receiver_not_ready(t30_state_t *s, int count); + +/*! Set a callback function for T.30 phase B handling. + \brief Set a callback function for T.30 phase B handling. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t30_set_phase_b_handler(t30_state_t *s, t30_phase_b_handler_t *handler, void *user_data); + +/*! Set a callback function for T.30 phase D handling. + \brief Set a callback function for T.30 phase D handling. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t30_set_phase_d_handler(t30_state_t *s, t30_phase_d_handler_t *handler, void *user_data); + +/*! Set a callback function for T.30 phase E handling. + \brief Set a callback function for T.30 phase E handling. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t30_set_phase_e_handler(t30_state_t *s, t30_phase_e_handler_t *handler, void *user_data); + +/*! Set a callback function for T.30 end of document handling. + \brief Set a callback function for T.30 end of document handling. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t30_set_document_handler(t30_state_t *s, t30_document_handler_t *handler, void *user_data); + +/*! Set a callback function for T.30 frame exchange monitoring. This is called from the heart + of the signal processing, so don't take too long in the handler routine. + \brief Set a callback function for T.30 frame exchange monitoring. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t30_set_real_time_frame_handler(t30_state_t *s, t30_real_time_frame_handler_t *handler, void *user_data); + +/*! Get a pointer to the logging context associated with a T.30 context. + \brief Get a pointer to the logging context associated with a T.30 context. + \param s The T.30 context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) t30_get_logging_state(t30_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t30_fcf.h b/Libraries/spandsp/spandsp/spandsp/t30_fcf.h new file mode 100644 index 000000000..a303fb4f9 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t30_fcf.h @@ -0,0 +1,123 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t30_fcf.h - ITU T.30 fax control field definitions + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30_fcf.h,v 1.18 2009/10/08 15:14:31 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T30_FCF_H_) +#define _SPANDSP_T30_FCF_H_ + +enum +{ + /*! Initial identification messages */ + /*! From the called to the calling terminal. */ + T30_DIS = 0x80, /*! [0000 0001] Digital identification signal */ + T30_CSI = 0x40, /*! [0000 0010] Called subscriber identification */ + T30_NSF = 0x20, /*! [0000 0100] Non-standard facilities */ + + /*! Commands to send */ + /*! From a calling terminal wishing to be a receiver, to a called terminal + which is capable of transmitting. */ + T30_DTC = 0x81, /*! [1000 0001] Digital transmit command */ + T30_CIG = 0x41, /*! [1000 0010] Calling subscriber identification */ + T30_NSC = 0x21, /*! [1000 0100] Non-standard facilities command */ + T30_PWD = 0xC1, /*! [1000 0011] Password */ + T30_SEP = 0xA1, /*! [1000 0101] Selective polling */ + T30_PSA = 0x61, /*! [1000 0110] Polled subaddress */ + T30_CIA = 0xE1, /*! [1000 0111] Calling subscriber internet address */ + T30_ISP = 0x11, /*! [1000 1000] Internet selective polling address */ + + /*! Commands to receive */ + /*! From a calling terminal wishing to be a transmitter, to a called terminal + which is capable of receiving. */ + T30_DCS = 0x82, /*! [X100 0001] Digital command signal */ + T30_TSI = 0x42, /*! [X100 0010] Transmitting subscriber information */ + T30_NSS = 0x22, /*! [X100 0100] Non-standard facilities set-up */ + T30_SUB = 0xC2, /*! [X100 0011] Sub-address */ + T30_SID = 0xA2, /*! [X100 0101] Sender identification */ + /*! T30_TCF - Training check is a burst of 1.5s of zeros sent using the image modem */ + T30_CTC = 0x12, /*! [X100 1000] Continue to correct */ + T30_TSA = 0x62, /*! [X100 0110] Transmitting subscriber internet address */ + T30_IRA = 0xE2, /*! [X100 0111] Internet routing address */ + + /*! Pre-message response signals */ + /*! From the receiver to the transmitter. */ + T30_CFR = 0x84, /*! [X010 0001] Confirmation to receive */ + T30_FTT = 0x44, /*! [X010 0010] Failure to train */ + T30_CTR = 0xC4, /*! [X010 0011] Response for continue to correct */ + T30_CSA = 0x24, /*! [X010 0100] Called subscriber internet address */ + + /*! Post-message commands */ + T30_EOM = 0x8E, /*! [X111 0001] End of message */ + T30_MPS = 0x4E, /*! [X111 0010] Multipage signal */ + T30_EOP = 0x2E, /*! [X111 0100] End of procedure */ + T30_PRI_EOM = 0x9E, /*! [X111 1001] Procedure interrupt - end of procedure */ + T30_PRI_MPS = 0x5E, /*! [X111 1010] Procedure interrupt - multipage signal */ + T30_PRI_EOP = 0x3E, /*! [X111 1100] Procedure interrupt - end of procedure */ + T30_EOS = 0x1E, /*! [X111 1000] End of selection */ + T30_PPS = 0xBE, /*! [X111 1101] Partial page signal */ + T30_EOR = 0xCE, /*! [X111 0011] End of retransmission */ + T30_RR = 0x6E, /*! [X111 0110] Receiver ready */ + + /*! Post-message responses */ + T30_MCF = 0x8C, /*! [X011 0001] Message confirmation */ + T30_RTP = 0xCC, /*! [X011 0011] Retrain positive */ + T30_RTN = 0x4C, /*! [X011 0010] Retrain negative */ + T30_PIP = 0xAC, /*! [X011 0101] Procedure interrupt positive */ + T30_PIN = 0x2C, /*! [X011 0100] Procedure interrupt negative */ + T30_PPR = 0xBC, /*! [X011 1101] Partial page request */ + T30_RNR = 0xEC, /*! [X011 0111] Receive not ready */ + T30_ERR = 0x1C, /*! [X011 1000] Response for end of retransmission */ + T30_FDM = 0xFC, /*! [X011 1111] File diagnostics message */ + + /*! Other line control signals */ + T30_DCN = 0xFA, /*! [X101 1111] Disconnect */ + T30_CRP = 0x1A, /*! [X101 1000] Command repeat */ + T30_FNV = 0xCA, /*! [X101 0011] Field not valid */ + T30_TNR = 0xEA, /*! [X101 0111] Transmitter not ready */ + T30_TR = 0x6A, /*! [X101 0110] Transmitter ready */ + T30_TK = 0x4B, /*! [1101 0010] Transmitter keys */ + T30_RK = 0x4A, /*! [0101 0010] Receiver keys */ + T30_PSS = 0x1F, /*! [1111 1000] Present signature signal (used only as FCF2) */ + T30_DES = 0xA0, /*! [0000 0101] Digital extended signal */ + T30_DEC = 0x93, /*! [1100 1001] Digital extended command */ + T30_DER = 0x53, /*! [1100 1010] Digital extended request */ + T30_DTR = 0x11, /*! [1000 1000] Digital turnaround request (conflicts with ISP) */ + T30_DNK = 0x9A, /*! [X101 1001] Digital not acknowledge */ + T30_PID = 0x6C, /*! [X011 0110] Procedure interrupt disconnect */ + T30_SPI = 0x10, /*! [0000 1000] Security page indicator */ + T30_SPT = 0x80, /*! [0000 0001] Security page type */ + + /*! Something only use as a secondary value in error correcting mode */ + T30_NULL = 0x00, /*! [0000 0000] Nothing to say */ + + /*! Information frame types used for error correction mode, in T.4 */ + T4_FCD = 0x06, /*! [0110 0000] Facsimile coded data */ + T4_RCP = 0x86 /*! [0110 0001] Return to control for partial page */ +}; + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t30_logging.h b/Libraries/spandsp/spandsp/spandsp/t30_logging.h new file mode 100644 index 000000000..cd68fdca9 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t30_logging.h @@ -0,0 +1,63 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t30_logging.h - definitions for T.30 fax processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t30_logging.h,v 1.4 2009/02/03 16:28:41 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T30_LOGGING_H_) +#define _SPANDSP_T30_LOGGING_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Return a text name for a T.30 frame type. + \brief Return a text name for a T.30 frame type. + \param x The frametype octet. + \return A pointer to the text name for the frame type. If the frame type is + not value, the string "???" is returned. */ +SPAN_DECLARE(const char *) t30_frametype(uint8_t x); + +/*! Decode a DIS, DTC or DCS frame, and log the contents. + \brief Decode a DIS, DTC or DCS frame, and log the contents. + \param s The T.30 context. + \param dis A pointer to the frame to be decoded. + \param len The length of the frame. */ +SPAN_DECLARE(void) t30_decode_dis_dtc_dcs(t30_state_t *s, const uint8_t *dis, int len); + +/*! Convert a phase E completion code to a short text description. + \brief Convert a phase E completion code to a short text description. + \param result The result code. + \return A pointer to the description. */ +SPAN_DECLARE(const char *) t30_completion_code_to_str(int result); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t31.h b/Libraries/spandsp/spandsp/spandsp/t31.h new file mode 100644 index 000000000..ffb47a98d --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t31.h @@ -0,0 +1,162 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t31.h - A T.31 compatible class 1 FAX modem interface. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t31.h,v 1.59 2009/03/13 12:59:26 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T31_H_) +#define _SPANDSP_T31_H_ + +/*! \page t31_page T.31 Class 1 FAX modem protocol handling +\section t31_page_sec_1 What does it do? +The T.31 class 1 FAX modem modules implements a class 1 interface to the FAX +modems in spandsp. + +\section t31_page_sec_2 How does it work? +*/ + +/*! + T.31 descriptor. This defines the working state for a single instance of + a T.31 FAX modem. +*/ +typedef struct t31_state_s t31_state_t; + +typedef int (t31_modem_control_handler_t)(t31_state_t *s, void *user_data, int op, const char *num); + +#define T31_TX_BUF_LEN (4096) +#define T31_TX_BUF_HIGH_TIDE (4096 - 1024) +#define T31_TX_BUF_LOW_TIDE (1024) +#define T31_MAX_HDLC_LEN 284 +#define T31_T38_MAX_HDLC_LEN 260 + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(void) t31_call_event(t31_state_t *s, int event); + +SPAN_DECLARE(int) t31_at_rx(t31_state_t *s, const char *t, int len); + +/*! Process a block of received T.31 modem audio samples. + \brief Process a block of received T.31 modem audio samples. + \param s The T.31 modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) t31_rx(t31_state_t *s, int16_t amp[], int len); + +/*! Fake processing of a missing block of received T.31 modem audio samples + (e.g due to packet loss). + \brief Fake processing of a missing block of received T.31 modem audio samples. + \param s The T.31 modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) t31_rx_fillin(t31_state_t *s, int len); + +/*! Generate a block of T.31 modem audio samples. + \brief Generate a block of T.31 modem audio samples. + \param s The T.31 modem context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE(int) t31_tx(t31_state_t *s, int16_t amp[], int max_len); + +SPAN_DECLARE(int) t31_t38_send_timeout(t31_state_t *s, int samples); + +/*! Select whether silent audio will be sent when transmit is idle. + \brief Select whether silent audio will be sent when transmit is idle. + \param s The T.31 modem context. + \param transmit_on_idle TRUE if silent audio should be output when the transmitter is + idle. FALSE to transmit zero length audio when the transmitter is idle. The default + behaviour is FALSE. +*/ +SPAN_DECLARE(void) t31_set_transmit_on_idle(t31_state_t *s, int transmit_on_idle); + +/*! Select whether TEP mode will be used (or time allowed for it (when transmitting). + \brief Select whether TEP mode will be used. + \param s The T.31 modem context. + \param use_tep TRUE if TEP is to be ised. +*/ +SPAN_DECLARE(void) t31_set_tep_mode(t31_state_t *s, int use_tep); + +/*! Select whether T.38 data will be paced as it is transmitted. + \brief Select whether T.38 data will be paced. + \param s The T.31 modem context. + \param without_pacing TRUE if data is to be sent as fast as possible. FALSE if it is + to be paced. +*/ +SPAN_DECLARE(void) t31_set_t38_config(t31_state_t *s, int without_pacing); + +SPAN_DECLARE(void) t31_set_mode(t31_state_t *s, int t38_mode); + +/*! Get a pointer to the logging context associated with a T.31 context. + \brief Get a pointer to the logging context associated with a T.31 context. + \param s The T.31 context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) t31_get_logging_state(t31_state_t *s); + +SPAN_DECLARE(t38_core_state_t *) t31_get_t38_core_state(t31_state_t *s); + +/*! Initialise a T.31 context. This must be called before the first + use of the context, to initialise its contents. + \brief Initialise a T.31 context. + \param s The T.31 context. + \param at_tx_handler A callback routine to handle AT interpreter channel output. + \param at_tx_user_data An opaque pointer passed in called to at_tx_handler. + \param modem_control_handler A callback routine to handle control of the modem (off-hook, etc). + \param modem_control_user_data An opaque pointer passed in called to modem_control_handler. + \param tx_t38_packet_handler ??? + \param tx_t38_packet_user_data ??? + \return A pointer to the T.31 context. */ +SPAN_DECLARE(t31_state_t *) t31_init(t31_state_t *s, + at_tx_handler_t *at_tx_handler, + void *at_tx_user_data, + t31_modem_control_handler_t *modem_control_handler, + void *modem_control_user_data, + t38_tx_packet_handler_t *tx_t38_packet_handler, + void *tx_t38_packet_user_data); + +/*! Release a T.31 context. + \brief Release a T.31 context. + \param s The T.31 context. + \return 0 for OK */ +SPAN_DECLARE(int) t31_release(t31_state_t *s); + +/*! Free a T.31 context. + \brief Release a T.31 context. + \param s The T.31 context. + \return 0 for OK */ +SPAN_DECLARE(int) t31_free(t31_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t35.h b/Libraries/spandsp/spandsp/spandsp/t35.h new file mode 100644 index 000000000..bdfbe0bc6 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t35.h @@ -0,0 +1,73 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t35.h - ITU T.35 FAX non-standard facility processing. + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t35.h,v 1.15 2009/01/31 08:48:11 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T35_H_) +#define _SPANDSP_T35_H_ + +/*! \page t35_page T.35 manufacturer specific processing for FAX machines +\section t35_page_sec_1 What does it do? +???. + +\section t35_page_sec_2 How does it work? +???. +*/ + +/*! A table of the country names associated with each possible value of the T.35 country code + selector octet. */ +extern const char *t35_country_codes[256]; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Decode an NSF field to try to determine the make and model of the + remote machine. + \brief Decode an NSF field. + \param msg The NSF message. + \param len The length of the NSF message. + \param country A pointer which will be pointed to the identified country of origin. + If a NULL pointer is given, the country of origin will not be returned. + If the country of origin is not identified, NULL will be returned. + \param vendor A pointer which will be pointed to the identified vendor. + If a NULL pointer is given, the vendor ID will not be returned. + If the vendor is not identified, NULL will be returned. + \param model A pointer which will be pointed to the identified model. + If a NULL pointer is given, the model will not be returned. + If the model is not identified, NULL will be returned. + \return TRUE if the machine was identified, otherwise FALSE. +*/ +SPAN_DECLARE(int) t35_decode(const uint8_t *msg, int len, const char **country, const char **vendor, const char **model); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t38_core.h b/Libraries/spandsp/spandsp/spandsp/t38_core.h new file mode 100644 index 000000000..9170ecd4d --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t38_core.h @@ -0,0 +1,403 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t38_core.h - An implementation of T.38, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_core.h,v 1.39 2009/07/14 13:54:22 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T38_CORE_H_) +#define _SPANDSP_T38_CORE_H_ + +/*! \page t38_core_page T.38 real time FAX over IP message handling +There are two ITU recommendations which address sending FAXes over IP networks. T.37 specifies a +method of encapsulating FAX images in e-mails, and transporting them to the recipient (an e-mail +box, or another FAX machine) in a store-and-forward manner. T.38 defines a protocol for +transmitting a FAX across an IP network in real time. The core T.38 modules implements the basic +message handling for the T.38, real time, FAX over IP (FoIP) protocol. + +The T.38 protocol can operate between: + - Internet-aware FAX terminals, which connect directly to an IP network. The T.38 terminal module + extends this module to provide a complete T.38 terminal. + - FAX gateways, which allow traditional PSTN FAX terminals to communicate through the Internet. + The T.38 gateway module extends this module to provide a T.38 gateway. + - A combination of terminals and gateways. + +T.38 is the only standardised protocol which exists for real-time FoIP. Reliably transporting a +FAX between PSTN FAX terminals, through an IP network, requires use of the T.38 protocol at FAX +gateways. VoIP connections are not robust for modem use, including FAX modem use. Most use low +bit rate codecs, which cannot convey the modem signals accurately. Even when high bit rate +codecs are used, VoIP connections suffer dropouts and timing adjustments, which modems cannot +tolerate. In a LAN environment the dropout rate may be very low, but the timing adjustments which +occur in VoIP connections still make modem operation unreliable. T.38 FAX gateways deal with the +delays, timing jitter, and packet loss experienced in packet networks, and isolate the PSTN FAX +terminals from these as far as possible. In addition, by sending FAXes as image data, rather than +digitised audio, they reduce the required bandwidth of the IP network. + +\section t38_core_page_sec_1 What does it do? + +\section t38_core_page_sec_2 How does it work? + +Timing differences and jitter between two T.38 entities can be a serious problem, if one of those +entities is a PSTN gateway. + +Flow control for non-ECM image data takes advantage of several features of the T.30 specification. +First, an unspecified number of 0xFF octets may be sent at the start of transmission. This means we +can add endless extra 0xFF bytes at this point, without breaking the T.30 spec. In practice, we +cannot add too many, or we will affect the timing tolerance of the T.30 protocol by delaying the +response at the end of each image. Secondly, just before an end of line (EOL) marker we can pad +with zero bits. Again, the number is limited only by need to avoid upsetting the timing of the +step following the non-ECM data. +*/ + +/*! T.38 indicator types */ +enum t30_indicator_types_e +{ + T38_IND_NO_SIGNAL = 0, + T38_IND_CNG, + T38_IND_CED, + T38_IND_V21_PREAMBLE, + T38_IND_V27TER_2400_TRAINING, + T38_IND_V27TER_4800_TRAINING, + T38_IND_V29_7200_TRAINING, + T38_IND_V29_9600_TRAINING, + T38_IND_V17_7200_SHORT_TRAINING, + T38_IND_V17_7200_LONG_TRAINING, + T38_IND_V17_9600_SHORT_TRAINING, + T38_IND_V17_9600_LONG_TRAINING, + T38_IND_V17_12000_SHORT_TRAINING, + T38_IND_V17_12000_LONG_TRAINING, + T38_IND_V17_14400_SHORT_TRAINING, + T38_IND_V17_14400_LONG_TRAINING, + T38_IND_V8_ANSAM, + T38_IND_V8_SIGNAL, + T38_IND_V34_CNTL_CHANNEL_1200, + T38_IND_V34_PRI_CHANNEL, + T38_IND_V34_CC_RETRAIN, + T38_IND_V33_12000_TRAINING, + T38_IND_V33_14400_TRAINING +}; + +/*! T.38 data types */ +enum t38_data_types_e +{ + T38_DATA_NONE = -1, + T38_DATA_V21 = 0, + T38_DATA_V27TER_2400, + T38_DATA_V27TER_4800, + T38_DATA_V29_7200, + T38_DATA_V29_9600, + T38_DATA_V17_7200, + T38_DATA_V17_9600, + T38_DATA_V17_12000, + T38_DATA_V17_14400, + T38_DATA_V8, + T38_DATA_V34_PRI_RATE, + T38_DATA_V34_CC_1200, + T38_DATA_V34_PRI_CH, + T38_DATA_V33_12000, + T38_DATA_V33_14400 +}; + +/*! T.38 data field types */ +enum t38_field_types_e +{ + T38_FIELD_HDLC_DATA = 0, + T38_FIELD_HDLC_SIG_END, + T38_FIELD_HDLC_FCS_OK, + T38_FIELD_HDLC_FCS_BAD, + T38_FIELD_HDLC_FCS_OK_SIG_END, + T38_FIELD_HDLC_FCS_BAD_SIG_END, + T38_FIELD_T4_NON_ECM_DATA, + T38_FIELD_T4_NON_ECM_SIG_END, + T38_FIELD_CM_MESSAGE, + T38_FIELD_JM_MESSAGE, + T38_FIELD_CI_MESSAGE, + T38_FIELD_V34RATE +}; + +/*! T.38 field classes */ +enum t38_field_classes_e +{ + T38_FIELD_CLASS_NONE = 0, + T38_FIELD_CLASS_HDLC, + T38_FIELD_CLASS_NON_ECM +}; + +/*! T.38 message types */ +enum t38_message_types_e +{ + T38_TYPE_OF_MSG_T30_INDICATOR = 0, + T38_TYPE_OF_MSG_T30_DATA +}; + +/*! T.38 transport types */ +enum t38_transport_types_e +{ + T38_TRANSPORT_UDPTL = 0, + T38_TRANSPORT_RTP, + T38_TRANSPORT_TCP +}; + +/*! T.38 TCF management types */ +enum t38_data_rate_management_types_e +{ + T38_DATA_RATE_MANAGEMENT_LOCAL_TCF = 1, + T38_DATA_RATE_MANAGEMENT_TRANSFERRED_TCF = 2 +}; + +/*! T.38 Packet categories used for setting the redundancy level and packet repeat + counts on a packet by packet basis. */ +enum t38_packet_categories_e +{ + /*! \brief Indicator packet */ + T38_PACKET_CATEGORY_INDICATOR = 0, + /*! \brief Control data packet */ + T38_PACKET_CATEGORY_CONTROL_DATA = 1, + /*! \brief Terminating control data packet */ + T38_PACKET_CATEGORY_CONTROL_DATA_END = 2, + /*! \brief Image data packet */ + T38_PACKET_CATEGORY_IMAGE_DATA = 3, + /*! \brief Terminating image data packet */ + T38_PACKET_CATEGORY_IMAGE_DATA_END = 4 +}; + +#define T38_RX_BUF_LEN 2048 +#define T38_TX_BUF_LEN 16384 + +/*! T.38 data field */ +typedef struct +{ + /*! Field type */ + int field_type; + /*! Field contents */ + const uint8_t *field; + /*! Field length */ + int field_len; +} t38_data_field_t; + +/*! + Core T.38 state, common to all modes of T.38. +*/ +typedef struct t38_core_state_s t38_core_state_t; + +typedef int (t38_tx_packet_handler_t)(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count); + +typedef int (t38_rx_indicator_handler_t)(t38_core_state_t *s, void *user_data, int indicator); +typedef int (t38_rx_data_handler_t)(t38_core_state_t *s, void *user_data, int data_type, int field_type, const uint8_t *buf, int len); +typedef int (t38_rx_missing_handler_t)(t38_core_state_t *s, void *user_data, int rx_seq_no, int expected_seq_no); + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Convert the code for an indicator to a short text name. + \param indicator The type of indicator. + \return A pointer to a short text name for the indicator. */ +SPAN_DECLARE(const char *) t38_indicator_to_str(int indicator); + +/*! \brief Convert the code for a type of data to a short text name. + \param data_type The data type. + \return A pointer to a short text name for the data type. */ +SPAN_DECLARE(const char *) t38_data_type_to_str(int data_type); + +/*! \brief Convert the code for a type of data field to a short text name. + \param field_type The field type. + \return A pointer to a short text name for the field type. */ +SPAN_DECLARE(const char *) t38_field_type_to_str(int field_type); + +/*! \brief Convert the code for a CM profile code to text description. + \param profile The profile code from a CM message. + \return A pointer to a short text description of the profile. */ +SPAN_DECLARE(const char *) t38_cm_profile_to_str(int profile); + +/*! \brief Convert a JM message code to text description. + \param data The data field of the message. + \param len The length of the data field. + \return A pointer to a short text description of the profile. */ +SPAN_DECLARE(const char *) t38_jm_to_str(const uint8_t *data, int len); + +/*! \brief Convert a V34rate message to an actual bit rate. + \param data The data field of the message. + \param len The length of the data field. + \return The bit rate, or -1 for a bad message. */ +SPAN_DECLARE(int) t38_v34rate_to_bps(const uint8_t *data, int len); + +/*! \brief Send an indicator packet + \param s The T.38 context. + \param indicator The indicator to send. + \return The delay to allow after this indicator is sent. */ +SPAN_DECLARE(int) t38_core_send_indicator(t38_core_state_t *s, int indicator); + +/*! \brief Find the delay to allow for HDLC flags after sending an indicator + \param s The T.38 context. + \param indicator The indicator to send. + \return The delay to allow for initial HDLC flags after this indicator is sent. */ +SPAN_DECLARE(int) t38_core_send_flags_delay(t38_core_state_t *s, int indicator); + +/*! \brief Send a data packet + \param s The T.38 context. + \param data_type The packet's data type. + \param field_type The packet's field type. + \param field The message data content for the packet. + \param field_len The length of the message data, in bytes. + \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e. + \return ??? */ +SPAN_DECLARE(int) t38_core_send_data(t38_core_state_t *s, int data_type, int field_type, const uint8_t field[], int field_len, int category); + +/*! \brief Send a data packet + \param s The T.38 context. + \param data_type The packet's data type. + \param field The list of fields. + \param fields The number of fields in the list. + \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e. + \return ??? */ +SPAN_DECLARE(int) t38_core_send_data_multi_field(t38_core_state_t *s, int data_type, const t38_data_field_t field[], int fields, int category); + +/*! \brief Process a received T.38 IFP packet. + \param s The T.38 context. + \param buf The packet contents. + \param len The length of the packet contents. + \param seq_no The packet sequence number. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t38_core_rx_ifp_packet(t38_core_state_t *s, const uint8_t *buf, int len, uint16_t seq_no); + +/*! Set the method to be used for data rate management, as per the T.38 spec. + \param s The T.38 context. + \param method 1 for pass TCF across the T.38 link, 2 for handle TCF locally. +*/ +SPAN_DECLARE(void) t38_set_data_rate_management_method(t38_core_state_t *s, int method); + +/*! Set the data transport protocol. + \param s The T.38 context. + \param data_transport_protocol UDPTL, RTP or TPKT. +*/ +SPAN_DECLARE(void) t38_set_data_transport_protocol(t38_core_state_t *s, int data_transport_protocol); + +/*! Set the non-ECM fill bit removal mode. + \param s The T.38 context. + \param fill_bit_removal TRUE to remove fill bits across the T.38 link, else FALSE. +*/ +SPAN_DECLARE(void) t38_set_fill_bit_removal(t38_core_state_t *s, int fill_bit_removal); + +/*! Set the MMR transcoding mode. + \param s The T.38 context. + \param mmr_transcoding TRUE to transcode to MMR across the T.38 link, else FALSE. +*/ +SPAN_DECLARE(void) t38_set_mmr_transcoding(t38_core_state_t *s, int mmr_transcoding); + +/*! Set the JBIG transcoding mode. + \param s The T.38 context. + \param jbig_transcoding TRUE to transcode to JBIG across the T.38 link, else FALSE. +*/ +SPAN_DECLARE(void) t38_set_jbig_transcoding(t38_core_state_t *s, int jbig_transcoding); + +/*! Set the maximum buffer size for received data at the far end. + \param s The T.38 context. + \param max_buffer_size The maximum buffer size. +*/ +SPAN_DECLARE(void) t38_set_max_buffer_size(t38_core_state_t *s, int max_buffer_size); + +/*! Set the maximum size of an IFP packet that is acceptable by the far end. + \param s The T.38 context. + \param max_datagram_size The maximum IFP packet length, in bytes. +*/ +SPAN_DECLARE(void) t38_set_max_datagram_size(t38_core_state_t *s, int max_datagram_size); + +/*! \brief Send a data packet + \param s The T.38 context. + \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e. + \param setting The repeat count for the category. This should be at least one for all categories other an indicator packets. + Zero is valid for indicator packets, as it suppresses the sending of indicator packets, as an application using + TCP for the transport would require. As the setting is passed through to the transmission channel, additional + information may be encoded in it, such as the redundancy depth for the particular packet category. */ +SPAN_DECLARE(void) t38_set_redundancy_control(t38_core_state_t *s, int category, int setting); + +SPAN_DECLARE(void) t38_set_fastest_image_data_rate(t38_core_state_t *s, int max_rate); + +SPAN_DECLARE(int) t38_get_fastest_image_data_rate(t38_core_state_t *s); + +/*! Set the T.38 version to be emulated. + \param s The T.38 context. + \param t38_version Version number, as in the T.38 spec. +*/ +SPAN_DECLARE(void) t38_set_t38_version(t38_core_state_t *s, int t38_version); + +/*! Set the sequence number handling option. + \param s The T.38 context. + \param check TRUE to check sequence numbers, and handle gaps reasonably. FALSE + for no sequence number processing (e.g. for TPKT over TCP transport). +*/ +SPAN_DECLARE(void) t38_set_sequence_number_handling(t38_core_state_t *s, int check); + +/*! Set the TEP handling option. + \param s The T.38 context. + \param allow_for_tep TRUE to allow for TEP playout, else FALSE. +*/ +SPAN_DECLARE(void) t38_set_tep_handling(t38_core_state_t *s, int allow_for_tep); + +/*! Get a pointer to the logging context associated with a T.38 context. + \brief Get a pointer to the logging context associated with a T.38 context. + \param s The T.38 context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) t38_core_get_logging_state(t38_core_state_t *s); + +/*! Initialise a T.38 core context. + \brief Initialise a T.38 core context. + \param s The T.38 context. + \param rx_indicator_handler Receive indicator handling routine. + \param rx_data_handler Receive data packet handling routine. + \param rx_rx_missing_handler Missing receive packet handling routine. + \param rx_packet_user_data An opaque pointer passed to the rx packet handling routines. + \param tx_packet_handler Packet transmit handling routine. + \param tx_packet_user_data An opaque pointer passed to the tx_packet_handler. + \return A pointer to the T.38 context, or NULL if there was a problem. */ +SPAN_DECLARE(t38_core_state_t *) t38_core_init(t38_core_state_t *s, + t38_rx_indicator_handler_t *rx_indicator_handler, + t38_rx_data_handler_t *rx_data_handler, + t38_rx_missing_handler_t *rx_missing_handler, + void *rx_user_data, + t38_tx_packet_handler_t *tx_packet_handler, + void *tx_packet_user_data); + +/*! Release a signaling tone transmitter context. + \brief Release a signaling tone transmitter context. + \param s The T.38 context. + \return 0 for OK */ +SPAN_DECLARE(int) t38_core_release(t38_core_state_t *s); + +/*! Free a signaling tone transmitter context. + \brief Free a signaling tone transmitter context. + \param s The T.38 context. + \return 0 for OK */ +SPAN_DECLARE(int) t38_core_free(t38_core_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t38_gateway.h b/Libraries/spandsp/spandsp/spandsp/t38_gateway.h new file mode 100644 index 000000000..c60db9432 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t38_gateway.h @@ -0,0 +1,215 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t38_gateway.h - A T.38, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005, 2006, 2007 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_gateway.h,v 1.63 2009/04/12 09:12:10 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T38_GATEWAY_H_) +#define _SPANDSP_T38_GATEWAY_H_ + +/*! \page t38_gateway_page T.38 real time FAX over IP PSTN gateway +\section t38_gateway_page_sec_1 What does it do? + +The T.38 gateway facility provides a robust interface between T.38 IP packet streams and +and 8k samples/second audio streams. It provides the buffering and flow control features needed +to maximum the tolerance of jitter and packet loss on the IP network. + +\section t38_gateway_page_sec_2 How does it work? +*/ + +/*! The receive buffer length */ +#define T38_RX_BUF_LEN 2048 +/*! The number of HDLC transmit buffers */ +#define T38_TX_HDLC_BUFS 256 +/*! The maximum length of an HDLC frame buffer. This must be big enough for ECM frames. */ +#define T38_MAX_HDLC_LEN 260 + +typedef struct t38_gateway_state_s t38_gateway_state_t; + +/*! + T.30 real time frame handler. + \brief T.30 real time frame handler. + \param s The T.30 context. + \param user_data An opaque pointer. + \param direction TRUE for incoming, FALSE for outgoing. + \param msg The HDLC message. + \param len The length of the message. +*/ +typedef void (t38_gateway_real_time_frame_handler_t)(t38_gateway_state_t *s, + void *user_data, + int direction, + const uint8_t *msg, + int len); + +/*! + T.38 gateway results. + */ +typedef struct +{ + /*! \brief The current bit rate for image transfer. */ + int bit_rate; + /*! \brief TRUE if error correcting mode is used. */ + int error_correcting_mode; + /*! \brief The number of pages transferred so far. */ + int pages_transferred; +} t38_stats_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Initialise a gateway mode T.38 context. + \param s The T.38 context. + \param tx_packet_handler A callback routine to encapsulate and transmit T.38 packets. + \param tx_packet_user_data An opaque pointer passed to the tx_packet_handler routine. + \return A pointer to the termination mode T.38 context, or NULL if there was a problem. */ +SPAN_DECLARE(t38_gateway_state_t *) t38_gateway_init(t38_gateway_state_t *s, + t38_tx_packet_handler_t *tx_packet_handler, + void *tx_packet_user_data); + +/*! Release a gateway mode T.38 context. + \brief Release a T.38 context. + \param s The T.38 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t38_gateway_release(t38_gateway_state_t *s); + +/*! Free a gateway mode T.38 context. + \brief Free a T.38 context. + \param s The T.38 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t38_gateway_free(t38_gateway_state_t *s); + +/*! Process a block of received FAX audio samples. + \brief Process a block of received FAX audio samples. + \param s The T.38 context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) t38_gateway_rx(t38_gateway_state_t *s, int16_t amp[], int len); + +/*! Generate a block of FAX audio samples. + \brief Generate a block of FAX audio samples. + \param s The T.38 context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE(int) t38_gateway_tx(t38_gateway_state_t *s, int16_t amp[], int max_len); + +/*! Control whether error correcting mode (ECM) is allowed. + \brief Control whether error correcting mode (ECM) is allowed. + \param s The T.38 context. + \param ecm_allowed TRUE is ECM is to be allowed. +*/ +SPAN_DECLARE(void) t38_gateway_set_ecm_capability(t38_gateway_state_t *s, int ecm_allowed); + +/*! Select whether silent audio will be sent when transmit is idle. + \brief Select whether silent audio will be sent when transmit is idle. + \param s The T.38 context. + \param transmit_on_idle TRUE if silent audio should be output when the FAX transmitter is + idle. FALSE to transmit zero length audio when the FAX transmitter is idle. The default + behaviour is FALSE. +*/ +SPAN_DECLARE(void) t38_gateway_set_transmit_on_idle(t38_gateway_state_t *s, int transmit_on_idle); + +/*! Specify which modem types are supported by a T.30 context. + \brief Specify supported modems. + \param s The T.38 context. + \param supported_modems Bit field list of the supported modems. +*/ +SPAN_DECLARE(void) t38_gateway_set_supported_modems(t38_gateway_state_t *s, int supported_modems); + +/*! Select whether NSC, NSF, and NSS should be suppressed. It selected, the contents of + these messages are forced to zero for all octets beyond the message type. This makes + them look like manufacturer specific messages, from a manufacturer which does not exist. + \brief Select whether NSC, NSF, and NSS should be suppressed. + \param s The T.38 context. + \param from_t38 A string of bytes to overwrite the header of any NSC, NSF, and NSS + frames passing through the gateway from T.38 the the modem. + \param from_t38_len The length of the overwrite string. + \param from_modem A string of bytes to overwrite the header of any NSC, NSF, and NSS + frames passing through the gateway from the modem to T.38. + \param from_modem_len The length of the overwrite string. +*/ +SPAN_DECLARE(void) t38_gateway_set_nsx_suppression(t38_gateway_state_t *s, + const uint8_t *from_t38, + int from_t38_len, + const uint8_t *from_modem, + int from_modem_len); + +/*! Select whether talker echo protection tone will be sent for the image modems. + \brief Select whether TEP will be sent for the image modems. + \param s The T.38 context. + \param use_tep TRUE if TEP should be sent. +*/ +SPAN_DECLARE(void) t38_gateway_set_tep_mode(t38_gateway_state_t *s, int use_tep); + +/*! Select whether non-ECM fill bits are to be removed during transmission. + \brief Select whether non-ECM fill bits are to be removed during transmission. + \param s The T.38 context. + \param remove TRUE if fill bits are to be removed. +*/ +SPAN_DECLARE(void) t38_gateway_set_fill_bit_removal(t38_gateway_state_t *s, int remove); + +/*! Get the current transfer statistics for the current T.38 session. + \brief Get the current transfer statistics. + \param s The T.38 context. + \param t A pointer to a buffer for the statistics. */ +SPAN_DECLARE(void) t38_gateway_get_transfer_statistics(t38_gateway_state_t *s, t38_stats_t *t); + +/*! Get a pointer to the T.38 core IFP packet engine associated with a + gateway mode T.38 context. + \brief Get a pointer to the T.38 core IFP packet engine associated + with a T.38 context. + \param s The T.38 context. + \return A pointer to the T.38 core context, or NULL. +*/ +SPAN_DECLARE(t38_core_state_t *) t38_gateway_get_t38_core_state(t38_gateway_state_t *s); + +/*! Get a pointer to the logging context associated with a T.38 context. + \brief Get a pointer to the logging context associated with a T.38 context. + \param s The T.38 context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) t38_gateway_get_logging_state(t38_gateway_state_t *s); + +/*! Set a callback function for T.30 frame exchange monitoring. This is called from the heart + of the signal processing, so don't take too long in the handler routine. + \brief Set a callback function for T.30 frame exchange monitoring. + \param s The T.30 context. + \param handler The callback function. + \param user_data An opaque pointer passed to the callback function. */ +SPAN_DECLARE(void) t38_gateway_set_real_time_frame_handler(t38_gateway_state_t *s, + t38_gateway_real_time_frame_handler_t *handler, + void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t38_non_ecm_buffer.h b/Libraries/spandsp/spandsp/spandsp/t38_non_ecm_buffer.h new file mode 100644 index 000000000..8ffffde18 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t38_non_ecm_buffer.h @@ -0,0 +1,136 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t38_non_ecm_buffer.h - A rate adapting buffer for T.38 non-ECM image + * and TCF data + * + * Written by Steve Underwood + * + * Copyright (C) 2005, 2006, 2007, 2008 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_non_ecm_buffer.h,v 1.7.4.1 2009/12/19 06:43:28 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T38_NON_ECM_BUFFER_H_) +#define _SPANDSP_T38_NON_ECM_BUFFER_H_ + +/*! \page t38_non_ecm_buffer_page T.38 rate adapting non-ECM image data buffer +\section t38_non_ecm_buffer_page_sec_1 What does it do? + +The T.38 rate adapting non-ECM image data buffer is used to buffer TCF and non-ECM +FAX image data being gatewayed from a T.38 link to an analogue FAX modem link. + +As well as rate adapting, the buffer has the ability to impose a minimum on the number +of bits per row of image data. This allows any row padding zeros to be stripped from +the data stream, to minimise the data sent as T.38 packets, and be reinserted before +the data is sent to its final destination. Not all T.38 implementations support this +feature, so it's use must be negotiated. + +\section t38_non_ecm_buffer_page_sec_2 How does it work? + +When inserting padding bits, whether to ensure a minimum row time or for flow control, +it is important the right value is inserted at the right point in the data sequence. +If we are in the optional initial period of all ones, we can insert a byte of extra +ones at any time. Once we pass that initial stage, TCF and image data need separate +handling. + +TCF data is all zeros. Once the period of all zeros has begun it is OK to insert +additional bytes of zeros at any point. + +Image data consists of rows, separated by EOL (end of line) markers. Inserting +zeros at arbitrary times would corrupt the image. However, it is OK to insert a +considerable number of extra zeros just before an EOL. Therefore we track where the +EOL markers occur as we fill the buffer. As we empty the buffer stop outputting real +data, and start outputting bytes of zero, if we reach this last EOL marker location. +The EOL marker is 11 zeros following by 1 (1D mode) or 2 (2D mode) ones. Therefore, it +always spills across 2 bytes in the buffer, and there is always a point where we can +insert our extra zeros between bytes. + +Padding between the group of successive EOL markers which for the RTC (return to control) +marker that ends an image causes some FAX machines not to recognise them as an RTC condition. +Therefore, our padding applies special protection so padding never occurs between two +successive EOL markers, with no pixel data between them. +*/ + +/*! The buffer length much be a power of two. The chosen length is big enough for + over 9s of data at the V.17 14,400bps rate. */ +#define T38_NON_ECM_TX_BUF_LEN 16384 + +/*! \brief A flow controlled non-ECM image data buffer, for buffering T.38 to analogue + modem data. +*/ +typedef struct t38_non_ecm_buffer_state_s t38_non_ecm_buffer_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Initialise a T.38 rate adapting non-ECM buffer context. + \param s The buffer context. + \param mode TRUE for image data mode, or FALSE for TCF mode. + \param bits The minimum number of bits per FAX image row. + \return A pointer to the buffer context, or NULL if there was a problem. */ +SPAN_DECLARE(t38_non_ecm_buffer_state_t *) t38_non_ecm_buffer_init(t38_non_ecm_buffer_state_t *s, int mode, int min_row_bits); + +SPAN_DECLARE(int) t38_non_ecm_buffer_release(t38_non_ecm_buffer_state_t *s); + +SPAN_DECLARE(int) t38_non_ecm_buffer_free(t38_non_ecm_buffer_state_t *s); + +/*! \brief Set the mode of a T.38 rate adapting non-ECM buffer context. + \param s The buffer context. + \param mode TRUE for image data mode, or FALSE for TCF mode. + \param bits The minimum number of bits per FAX image row. */ +SPAN_DECLARE(void) t38_non_ecm_buffer_set_mode(t38_non_ecm_buffer_state_t *s, int mode, int min_row_bits); + +/*! \brief Inject data to T.38 rate adapting non-ECM buffer context. + \param s The buffer context. + \param buf The data buffer to be injected. + \param len The length of the data to be injected. */ +SPAN_DECLARE(void) t38_non_ecm_buffer_inject(t38_non_ecm_buffer_state_t *s, const uint8_t *buf, int len); + +/*! \brief Inform a T.38 rate adapting non-ECM buffer context that the incoming data has finished, + and the contents of the buffer should be played out as quickly as possible. + \param s The buffer context. */ +SPAN_DECLARE(void) t38_non_ecm_buffer_push(t38_non_ecm_buffer_state_t *s); + +/*! \brief Report the input status of a T.38 rate adapting non-ECM buffer context to the specified + logging context. + \param s The buffer context. + \param logging The logging context. */ +SPAN_DECLARE(void) t38_non_ecm_buffer_report_input_status(t38_non_ecm_buffer_state_t *s, logging_state_t *logging); + +/*! \brief Report the output status of a T.38 rate adapting non-ECM buffer context to the specified + logging context. + \param s The buffer context. + \param logging The logging context. */ +SPAN_DECLARE(void) t38_non_ecm_buffer_report_output_status(t38_non_ecm_buffer_state_t *s, logging_state_t *logging); + +/*! \brief Get the next bit of data from a T.38 rate adapting non-ECM buffer context. + \param user_data The buffer context, cast to a void pointer. + \return The next bit, or one of the values indicating a change of modem status. */ +SPAN_DECLARE_NONSTD(int) t38_non_ecm_buffer_get_bit(void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t38_terminal.h b/Libraries/spandsp/spandsp/spandsp/t38_terminal.h new file mode 100644 index 000000000..4bcb1e0d2 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t38_terminal.h @@ -0,0 +1,119 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t38_terminal.h - T.38 termination, less the packet exchange part + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t38_terminal.h,v 1.41 2009/02/03 16:28:41 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T38_TERMINAL_H_) +#define _SPANDSP_T38_TERMINAL_H_ + +/*! \page t38_terminal_page T.38 real time FAX over IP termination +\section t38_terminal_page_sec_1 What does it do? + +\section t38_terminal_page_sec_2 How does it work? +*/ + +/* Make sure the HDLC frame buffers are big enough for ECM frames. */ +#define T38_MAX_HDLC_LEN 260 + +typedef struct t38_terminal_state_s t38_terminal_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(int) t38_terminal_send_timeout(t38_terminal_state_t *s, int samples); + +SPAN_DECLARE(void) t38_terminal_set_config(t38_terminal_state_t *s, int without_pacing); + +/*! Select whether the time for talker echo protection tone will be allowed for when sending. + \brief Select whether TEP time will be allowed for. + \param s The T.38 context. + \param use_tep TRUE if TEP should be allowed for. +*/ +SPAN_DECLARE(void) t38_terminal_set_tep_mode(t38_terminal_state_t *s, int use_tep); + +/*! Select whether non-ECM fill bits are to be removed during transmission. + \brief Select whether non-ECM fill bits are to be removed during transmission. + \param s The T.38 context. + \param remove TRUE if fill bits are to be removed. +*/ +SPAN_DECLARE(void) t38_terminal_set_fill_bit_removal(t38_terminal_state_t *s, int remove); + +/*! Get a pointer to the T.30 engine associated with a termination mode T.38 context. + \brief Get a pointer to the T.30 engine associated with a T.38 context. + \param s The T.38 context. + \return A pointer to the T.30 context, or NULL. +*/ +SPAN_DECLARE(t30_state_t *) t38_terminal_get_t30_state(t38_terminal_state_t *s); + +/*! Get a pointer to the T.38 core IFP packet engine associated with a + termination mode T.38 context. + \brief Get a pointer to the T.38 core IFP packet engine associated + with a T.38 context. + \param s The T.38 context. + \return A pointer to the T.38 core context, or NULL. +*/ +SPAN_DECLARE(t38_core_state_t *) t38_terminal_get_t38_core_state(t38_terminal_state_t *s); + +/*! Get a pointer to the logging context associated with a T.38 context. + \brief Get a pointer to the logging context associated with a T.38 context. + \param s The T.38 context. + \return A pointer to the logging context, or NULL. +*/ +SPAN_DECLARE(logging_state_t *) t38_terminal_get_logging_state(t38_terminal_state_t *s); + +/*! \brief Initialise a termination mode T.38 context. + \param s The T.38 context. + \param calling_party TRUE if the context is for a calling party. FALSE if the + context is for an answering party. + \param tx_packet_handler A callback routine to encapsulate and transmit T.38 packets. + \param tx_packet_user_data An opaque pointer passed to the tx_packet_handler routine. + \return A pointer to the termination mode T.38 context, or NULL if there was a problem. */ +SPAN_DECLARE(t38_terminal_state_t *) t38_terminal_init(t38_terminal_state_t *s, + int calling_party, + t38_tx_packet_handler_t *tx_packet_handler, + void *tx_packet_user_data); + +/*! Release a termination mode T.38 context. + \brief Release a T.38 context. + \param s The T.38 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t38_terminal_release(t38_terminal_state_t *s); + +/*! Free a a termination mode T.38 context. + \brief Free a T.38 context. + \param s The T.38 context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) t38_terminal_free(t38_terminal_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t4_rx.h b/Libraries/spandsp/spandsp/spandsp/t4_rx.h new file mode 100644 index 000000000..1cc887325 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t4_rx.h @@ -0,0 +1,344 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t4_rx.h - definitions for T.4 FAX receive processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t4_rx.h,v 1.3.2.3 2009/12/21 17:18:40 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T4_RX_H_) +#define _SPANDSP_T4_RX_H_ + +/*! \page t4_page T.4 image compression and decompression + +\section t4_page_sec_1 What does it do? +The T.4 image compression and decompression routines implement the 1D and 2D +encoding methods defined in ITU specification T.4. They also implement the pure +2D encoding method defined in T.6. These are image compression algorithms used +for FAX transmission. + +\section t4_page_sec_1 How does it work? +*/ + +typedef int (*t4_row_write_handler_t)(void *user_data, const uint8_t buf[], size_t len); + +/*! Supported compression modes. */ +typedef enum +{ + /*! No compression */ + T4_COMPRESSION_NONE = 0, + /*! T.1 1D compression */ + T4_COMPRESSION_ITU_T4_1D = 1, + /*! T.4 2D compression */ + T4_COMPRESSION_ITU_T4_2D = 2, + /*! T.6 2D compression */ + T4_COMPRESSION_ITU_T6 = 3, + /*! T.85 monochrome JBIG coding */ + T4_COMPRESSION_ITU_T85 = 4, + /*! T.43 colour JBIG coding */ + T4_COMPRESSION_ITU_T43 = 5, + /*! T.45 run length colour compression */ + T4_COMPRESSION_ITU_T45 = 6, + /*! T.81 + T.30 Annex E colour JPEG coding */ + T4_COMPRESSION_ITU_T81 = 7, + /*! T.81 + T.30 Annex K colour sYCC-JPEG coding */ + T4_COMPRESSION_ITU_SYCC_T81 = 8 +} t4_image_compression_t; + +/*! Supported X resolutions, in pixels per metre. */ +typedef enum +{ + T4_X_RESOLUTION_R4 = 4016, + T4_X_RESOLUTION_R8 = 8031, + T4_X_RESOLUTION_300 = 11811, + T4_X_RESOLUTION_R16 = 16063, + T4_X_RESOLUTION_600 = 23622, + T4_X_RESOLUTION_800 = 31496, + T4_X_RESOLUTION_1200 = 47244 +} t4_image_x_resolution_t; + +/*! Supported Y resolutions, in pixels per metre. */ +typedef enum +{ + T4_Y_RESOLUTION_STANDARD = 3850, + T4_Y_RESOLUTION_FINE = 7700, + T4_Y_RESOLUTION_300 = 11811, + T4_Y_RESOLUTION_SUPERFINE = 15400, /* 400 is 15748 */ + T4_Y_RESOLUTION_600 = 23622, + T4_Y_RESOLUTION_800 = 31496, + T4_Y_RESOLUTION_1200 = 47244 +} t4_image_y_resolution_t; + +/*! + Exact widths in PELs for the difference resolutions, and page widths. + Note: + The A4 widths also apply to North American letter and legal. + The R4 resolution widths are not supported in recent versions of T.30 + Only images of exactly these widths are acceptable for FAX transmisson. + + R4 864 pels/215mm for ISO A4, North American Letter and Legal + R4 1024 pels/255mm for ISO B4 + R4 1216 pels/303mm for ISO A3 + R8 1728 pels/215mm for ISO A4, North American Letter and Legal + R8 2048 pels/255mm for ISO B4 + R8 2432 pels/303mm for ISO A3 + R16 3456 pels/215mm for ISO A4, North American Letter and Legal + R16 4096 pels/255mm for ISO B4 + R16 4864 pels/303mm for ISO A3 +*/ +typedef enum +{ + T4_WIDTH_R4_A4 = 864, + T4_WIDTH_R4_B4 = 1024, + T4_WIDTH_R4_A3 = 1216, + T4_WIDTH_R8_A4 = 1728, + T4_WIDTH_R8_B4 = 2048, + T4_WIDTH_R8_A3 = 2432, + T4_WIDTH_300_A4 = 2592, + T4_WIDTH_300_B4 = 3072, + T4_WIDTH_300_A3 = 3648, + T4_WIDTH_R16_A4 = 3456, + T4_WIDTH_R16_B4 = 4096, + T4_WIDTH_R16_A3 = 4864, + T4_WIDTH_600_A4 = 5184, + T4_WIDTH_600_B4 = 6144, + T4_WIDTH_600_A3 = 7296, + T4_WIDTH_1200_A4 = 10368, + T4_WIDTH_1200_B4 = 12288, + T4_WIDTH_1200_A3 = 14592 +} t4_image_width_t; + +/*! + Length of the various supported paper sizes, in pixels at the various Y resolutions. + Paper sizes are + A4 (215mm x 297mm) + B4 (255mm x 364mm) + A3 (303mm x 418.56mm) + North American Letter (215.9mm x 279.4mm) + North American Legal (215.9mm x 355.6mm) + Unlimited + + T.4 does not accurately define the maximum number of scan lines in a page. A wide + variety of maximum row counts are used in the real world. It is important not to + set our sending limit too high, or a receiving machine might split pages. It is + important not to set it too low, or we might clip pages. + + Values seen for standard resolution A4 pages include 1037, 1045, 1109, 1126 and 1143. + 1109 seems the most-popular. At fine res 2150, 2196, 2200, 2237, 2252-2262, 2264, + 2286, and 2394 are used. 2255 seems the most popular. We try to use balanced choices + here. +*/ +typedef enum +{ + /* A4 is 297mm long */ + T4_LENGTH_STANDARD_A4 = 1143, + T4_LENGTH_FINE_A4 = 2286, + T4_LENGTH_300_A4 = 4665, + T4_LENGTH_SUPERFINE_A4 = 4573, + T4_LENGTH_600_A4 = 6998, + T4_LENGTH_800_A4 = 9330, + T4_LENGTH_1200_A4 = 13996, + /* B4 is 364mm long */ + T4_LENGTH_STANDARD_B4 = 1401, + T4_LENGTH_FINE_B4 = 2802, + T4_LENGTH_300_B4 = 0, + T4_LENGTH_SUPERFINE_B4 = 5605, + T4_LENGTH_600_B4 = 0, + T4_LENGTH_800_B4 = 0, + T4_LENGTH_1200_B4 = 0, + /* North American letter is 279.4mm long */ + T4_LENGTH_STANDARD_US_LETTER = 1075, + T4_LENGTH_FINE_US_LETTER = 2151, + T4_LENGTH_300_US_LETTER = 0, + T4_LENGTH_SUPERFINE_US_LETTER = 4302, + T4_LENGTH_600_US_LETTER = 0, + T4_LENGTH_800_US_LETTER = 0, + T4_LENGTH_1200_US_LETTER = 0, + /* North American legal is 355.6mm long */ + T4_LENGTH_STANDARD_US_LEGAL = 1369, + T4_LENGTH_FINE_US_LEGAL = 2738, + T4_LENGTH_300_US_LEGAL = 0, + T4_LENGTH_SUPERFINE_US_LEGAL = 5476, + T4_LENGTH_600_US_LEGAL = 0, + T4_LENGTH_800_US_LEGAL = 0, + T4_LENGTH_1200_US_LEGAL = 0 +} t4_image_length_t; + +/*! + T.4 FAX compression/decompression descriptor. This defines the working state + for a single instance of a T.4 FAX compression or decompression channel. +*/ +typedef struct t4_state_s t4_state_t; + +/*! + T.4 FAX compression/decompression statistics. +*/ +typedef struct +{ + /*! \brief The number of pages transferred so far. */ + int pages_transferred; + /*! \brief The number of pages in the file (<0 if unknown). */ + int pages_in_file; + /*! \brief The number of horizontal pixels in the most recent page. */ + int width; + /*! \brief The number of vertical pixels in the most recent page. */ + int length; + /*! \brief The number of bad pixel rows in the most recent page. */ + int bad_rows; + /*! \brief The largest number of bad pixel rows in a block in the most recent page. */ + int longest_bad_row_run; + /*! \brief The horizontal resolution of the page in pixels per metre */ + int x_resolution; + /*! \brief The vertical resolution of the page in pixels per metre */ + int y_resolution; + /*! \brief The type of compression used between the FAX machines */ + int encoding; + /*! \brief The size of the image on the line, in bytes */ + int line_image_size; +} t4_stats_t; + +#if defined(__cplusplus) +extern "C" { +#endif + +/*! \brief Prepare for reception of a document. + \param s The T.4 context. + \param file The name of the file to be received. + \param output_encoding The output encoding. + \return A pointer to the context, or NULL if there was a problem. */ +SPAN_DECLARE(t4_state_t *) t4_rx_init(t4_state_t *s, const char *file, int output_encoding); + +/*! \brief Prepare to receive the next page of the current document. + \param s The T.4 context. + \return zero for success, -1 for failure. */ +SPAN_DECLARE(int) t4_rx_start_page(t4_state_t *s); + +/*! \brief Put a bit of the current document page. + \param s The T.4 context. + \param bit The data bit. + \return TRUE when the bit ends the document page, otherwise FALSE. */ +SPAN_DECLARE(int) t4_rx_put_bit(t4_state_t *s, int bit); + +/*! \brief Put a byte of the current document page. + \param s The T.4 context. + \param byte The data byte. + \return TRUE when the byte ends the document page, otherwise FALSE. */ +SPAN_DECLARE(int) t4_rx_put_byte(t4_state_t *s, uint8_t byte); + +/*! \brief Put a byte of the current document page. + \param s The T.4 context. + \param buf The buffer containing the chunk. + \param len The length of the chunk. + \return TRUE when the byte ends the document page, otherwise FALSE. */ +SPAN_DECLARE(int) t4_rx_put_chunk(t4_state_t *s, const uint8_t buf[], int len); + +/*! \brief Complete the reception of a page. + \param s The T.4 receive context. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_rx_end_page(t4_state_t *s); + +/*! \brief End reception of a document. Tidy up and close the file. + This should be used to end T.4 reception started with + t4_rx_init. + \param s The T.4 receive context. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_rx_release(t4_state_t *s); + +/*! \brief End reception of a document. Tidy up, close the file and + free the context. This should be used to end T.4 reception + started with t4_rx_init. + \param s The T.4 receive context. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_rx_free(t4_state_t *s); + +/*! \brief Set the row write handler for a T.4 receive context. + \param s The T.4 receive context. + \param handler A pointer to the handler routine. + \param user_data An opaque pointer passed to the handler routine. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_rx_set_row_write_handler(t4_state_t *s, t4_row_write_handler_t handler, void *user_data); + +/*! \brief Set the encoding for the received data. + \param s The T.4 context. + \param encoding The encoding. */ +SPAN_DECLARE(void) t4_rx_set_rx_encoding(t4_state_t *s, int encoding); + +/*! \brief Set the expected width of the received image, in pixel columns. + \param s The T.4 context. + \param width The number of pixels across the image. */ +SPAN_DECLARE(void) t4_rx_set_image_width(t4_state_t *s, int width); + +/*! \brief Set the row-to-row (y) resolution to expect for a received image. + \param s The T.4 context. + \param resolution The resolution, in pixels per metre. */ +SPAN_DECLARE(void) t4_rx_set_y_resolution(t4_state_t *s, int resolution); + +/*! \brief Set the column-to-column (x) resolution to expect for a received image. + \param s The T.4 context. + \param resolution The resolution, in pixels per metre. */ +SPAN_DECLARE(void) t4_rx_set_x_resolution(t4_state_t *s, int resolution); + +/*! \brief Set the DCS information of the fax, for inclusion in the file. + \param s The T.4 context. + \param dcs The DCS information, formatted as an ASCII string. */ +SPAN_DECLARE(void) t4_rx_set_dcs(t4_state_t *s, const char *dcs); + +/*! \brief Set the sub-address of the fax, for inclusion in the file. + \param s The T.4 context. + \param sub_address The sub-address string. */ +SPAN_DECLARE(void) t4_rx_set_sub_address(t4_state_t *s, const char *sub_address); + +/*! \brief Set the identity of the remote machine, for inclusion in the file. + \param s The T.4 context. + \param ident The identity string. */ +SPAN_DECLARE(void) t4_rx_set_far_ident(t4_state_t *s, const char *ident); + +/*! \brief Set the vendor of the remote machine, for inclusion in the file. + \param s The T.4 context. + \param vendor The vendor string, or NULL. */ +SPAN_DECLARE(void) t4_rx_set_vendor(t4_state_t *s, const char *vendor); + +/*! \brief Set the model of the remote machine, for inclusion in the file. + \param s The T.4 context. + \param model The model string, or NULL. */ +SPAN_DECLARE(void) t4_rx_set_model(t4_state_t *s, const char *model); + +/*! Get the current image transfer statistics. + \brief Get the current transfer statistics. + \param s The T.4 context. + \param t A pointer to a statistics structure. */ +SPAN_DECLARE(void) t4_get_transfer_statistics(t4_state_t *s, t4_stats_t *t); + +/*! Get the short text name of an encoding format. + \brief Get the short text name of an encoding format. + \param encoding The encoding type. + \return A pointer to the string. */ +SPAN_DECLARE(const char *) t4_encoding_to_str(int encoding); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/t4_tx.h b/Libraries/spandsp/spandsp/spandsp/t4_tx.h new file mode 100644 index 000000000..c30386c03 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/t4_tx.h @@ -0,0 +1,179 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * t4_tx.h - definitions for T.4 FAX transmit processing + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: t4_tx.h,v 1.2.2.3 2009/12/21 17:18:40 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_T4_TX_H_) +#define _SPANDSP_T4_TX_H_ + +typedef int (*t4_row_read_handler_t)(void *user_data, uint8_t buf[], size_t len); + +#if defined(__cplusplus) +extern "C" { +#endif + +/*! \brief Prepare for transmission of a document. + \param s The T.4 context. + \param file The name of the file to be sent. + \param start_page The first page to send. -1 for no restriction. + \param stop_page The last page to send. -1 for no restriction. + \return A pointer to the context, or NULL if there was a problem. */ +SPAN_DECLARE(t4_state_t *) t4_tx_init(t4_state_t *s, const char *file, int start_page, int stop_page); + +/*! \brief Prepare to send the next page of the current document. + \param s The T.4 context. + \return zero for success, -1 for failure. */ +SPAN_DECLARE(int) t4_tx_start_page(t4_state_t *s); + +/*! \brief Prepare the current page for a resend. + \param s The T.4 context. + \return zero for success, -1 for failure. */ +SPAN_DECLARE(int) t4_tx_restart_page(t4_state_t *s); + +/*! \brief Check for the existance of the next page, and whether its format is like the + current one. This information can be needed before it is determined that the current + page is finished with. + \param s The T.4 context. + \return 0 for next page found with the same format as the current page. + 1 for next page found with different format from the current page. + -1 for no page found, or file failure. */ +SPAN_DECLARE(int) t4_tx_next_page_has_different_format(t4_state_t *s); + +/*! \brief Complete the sending of a page. + \param s The T.4 context. + \return zero for success, -1 for failure. */ +SPAN_DECLARE(int) t4_tx_end_page(t4_state_t *s); + +/*! \brief Return the next bit of the current document page, without actually + moving forward in the buffer. The document will be padded for the + current minimum scan line time. + \param s The T.4 context. + \return The next bit (i.e. 0 or 1). For the last bit of data, bit 1 is + set (i.e. the returned value is 2 or 3). */ +SPAN_DECLARE(int) t4_tx_check_bit(t4_state_t *s); + +/*! \brief Get the next bit of the current document page. The document will + be padded for the current minimum scan line time. + \param s The T.4 context. + \return The next bit (i.e. 0 or 1). For the last bit of data, bit 1 is + set (i.e. the returned value is 2 or 3). */ +SPAN_DECLARE(int) t4_tx_get_bit(t4_state_t *s); + +/*! \brief Get the next byte of the current document page. The document will + be padded for the current minimum scan line time. + \param s The T.4 context. + \return The next byte. For the last byte of data, bit 8 is + set. In this case, one or more bits of the byte may be padded with + zeros, to complete the byte. */ +SPAN_DECLARE(int) t4_tx_get_byte(t4_state_t *s); + +/*! \brief Get the next chunk of the current document page. The document will + be padded for the current minimum scan line time. + \param s The T.4 context. + \param buf The buffer into which the chunk is to written. + \param max_len The maximum length of the chunk. + \return The actual length of the chunk. If this is less than max_len it + indicates that the end of the document has been reached. */ +SPAN_DECLARE(int) t4_tx_get_chunk(t4_state_t *s, uint8_t buf[], int max_len); + +/*! \brief End the transmission of a document. Tidy up and close the file. + This should be used to end T.4 transmission started with t4_tx_init. + \param s The T.4 context. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_tx_release(t4_state_t *s); + +/*! \brief End the transmission of a document. Tidy up, close the file and + free the context. This should be used to end T.4 transmission + started with t4_tx_init. + \param s The T.4 context. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_tx_free(t4_state_t *s); + +/*! \brief Set the encoding for the encoded data. + \param s The T.4 context. + \param encoding The encoding. */ +SPAN_DECLARE(void) t4_tx_set_tx_encoding(t4_state_t *s, int encoding); + +/*! \brief Set the minimum number of encoded bits per row. This allows the + makes the encoding process to be set to comply with the minimum row + time specified by a remote receiving machine. + \param s The T.4 context. + \param bits The minimum number of bits per row. */ +SPAN_DECLARE(void) t4_tx_set_min_row_bits(t4_state_t *s, int bits); + +/*! \brief Set the identity of the local machine, for inclusion in page headers. + \param s The T.4 context. + \param ident The identity string. */ +SPAN_DECLARE(void) t4_tx_set_local_ident(t4_state_t *s, const char *ident); + +/*! Set the info field, included in the header line included in each page of an encoded + FAX. This is a string of up to 50 characters. Other information (date, local ident, etc.) + are automatically included in the header. If the header info is set to NULL or a zero + length string, no header lines will be added to the encoded FAX. + \brief Set the header info. + \param s The T.4 context. + \param info A string, of up to 50 bytes, which will form the info field. */ +SPAN_DECLARE(void) t4_tx_set_header_info(t4_state_t *s, const char *info); + +/*! \brief Set the row read handler for a T.4 transmit context. + \param s The T.4 transmit context. + \param handler A pointer to the handler routine. + \param user_data An opaque pointer passed to the handler routine. + \return 0 for success, otherwise -1. */ +SPAN_DECLARE(int) t4_tx_set_row_read_handler(t4_state_t *s, t4_row_read_handler_t handler, void *user_data); + +/*! \brief Get the row-to-row (y) resolution of the current page. + \param s The T.4 context. + \return The resolution, in pixels per metre. */ +SPAN_DECLARE(int) t4_tx_get_y_resolution(t4_state_t *s); + +/*! \brief Get the column-to-column (x) resolution of the current page. + \param s The T.4 context. + \return The resolution, in pixels per metre. */ +SPAN_DECLARE(int) t4_tx_get_x_resolution(t4_state_t *s); + +/*! \brief Get the width of the current page, in pixel columns. + \param s The T.4 context. + \return The number of columns. */ +SPAN_DECLARE(int) t4_tx_get_image_width(t4_state_t *s); + +/*! \brief Get the number of pages in the file. + \param s The T.4 context. + \return The number of pages, or -1 if there is an error. */ +SPAN_DECLARE(int) t4_tx_get_pages_in_file(t4_state_t *s); + +/*! \brief Get the currnet page number in the file. + \param s The T.4 context. + \return The page number, or -1 if there is an error. */ +SPAN_DECLARE(int) t4_tx_get_current_page_in_file(t4_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/telephony.h b/Libraries/spandsp/spandsp/spandsp/telephony.h new file mode 100644 index 000000000..703c0bd96 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/telephony.h @@ -0,0 +1,91 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * telephony.h - some very basic telephony definitions + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: telephony.h,v 1.18.4.2 2009/12/21 18:38:06 steveu Exp $ + */ + +#if !defined(_SPANDSP_TELEPHONY_H_) +#define _SPANDSP_TELEPHONY_H_ + +#if defined(_M_IX86) || defined(_M_X64) +#if defined(LIBSPANDSP_EXPORTS) +#define SPAN_DECLARE(type) __declspec(dllexport) type __stdcall +#define SPAN_DECLARE_NONSTD(type) __declspec(dllexport) type __cdecl +#define SPAN_DECLARE_DATA __declspec(dllexport) +#else +#define SPAN_DECLARE(type) __declspec(dllimport) type __stdcall +#define SPAN_DECLARE_NONSTD(type) __declspec(dllimport) type __cdecl +#define SPAN_DECLARE_DATA __declspec(dllimport) +#endif +#elif defined(SPANDSP_USE_EXPORT_CAPABILITY) && (defined(__GNUC__) || defined(__SUNCC__)) +#define SPAN_DECLARE(type) __attribute__((visibility("default"))) type +#define SPAN_DECLARE_NONSTD(type) __attribute__((visibility("default"))) type +#define SPAN_DECLARE_DATA __attribute__((visibility("default"))) +#else +#define SPAN_DECLARE(type) /**/ type +#define SPAN_DECLARE_NONSTD(type) /**/ type +#define SPAN_DECLARE_DATA /**/ +#endif + +#define SAMPLE_RATE 8000 + +/* This is based on A-law, but u-law is only 0.03dB different */ +#define DBM0_MAX_POWER (3.14f + 3.02f) +#define DBM0_MAX_SINE_POWER (3.14f) +/* This is based on the ITU definition of dbOv in G.100.1 */ +#define DBOV_MAX_POWER (0.0f) +#define DBOV_MAX_SINE_POWER (-3.02f) + +/*! \brief A handler for pure receive. The buffer cannot be altered. */ +typedef int (span_rx_handler_t)(void *s, const int16_t amp[], int len); + +/*! \brief A handler for receive, where the buffer can be altered. */ +typedef int (span_mod_handler_t)(void *s, int16_t amp[], int len); + +/*! \brief A handler for missing receive data fill-in. */ +typedef int (span_rx_fillin_handler_t)(void *s, int len); + +/*! \brief A handler for transmit, where the buffer will be filled. */ +typedef int (span_tx_handler_t)(void *s, int16_t amp[], int max_len); + +#define ms_to_samples(t) ((t)*(SAMPLE_RATE/1000)) +#define us_to_samples(t) ((t)/(1000000/SAMPLE_RATE)) + +#if !defined(FALSE) +#define FALSE 0 +#endif +#if !defined(TRUE) +#define TRUE (!FALSE) +#endif + +#if defined(__cplusplus) +/* C++ doesn't seem to have sane rounding functions/macros yet */ +#if !defined(WIN32) +#define lrint(x) ((long int) (x)) +#define lrintf(x) ((long int) (x)) +#endif +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/time_scale.h b/Libraries/spandsp/spandsp/spandsp/time_scale.h new file mode 100644 index 000000000..f91e15f4b --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/time_scale.h @@ -0,0 +1,121 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * time_scale.h - Time scaling for linear speech data + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: time_scale.h,v 1.20 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_TIME_SCALE_H_) +#define _SPANDSP_TIME_SCALE_H_ + +#include "telephony.h" +#include "private/time_scale.h" + +/*! \page time_scale_page Time scaling speech +\section time_scale_page_sec_1 What does it do? +The time scaling module allows speech files to be played back at a +different speed from the speed at which they were recorded. If this +were done by simply speeding up or slowing down replay, the pitch of +the voice would change, and sound very odd. This module keeps the pitch +of the voice at its original level. + +The speed of the voice may be altered over a wide range. However, the practical +useful rates are between about half normal speed and twice normal speed. + +\section time_scale_page_sec_2 How does it work? +The time scaling module is based on the Pointer Interval Controlled +OverLap and Add (PICOLA) method, developed by Morita Naotaka. +Mikio Ikeda has an excellent web page on this subject at +http://keizai.yokkaichi-u.ac.jp/~ikeda/research/picola.html +There is also working code there. This implementation uses +exactly the same algorithms, but the code is a complete rewrite. +Mikio's code batch processes files. This version works incrementally +on streams, and allows multiple streams to be processed concurrently. + +\section time_scale_page_sec_3 How do I used it? +The output buffer must be big enough to hold the maximum number of samples which +could result from the data in the input buffer, which is: + + input_len*playout_rate + sample_rate/TIME_SCALE_MIN_PITCH + 1 +*/ + +/*! Audio time scaling descriptor. */ +typedef struct time_scale_state_s time_scale_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a time scale context. This must be called before the first + use of the context, to initialise its contents. + \brief Initialise a time scale context. + \param s The time scale context. + \param sample_rate The sample rate of the signal. + \param playout_rate The ratio between the output speed and the input speed. + \return A pointer to the context, or NULL if there was a problem. */ +SPAN_DECLARE(time_scale_state_t *) time_scale_init(time_scale_state_t *s, int sample_rate, float playout_rate); + +/*! \brief Release a time scale context. + \param s The time scale context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) time_scale_release(time_scale_state_t *s); + +/*! \brief Free a time scale context. + \param s The time scale context. + \return 0 for OK, else -1. */ +SPAN_DECLARE(int) time_scale_free(time_scale_state_t *s); + +/*! Change the time scale rate. + \brief Change the time scale rate. + \param s The time scale context. + \param playout_rate The ratio between the output speed and the input speed. + \return 0 if changed OK, else -1. */ +SPAN_DECLARE(int) time_scale_rate(time_scale_state_t *s, float playout_rate); + +/*! Find the maximum possible samples which could result from scaling the specified + number of input samples, at the current playback rate. + \brief Find the maximum possible output samples. + \param s The time scale context. + \param input_len The number of input samples. + \return The maximum possible output samples. */ +SPAN_DECLARE(int) time_scale_max_output_len(time_scale_state_t *s, int input_len); + +/*! Time scale a chunk of audio samples. + \brief Time scale a chunk of audio samples. + \param s The time scale context. + \param out The output audio sample buffer. This must be large enough to accept + the longest possible result from processing the input data. See the + algorithm documentation for how the longest possible result may be calculated. + \param in The input audio sample buffer. + \param len The number of input samples. + \return The number of output samples. +*/ +SPAN_DECLARE(int) time_scale(time_scale_state_t *s, int16_t out[], int16_t in[], int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/timing.h b/Libraries/spandsp/spandsp/spandsp/timing.h new file mode 100644 index 000000000..85f3ce5f4 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/timing.h @@ -0,0 +1,83 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * timing.h - Provide access to the Pentium/Athlon TSC timer register + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: timing.h,v 1.14 2009/09/04 14:38:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_TIMING_H_) +#define _SPANDSP_TIMING_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#if defined(__MSVC__) +__declspec(naked) unsigned __int64 __cdecl rdtscll(void) +{ + __asm + { + rdtsc + ret ; return value at EDX:EAX + } +} +/*- End of function --------------------------------------------------------*/ +#elif defined(__GNUC__) +#if defined(__i386__) +static __inline__ uint64_t rdtscll(void) +{ + uint64_t now; + + __asm__ __volatile__(" rdtsc\n" : "=A" (now)); + return now; +} +/*- End of function --------------------------------------------------------*/ +#elif defined(__x86_64__) +static __inline__ uint64_t rdtscll(void) +{ + uint32_t a; + uint32_t d; + + /* For x86_64 we need to merge the result in 2 32 bit registers + into one clean 64 bit result. */ + __asm__ __volatile__(" rdtsc\n" : "=a" (a), "=d" (d)); + return ((uint64_t) a) | (((uint64_t) d) << 32); +} +/*- End of function --------------------------------------------------------*/ +#else +static __inline__ uint64_t rdtscll(void) +{ + /* This architecture doesn't have a suitable timer */ + return 0llu; +} +/*- End of function --------------------------------------------------------*/ +#endif +#endif + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/tone_detect.h b/Libraries/spandsp/spandsp/spandsp/tone_detect.h new file mode 100644 index 000000000..6822fff67 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/tone_detect.h @@ -0,0 +1,248 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * tone_detect.h - General telephony tone detection. + * + * Written by Steve Underwood + * + * Copyright (C) 2001, 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: tone_detect.h,v 1.45 2009/02/10 13:06:47 steveu Exp $ + */ + +#if !defined(_SPANDSP_TONE_DETECT_H_) +#define _SPANDSP_TONE_DETECT_H_ + +/*! + Goertzel filter descriptor. +*/ +struct goertzel_descriptor_s +{ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t fac; +#else + float fac; +#endif + int samples; +}; + +/*! + Goertzel filter state descriptor. +*/ +struct goertzel_state_s +{ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t v2; + int16_t v3; + int16_t fac; +#else + float v2; + float v3; + float fac; +#endif + int samples; + int current_sample; +}; + +/*! + Goertzel filter descriptor. +*/ +typedef struct goertzel_descriptor_s goertzel_descriptor_t; + +/*! + Goertzel filter state descriptor. +*/ +typedef struct goertzel_state_s goertzel_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! \brief Create a descriptor for use with either a Goertzel transform */ +SPAN_DECLARE(void) make_goertzel_descriptor(goertzel_descriptor_t *t, + float freq, + int samples); + +/*! \brief Initialise the state of a Goertzel transform. + \param s The Goertzel context. If NULL, a context is allocated with malloc. + \param t The Goertzel descriptor. + \return A pointer to the Goertzel state. */ +SPAN_DECLARE(goertzel_state_t *) goertzel_init(goertzel_state_t *s, + goertzel_descriptor_t *t); + +SPAN_DECLARE(int) goertzel_release(goertzel_state_t *s); + +SPAN_DECLARE(int) goertzel_free(goertzel_state_t *s); + +/*! \brief Reset the state of a Goertzel transform. + \param s The Goertzel context. */ +SPAN_DECLARE(void) goertzel_reset(goertzel_state_t *s); + +/*! \brief Update the state of a Goertzel transform. + \param s The Goertzel context. + \param amp The samples to be transformed. + \param samples The number of samples. + \return The number of samples unprocessed */ +SPAN_DECLARE(int) goertzel_update(goertzel_state_t *s, + const int16_t amp[], + int samples); + +/*! \brief Evaluate the final result of a Goertzel transform. + \param s The Goertzel context. + \return The result of the transform. The expected result for a pure sine wave + signal of level x dBm0, at the very centre of the bin is: + [Floating point] ((samples_per_goertzel_block*32768.0/1.4142)*10^((x - DBM0_MAX_SINE_POWER)/20.0))^2 + [Fixed point] ((samples_per_goertzel_block*256.0/1.4142)*10^((x - DBM0_MAX_SINE_POWER)/20.0))^2 */ +#if defined(SPANDSP_USE_FIXED_POINT) +SPAN_DECLARE(int32_t) goertzel_result(goertzel_state_t *s); +#else +SPAN_DECLARE(float) goertzel_result(goertzel_state_t *s); +#endif + +/*! \brief Update the state of a Goertzel transform. + \param s The Goertzel context. + \param amp The sample to be transformed. */ +static __inline__ void goertzel_sample(goertzel_state_t *s, int16_t amp) +{ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t x; + int16_t v1; +#else + float v1; +#endif + + v1 = s->v2; + s->v2 = s->v3; +#if defined(SPANDSP_USE_FIXED_POINT) + x = (((int32_t) s->fac*s->v2) >> 14); + /* Scale down the input signal to avoid overflows. 9 bits is enough to + monitor the signals of interest with adequate dynamic range and + resolution. In telephony we generally only start with 13 or 14 bits, + anyway. */ + s->v3 = x - v1 + (amp >> 7); +#else + s->v3 = s->fac*s->v2 - v1 + amp; +#endif + s->current_sample++; +} +/*- End of function --------------------------------------------------------*/ + +/* Scale down the input signal to avoid overflows. 9 bits is enough to + monitor the signals of interest with adequate dynamic range and + resolution. In telephony we generally only start with 13 or 14 bits, + anyway. This is sufficient for the longest Goertzel we currently use. */ +#if defined(SPANDSP_USE_FIXED_POINT) +#define goertzel_preadjust_amp(amp) (((int16_t) amp) >> 7) +#else +#define goertzel_preadjust_amp(amp) ((float) amp) +#endif + +/* Minimal update the state of a Goertzel transform. This is similar to + goertzel_sample, but more suited to blocks of Goertzels. It assumes + the amplitude is pre-shifted, and does not update the per-state sample + count. + \brief Update the state of a Goertzel transform. + \param s The Goertzel context. + \param amp The adjusted sample to be transformed. */ +#if defined(SPANDSP_USE_FIXED_POINT) +static __inline__ void goertzel_samplex(goertzel_state_t *s, int16_t amp) +#else +static __inline__ void goertzel_samplex(goertzel_state_t *s, float amp) +#endif +{ +#if defined(SPANDSP_USE_FIXED_POINT) + int16_t x; + int16_t v1; +#else + float v1; +#endif + + v1 = s->v2; + s->v2 = s->v3; +#if defined(SPANDSP_USE_FIXED_POINT) + x = (((int32_t) s->fac*s->v2) >> 14); + s->v3 = x - v1 + amp; +#else + s->v3 = s->fac*s->v2 - v1 + amp; +#endif +} +/*- End of function --------------------------------------------------------*/ + +/*! Generate a Hamming weighted coefficient set, to be used for a periodogram analysis. + \param coeffs The generated coefficients. + \param freq The frequency to be matched by the periodogram, in Hz. + \param sample_rate The sample rate of the signal, in samples per second. + \param window_len The length of the periodogram window. This must be an even number. + \return The number of generated coefficients. +*/ +SPAN_DECLARE(int) periodogram_generate_coeffs(complexf_t coeffs[], float freq, int sample_rate, int window_len); + +/*! Generate the phase offset to be expected between successive periodograms evaluated at the + specified interval. + \param offset A point to the generated phase offset. + \param freq The frequency being matched by the periodogram, in Hz. + \param sample_rate The sample rate of the signal, in samples per second. + \param interval The interval between periodograms, in samples. + \return The scaling factor. +*/ +SPAN_DECLARE(float) periodogram_generate_phase_offset(complexf_t *offset, float freq, int sample_rate, int interval); + +/*! Evaluate a periodogram. + \param coeffs A set of coefficients generated by periodogram_generate_coeffs(). + \param amp The complex amplitude of the signal. + \param len The length of the periodogram, in samples. This must be an even number. + \return The periodogram result. +*/ +SPAN_DECLARE(complexf_t) periodogram(const complexf_t coeffs[], const complexf_t amp[], int len); + +/*! Prepare data for evaluating a set of periodograms. + \param sum A vector of sums of pairs of signal samples. This will be half the length of len. + \param diff A vector of differences between pairs of signal samples. This will be half the length of len. + \param amp The complex amplitude of the signal. + \param len The length of the periodogram, in samples. This must be an even number. + \return The length of the vectors sum and diff. +*/ +SPAN_DECLARE(int) periodogram_prepare(complexf_t sum[], complexf_t diff[], const complexf_t amp[], int len); + +/*! Evaluate a periodogram, based on data prepared by periodogram_prepare(). This is more efficient + than using periodogram() when several periodograms are to be applied to the same signal. + \param coeffs A set of coefficients generated by periodogram_generate_coeffs(). + \param sum A vector of sums produced by periodogram_prepare(). + \param diff A vector of differences produced by periodogram_prepare(). + \param len The length of the periodogram, in samples. This must be an even number. + \return The periodogram result. +*/ +SPAN_DECLARE(complexf_t) periodogram_apply(const complexf_t coeffs[], const complexf_t sum[], const complexf_t diff[], int len); + +/*! Apply a phase offset, to find the frequency error between periodogram evaluations. + specified interval. + \param phase_offset A point to the expected phase offset. + \param scale The scaling factor to be used. + \param last_result A pointer to the previous periodogram result. + \param result A pointer to the current periodogram result. + \return The frequency error, in Hz. +*/ +SPAN_DECLARE(float) periodogram_freq_error(const complexf_t *phase_offset, float scale, const complexf_t *last_result, const complexf_t *result); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/tone_generate.h b/Libraries/spandsp/spandsp/spandsp/tone_generate.h new file mode 100644 index 000000000..2a15914ef --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/tone_generate.h @@ -0,0 +1,104 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * tone_generate.h - General telephony tone generation. + * + * Written by Steve Underwood + * + * Copyright (C) 2001 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: tone_generate.h,v 1.39 2009/06/02 16:03:56 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_TONE_GENERATE_H_) +#define _SPANDSP_TONE_GENERATE_H_ + +/*! \page tone_generation_page Tone generation +\section tone_generation_page_sec_1 What does it do? +The tone generation module provides for the generation of cadenced tones, +suitable for a wide range of telephony applications. + +\section tone_generation_page_sec_2 How does it work? +Oscillators are a problem. They oscillate due to instability, and yet we need +them to behave in a stable manner. A look around the web will reveal many papers +on this subject. Many describe rather complex solutions to the problem. However, +we are only concerned with telephony applications. It is possible to generate +the tones we need with a very simple efficient scheme. It is also practical to +use an exhaustive test to prove the oscillator is stable under all the +conditions in which we will use it. +*/ + +typedef struct tone_gen_tone_descriptor_s tone_gen_tone_descriptor_t; + +/*! + Cadenced multi-tone generator descriptor. +*/ +typedef struct tone_gen_descriptor_s tone_gen_descriptor_t; + +/*! + Cadenced multi-tone generator state descriptor. This defines the state of + a single working instance of a generator. +*/ +typedef struct tone_gen_state_s tone_gen_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Create a tone generator descriptor + \brief Create a tone generator descriptor + \param s The descriptor + \param f1 The first frequency, in Hz + \param l1 The level of the first frequency, in dBm0 + \param f2 0 for no second frequency, a positive number for the second frequency, + in Hz, or a negative number for an AM modulation frequency, in Hz + \param l2 The level of the second frequency, in dBm0, or the percentage modulation depth + for an AM modulated tone. + \param d1 x + \param d2 x + \param d3 x + \param d4 x + \param repeat x */ +SPAN_DECLARE(void) make_tone_gen_descriptor(tone_gen_descriptor_t *s, + int f1, + int l1, + int f2, + int l2, + int d1, + int d2, + int d3, + int d4, + int repeat); + +SPAN_DECLARE_NONSTD(int) tone_gen(tone_gen_state_t *s, int16_t amp[], int max_samples); + +SPAN_DECLARE(tone_gen_state_t *) tone_gen_init(tone_gen_state_t *s, tone_gen_descriptor_t *t); + +SPAN_DECLARE(int) tone_gen_release(tone_gen_state_t *s); + +SPAN_DECLARE(int) tone_gen_free(tone_gen_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v17rx.h b/Libraries/spandsp/spandsp/spandsp/v17rx.h new file mode 100644 index 000000000..2ab84151d --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v17rx.h @@ -0,0 +1,334 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v17rx.h - ITU V.17 modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v17rx.h,v 1.65 2009/07/09 13:52:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V17RX_H_) +#define _SPANDSP_V17RX_H_ + +/*! \page v17rx_page The V.17 receiver +\section v17rx_page_sec_1 What does it do? +The V.17 receiver implements the receive side of a V.17 modem. This can operate +at data rates of 14400, 12000, 9600 and 7200 bits/second. The audio input is a stream +of 16 bit samples, at 8000 samples/second. The transmit and receive side of V.17 +modems operate independantly. V.17 is mostly used for FAX transmission over PSTN +lines, where it provides the standard 14400 bits/second rate. + +\section v17rx_page_sec_2 How does it work? +V.17 uses QAM modulation, at 2400 baud, and trellis coding. Constellations with +16, 32, 64, and 128 points are defined. After one bit per baud is absorbed by the +trellis coding, this gives usable bit rates of 7200, 9600, 12000, and 14400 per +second. + +V.17 specifies a training sequence at the start of transmission, which makes the +design of a V.17 receiver relatively straightforward. The first stage of the +training sequence consists of 256 +symbols, alternating between two constellation positions. The receiver monitors +the signal power, to sense the possible presence of a valid carrier. When the +alternating signal begins, the power rising above a minimum threshold (-43dBm0) +causes the main receiver computation to begin. The initial measured power is +used to quickly set the gain of the receiver. After this initial settling, the +front end gain is locked, and the adaptive equalizer tracks any subsequent +signal level variation. The signal is oversampled to 24000 samples/second (i.e. +signal, zero, zero, signal, zero, zero, ...) and fed to a complex root raised +cosine pulse shaping filter. This filter has been modified from the conventional +root raised cosine filter, by shifting it up the band, to be centred at the nominal +carrier frequency. This filter interpolates the samples, pulse shapes, and performs +a fractional sample delay at the same time. 192 sets of filter coefficients are used +to achieve a set of finely spaces fractional sample delays, between zero and +one sample. By choosing every fifth sample, and the appropriate set of filter +coefficients, the properly tuned symbol tracker can select data samples at 4800 +samples/second from points within 0.28 degrees of the centre and mid-points of +each symbol. The output of the filter is multiplied by a complex carrier, generated +by a DDS. The result is a baseband signal, requiring no further filtering, apart from +an adaptive equalizer. The baseband signal is fed to a T/2 adaptive equalizer. +A band edge component maximisation algorithm is used to tune the sampling, so the samples +fed to the equalizer are close to the mid point and edges of each symbol. Initially +the algorithm is very lightly damped, to ensure the symbol alignment pulls in +quickly. Because the sampling rate will not be precisely the same as the +transmitter's (the spec. says the symbol timing should be within 0.01%), the +receiver constantly evaluates and corrects this sampling throughout its +operation. During the symbol timing maintainence phase, the algorithm uses +a heavier damping. + +The carrier is specified as 1800Hz +- 1Hz at the transmitter, and 1800 +-7Hz at +the receiver. The receive carrier would only be this inaccurate if the link +includes FDM sections. These are being phased out, but the design must still +allow for the worst case. Using an initial 1800Hz signal for demodulation gives +a worst case rotation rate for the constellation of about one degree per symbol. +Once the symbol timing synchronisation algorithm has been given time to lock to the +symbol timing of the initial alternating pattern, the phase of the demodulated signal +is recorded on two successive symbols - once for each of the constellation positions. +The receiver then tracks the symbol alternations, until a large phase jump occurs. +This signifies the start of the next phase of the training sequence. At this +point the total phase shift between the original recorded symbol phase, and the +symbol phase just before the phase jump occurred is used to provide a coarse +estimation of the rotation rate of the constellation, and it current absolute +angle of rotation. These are used to update the current carrier phase and phase +update rate in the carrier DDS. The working data already in the pulse shaping +filter and equalizer buffers is given a similar step rotation to pull it all +into line. From this point on, a heavily damped integrate and dump approach, +based on the angular difference between each received constellation position and +its expected position, is sufficient to track the carrier, and maintain phase +alignment. A fast rough approximator for the arc-tangent function is adequate +for the estimation of the angular error. + +The next phase of the training sequence is a scrambled sequence of two +particular symbols. We train the T/2 adaptive equalizer using this sequence. The +scrambling makes the signal sufficiently diverse to ensure the equalizer +converges to the proper generalised solution. At the end of this sequence, the +equalizer should be sufficiently well adapted that is can correctly resolve the +full QAM constellation. However, the equalizer continues to adapt throughout +operation of the modem, fine tuning on the more complex data patterns of the +full QAM constellation. + +In the last phase of the training sequence, the modem enters normal data +operation, with a short defined period of all ones as data. As in most high +speed modems, data in a V.17 modem passes through a scrambler, to whiten the +spectrum of the signal. The transmitter should initialise its data scrambler, +and pass the ones through it. At the end of the ones, real data begins to pass +through the scrambler, and the transmit modem is in normal operation. The +receiver tests that ones are really received, in order to verify the modem +trained correctly. If all is well, the data following the ones is fed to the +application, and the receive modem is up and running. Unfortunately, some +transmit side of some real V.17 modems fail to initialise their scrambler before +sending the ones. This means the first 23 received bits (the length of the +scrambler register) cannot be trusted for the test. The receive modem, +therefore, only tests that bits starting at bit 24 are really ones. + +The V.17 signal is trellis coded. Two bits of each symbol are convolutionally coded +to form a 3 bit trellis code - the two original bits, plus an extra redundant bit. It +is possible to ignore the trellis coding, and just decode the non-redundant bits. +However, the noise performance of the receiver would suffer. Using a proper +trellis decoder adds several dB to the noise tolerance to the receiving modem. Trellis +coding seems quite complex at first sight, but is fairly straightforward once you +get to grips with it. + +Trellis decoding tracks the data in terms of the possible states of the convolutional +coder at the transmitter. There are 8 possible states of the V.17 coder. The first +step in trellis decoding is to find the best candidate constellation point +for each of these 8 states. One of thse will be our final answer. The constellation +has been designed so groups of 8 are spread fairly evenly across it. Locating them +is achieved is a reasonably fast manner, by looking up the answers in a set of space +map tables. The disadvantage is the tables are potentially large enough to affect +cache performance. The trellis decoder works over 16 successive symbols. The result +of decoding is not known until 16 symbols after the data enters the decoder. The +minimum total accumulated mismatch between each received point and the actual +constellation (termed the distance) is assessed for each of the 8 states. A little +analysis of the coder shows that each of the 8 current states could be arrived at +from 4 different previous states, through 4 different constellation bit patterns. +For each new state, the running total distance is arrived at by inspecting a previous +total plus a new distance for the appropriate 4 previous states. The minimum of the 4 +values becomes the new distance for the state. Clearly, a mechanism is needed to stop +this distance from growing indefinitely. A sliding window, and several other schemes +are possible. However, a simple single pole IIR is very simple, and provides adequate +results. + +For each new state we store the constellation bit pattern, or path, to that state, and +the number of the previous state. We find the minimum distance amongst the 8 new +states for each new symbol. We then trace back through the states, until we reach the +one 16 states ago which leads to the current minimum distance. The bit pattern stored +there is the error corrected bit pattern for that symbol. + +So, what does Trellis coding actually achieve? TCM is easier to understand by looking +at the V.23bis modem spec. The V.32bis spec. is very similar to V.17, except that it +is a full duplex modem and has non-TCM options, as well as the TCM ones in V.17. + +V32bis defines two options for pumping 9600 bits per second down a phone line - one +with and one without TCM. Both run at 2400 baud. The non-TCM one uses simple 16 point +QAM on the raw data. The other takes two out of every four raw bits, and convolutionally +encodes them to 3. Now we have 5 bits per symbol, and we need 32 point QAM to send the +data. + +The raw error rate from simple decoding of the 32 point QAM is horrible compared to +decoding the 16 point QAM. If a point decoded from the 32 point QAM is wrong, the likely +correct choice should be one of the adjacent ones. It is unlikely to have been one that +is far away across the constellation, unless there was a huge noise spike, interference, +or something equally nasty. Now, the 32 point symbols do not exist in isolation. There +was a kind of temporal smearing in the convolutional coding. It created a well defined +dependency between successive symbols. If we knew for sure what the last few symbols +were, they would lead us to a limited group of possible values for the current symbol, +constrained by the behaviour of the convolutional coder. If you look at how the symbols +were mapped to constellation points, you will see the mapping tries to spread those +possible symbols as far apart as possible. This will leave only one that is pretty +close to the received point, which must be the correct choice. However, this assumes +we know the last few symbols for sure. Since we don't, we have a bit more work to do +to achieve reliable decoding. + +Instead of decoding to the nearest point on the constellation, we decode to a group of +likely constellation points in the neighbourhood of the received point. We record the +mismatch for each - that is the distance across the constellation between the received +point and the group of nearby points. To avoid square roots, recording x2 + y2 can be +good enough. Symbol by symbol, we record this information. After a few symbols we can +stand back and look at the recorded information. + +For each symbol we have a set of possible symbol values and error metric pairs. The +dependency between symbols, created by the convolutional coder, means some paths from +symbol to symbol are possible and some are not. It we trace back through the possible +symbol to symbol paths, and total up the error metric through those paths, we end up +with a set of figures of merit (or more accurately figures of demerit, since +larger == worse) for the likelihood of each path being the correct one. The path with +the lowest total metric is the most likely, and gives us our final choice for what we +think the current symbol really is. + +That was hard work. It takes considerable computation to do this selection and traceback, +symbol by symbol. We need to get quite a lot from this. It needs to drive the error rate +down so far that is compensates for the much higher error rate due to the larger +constellation, and then buys us some actual benefit. Well in the example we are looking +at - V.32bis at 9600bps - it works out the error rate from the TCM option is like using +the non-TCM option with several dB more signal to noise ratio. That's nice. The non-TCM +option is pretty reasonable on most phone lines, but a better error rate is always a +good thing. However, V32bis includes a 14,400bps option. That uses 2400 baud, and 6 bit +symbols. Convolutional encoding increases that to 7 bits per symbol, by taking 2 bits and +encoding them to 3. This give a 128 point QAM constellation. Again, the difference between +using this, and using just an uncoded 64 point constellation is equivalent to maybe 5dB of +extra signal to noise ratio. However, in this case it is the difference between the modem +working only on the most optimal lines, and being widely usable across most phone lines. +TCM absolutely transformed the phone line modem business. +*/ + +/*! + V.17 modem receive side descriptor. This defines the working state for a + single instance of a V.17 modem receiver. +*/ +typedef struct v17_rx_state_s v17_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a V.17 modem receive context. + \brief Initialise a V.17 modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 7200, 9600, 12000 and 14400. + \param put_bit The callback routine used to put the received data. + \param user_data An opaque pointer passed to the put_bit routine. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v17_rx_state_t *) v17_rx_init(v17_rx_state_t *s, int bit_rate, put_bit_func_t put_bit, void *user_data); + +/*! Reinitialise an existing V.17 modem receive context. + \brief Reinitialise an existing V.17 modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 7200, 9600, 12000 and 14400. + \param short_train TRUE if a short training sequence is expected. + \return 0 for OK, -1 for bad parameter */ +SPAN_DECLARE(int) v17_rx_restart(v17_rx_state_t *s, int bit_rate, int short_train); + +/*! Release a V.17 modem receive context. + \brief Release a V.17 modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v17_rx_release(v17_rx_state_t *s); + +/*! Free a V.17 modem receive context. + \brief Free a V.17 modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v17_rx_free(v17_rx_state_t *s); + +/*! Get the logging context associated with a V.17 modem receive context. + \brief Get the logging context associated with a V.17 modem receive context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v17_rx_get_logging_state(v17_rx_state_t *s); + +/*! Change the put_bit function associated with a V.17 modem receive context. + \brief Change the put_bit function associated with a V.17 modem receive context. + \param s The modem context. + \param put_bit The callback routine used to handle received bits. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v17_rx_set_put_bit(v17_rx_state_t *s, put_bit_func_t put_bit, void *user_data); + +/*! Change the modem status report function associated with a V.17 modem receive context. + \brief Change the modem status report function associated with a V.17 modem receive context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v17_rx_set_modem_status_handler(v17_rx_state_t *s, modem_rx_status_func_t handler, void *user_data); + +/*! Process a block of received V.17 modem audio samples. + \brief Process a block of received V.17 modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE_NONSTD(int) v17_rx(v17_rx_state_t *s, const int16_t amp[], int len); + +/*! Fake processing of a missing block of received V.17 modem audio samples. + (e.g due to packet loss). + \brief Fake processing of a missing block of received V.17 modem audio samples. + \param s The modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE(int) v17_rx_fillin(v17_rx_state_t *s, int len); + +/*! Get a snapshot of the current equalizer coefficients. + \brief Get a snapshot of the current equalizer coefficients. + \param s The modem context. + \param coeffs The vector of complex coefficients. + \return The number of coefficients in the vector. */ +#if defined(SPANDSP_USE_FIXED_POINTx) +SPAN_DECLARE(int) v17_rx_equalizer_state(v17_rx_state_t *s, complexi_t **coeffs); +#else +SPAN_DECLARE(int) v17_rx_equalizer_state(v17_rx_state_t *s, complexf_t **coeffs); +#endif + +/*! Get the current received carrier frequency. + \param s The modem context. + \return The frequency, in Hertz. */ +SPAN_DECLARE(float) v17_rx_carrier_frequency(v17_rx_state_t *s); + +/*! Get the current symbol timing correction since startup. + \param s The modem context. + \return The correction. */ +SPAN_DECLARE(float) v17_rx_symbol_timing_correction(v17_rx_state_t *s); + +/*! Get a current received signal power. + \param s The modem context. + \return The signal power, in dBm0. */ +SPAN_DECLARE(float) v17_rx_signal_power(v17_rx_state_t *s); + +/*! Set the power level at which the carrier detection will cut in + \param s The modem context. + \param cutoff The signal cutoff power, in dBm0. */ +SPAN_DECLARE(void) v17_rx_signal_cutoff(v17_rx_state_t *s, float cutoff); + +/*! Set a handler routine to process QAM status reports + \param s The modem context. + \param handler The handler routine. + \param user_data An opaque pointer passed to the handler routine. */ +SPAN_DECLARE(void) v17_rx_set_qam_report_handler(v17_rx_state_t *s, qam_report_handler_t handler, void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v17tx.h b/Libraries/spandsp/spandsp/spandsp/v17tx.h new file mode 100644 index 000000000..01b0f8205 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v17tx.h @@ -0,0 +1,167 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v17tx.h - ITU V.17 modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v17tx.h,v 1.43.4.1 2009/12/24 16:52:30 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V17TX_H_) +#define _SPANDSP_V17TX_H_ + +/*! \page v17tx_page The V.17 transmitter +\section v17tx_page_sec_1 What does it do? +The V.17 transmitter implements the transmit side of a V.17 modem. This can +operate at data rates of 14400, 12000, 9600 and 7200 bits/second. The audio +output is a stream of 16 bit samples, at 8000 samples/second. The transmit and +receive side of V.17 modems operate independantly. V.17 is mostly used for FAX +transmission, where it provides the standard 14400 bits/second rate. + +\section v17tx_page_sec_2 How does it work? +V.17 uses QAM modulation and trellis coding. The data to be transmitted is +scrambled, to whiten it. The least significant 2 bits of each symbol are then +differentially encoded, using a simple lookup approach. The resulting 2 bits are +convolutionally encoded, producing 3 bits. The extra bit is the redundant bit +of the trellis code. The other bits of the symbol pass by the differential +and convolutional coding unchanged. The resulting bits define the constellation +point to be transmitted for the symbol. The redundant bit doubles the size of the +constellation, and so increases the error rate for detecting individual symbols +at the receiver. However, when a number of successive symbols are processed at +the receiver, the redundancy actually provides several dB of improved error +performance. + +The standard method of producing a QAM modulated signal is to use a sampling +rate which is a multiple of the baud rate. The raw signal is then a series of +complex pulses, each an integer number of samples long. These can be shaped, +using a suitable complex filter, and multiplied by a complex carrier signal +to produce the final QAM signal for transmission. + +The pulse shaping filter is only vaguely defined by the V.17 spec. Some of the +other ITU modem specs. fully define the filter, typically specifying a root +raised cosine filter, with 50% excess bandwidth. This is a pity, since it +increases the variability of the received signal. However, the receiver's +adaptive equalizer will compensate for these differences. The current +design uses a root raised cosine filter with 25% excess bandwidth. Greater +excess bandwidth will not allow the tranmitted signal to meet the spectral +requirements. + +The sampling rate for our transmitter is defined by the channel - 8000 per +second. This is not a multiple of the baud rate (i.e. 2400 baud). The baud +interval is actually 10/3 sample periods. Instead of using a symmetric +FIR to pulse shape the signal, a polyphase filter is used. This consists of +10 sets of coefficients, offering zero to 9/10ths of a baud phase shift as well +as root raised cosine filtering. The appropriate coefficient set is chosen for +each signal sample generated. + +The carrier is generated using the DDS method. Using two second order resonators, +started in quadrature, might be more efficient, as it would have less impact on +the processor cache than a table lookup approach. However, the DDS approach +suits the receiver better, so the same signal generator is also used for the +transmitter. +*/ + +/*! + V.17 modem transmit side descriptor. This defines the working state for a + single instance of a V.17 modem transmitter. +*/ +typedef struct v17_tx_state_s v17_tx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Adjust a V.17 modem transmit context's power output. + \brief Adjust a V.17 modem transmit context's output power. + \param s The modem context. + \param power The power level, in dBm0 */ +SPAN_DECLARE(void) v17_tx_power(v17_tx_state_t *s, float power); + +/*! Initialise a V.17 modem transmit context. This must be called before the first + use of the context, to initialise its contents. + \brief Initialise a V.17 modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 7200, 9600, 12000 and 14400. + \param tep TRUE is the optional TEP tone is to be transmitted. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v17_tx_state_t *) v17_tx_init(v17_tx_state_t *s, int bit_rate, int tep, get_bit_func_t get_bit, void *user_data); + +/*! Reinitialise an existing V.17 modem transmit context, so it may be reused. + \brief Reinitialise an existing V.17 modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 7200, 9600, 12000 and 14400. + \param tep TRUE is the optional TEP tone is to be transmitted. + \param short_train TRUE if the short training sequence should be used. + \return 0 for OK, -1 for parameter error. */ +SPAN_DECLARE(int) v17_tx_restart(v17_tx_state_t *s, int bit_rate, int tep, int short_train); + +/*! Release a V.17 modem transmit context. + \brief Release a V.17 modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v17_tx_release(v17_tx_state_t *s); + +/*! Free a V.17 modem transmit context. + \brief Free a V.17 modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v17_tx_free(v17_tx_state_t *s); + +/*! Get the logging context associated with a V.17 modem transmit context. + \brief Get the logging context associated with a V.17 modem transmit context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v17_tx_get_logging_state(v17_tx_state_t *s); + +/*! Change the get_bit function associated with a V.17 modem transmit context. + \brief Change the get_bit function associated with a V.17 modem transmit context. + \param s The modem context. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v17_tx_set_get_bit(v17_tx_state_t *s, get_bit_func_t get_bit, void *user_data); + +/*! Change the modem status report function associated with a V.17 modem transmit context. + \brief Change the modem status report function associated with a V.17 modem transmit context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v17_tx_set_modem_status_handler(v17_tx_state_t *s, modem_tx_status_func_t handler, void *user_data); + +/*! Generate a block of V.17 modem audio samples. + \brief Generate a block of V.17 modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) v17_tx(v17_tx_state_t *s, int16_t amp[], int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v18.h b/Libraries/spandsp/spandsp/spandsp/v18.h new file mode 100644 index 000000000..7e964cdba --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v18.h @@ -0,0 +1,156 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v18.h - V.18 text telephony for the deaf. + * + * Written by Steve Underwood + * + * Copyright (C) 2004-2009 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v18.h,v 1.6 2009/11/04 15:52:06 steveu Exp $ + */ + +/*! \file */ + +/*! \page v18_page The V.18 text telephony protocols +\section v18_page_sec_1 What does it do? + +\section v18_page_sec_2 How does it work? +*/ + +#if !defined(_SPANDSP_V18_H_) +#define _SPANDSP_V18_H_ + +typedef struct v18_state_s v18_state_t; + +enum +{ + V18_MODE_NONE = 0, + /* V.18 Annex A - Weitbrecht TDD at 45.45bps, half-duplex, 5 bit baudot. */ + V18_MODE_5BIT_45 = 1, + /* V.18 Annex A - Weitbrecht TDD at 50bps, half-duplex, 5 bit baudot. */ + V18_MODE_5BIT_50 = 2, + /* V.18 Annex B - DTMF encoding of ASCII. */ + V18_MODE_DTMF = 3, + /* V.18 Annex C - EDT 110bps, V.21, half-duplex, ASCII. */ + V18_MODE_EDT = 4, + /* V.18 Annex D - 300bps, Bell 103, duplex, ASCII. */ + V18_MODE_BELL103 = 5, + /* V.18 Annex E - 1200bps Videotex terminals, ASCII. */ + V18_MODE_V23VIDEOTEX = 6, + /* V.18 Annex F - V.21 text telephone, V.21, duplex, ASCII. */ + V18_MODE_V21TEXTPHONE = 7, + /* V.18 Annex G - V.18 text telephone mode. */ + V18_MODE_V18TEXTPHONE = 8 +}; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(logging_state_t *) v18_get_logging_state(v18_state_t *s); + +/*! Initialise a V.18 context. + \brief Initialise a V.18 context. + \param s The V.18 context. + \param calling_party TRUE if caller mode, else answerer mode. + \param mode Mode of operation. + \param put_msg A callback routine called to deliver the received text + to the application. + \param user_data An opaque pointer for the callback routine. + \return A pointer to the V.18 context, or NULL if there was a problem. */ +SPAN_DECLARE(v18_state_t *) v18_init(v18_state_t *s, + int calling_party, + int mode, + put_msg_func_t put_msg, + void *user_data); + +/*! Release a V.18 context. + \brief Release a V.18 context. + \param s The V.18 context. + \return 0 for OK. */ +SPAN_DECLARE(int) v18_release(v18_state_t *s); + +/*! Free a V.18 context. + \brief Release a V.18 context. + \param s The V.18 context. + \return 0 for OK. */ +SPAN_DECLARE(int) v18_free(v18_state_t *s); + +/*! Generate a block of V.18 audio samples. + \brief Generate a block of V.18 audio samples. + \param s The V.18 context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) v18_tx(v18_state_t *s, int16_t amp[], int max_len); + +/*! Process a block of received V.18 audio samples. + \brief Process a block of received V.18 audio samples. + \param s The V.18 context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. +*/ +SPAN_DECLARE_NONSTD(int) v18_rx(v18_state_t *s, const int16_t amp[], int len); + +/*! \brief Put a string to a V.18 context's input buffer. + \param s The V.18 context. + \param msg The string to be added. + \param len The length of the string. If negative, the string is + assumed to be a NULL terminated string. + \return The number of characters actually added. This may be less than the + length of the digit string, if the buffer fills up. If the string is + invalid, this function will return -1. */ +SPAN_DECLARE(int) v18_put(v18_state_t *s, const char msg[], int len); + +/*! Convert a text string to a V.18 DTMF string. + \brief Convert a text string to a V.18 DTMF string. + \param s The V.18 context. + \param dtmf The resulting DTMF string. + \param msg The text string to be converted. + \return The length of the DTMF string. +*/ +SPAN_DECLARE(int) v18_encode_dtmf(v18_state_t *s, char dtmf[], const char msg[]); + +/*! Convert a V.18 DTMF string to a text string. + \brief Convert a V.18 DTMF string to a text string. + \param s The V.18 context. + \param msg The resulting test string. + \param dtmf The DTMF string to be converted. + \return The length of the text string. +*/ +SPAN_DECLARE(int) v18_decode_dtmf(v18_state_t *s, char msg[], const char dtmf[]); + +SPAN_DECLARE(uint16_t) v18_encode_baudot(v18_state_t *s, uint8_t ch); + +SPAN_DECLARE(uint8_t) v18_decode_baudot(v18_state_t *s, uint8_t ch); + +/*! \brief Return a short name for an V.18 mode + \param mode The code for the V.18 mode. + \return A pointer to the name. +*/ +SPAN_DECLARE(const char *) v18_mode_to_str(int mode); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v22bis.h b/Libraries/spandsp/spandsp/spandsp/v22bis.h new file mode 100644 index 000000000..aa2a50a05 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v22bis.h @@ -0,0 +1,225 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v22bis.h - ITU V.22bis modem + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v22bis.h,v 1.46 2009/11/04 15:52:06 steveu Exp $ + */ + +/*! \file */ + +/*! \page v22bis_page The V.22bis modem +\section v22bis_page_sec_1 What does it do? +The V.22bis modem is a duplex modem for general data use on the PSTN, at rates +of 1200 and 2400 bits/second. It is a compatible extension of the V.22 modem, +which is a 1200 bits/second only design. It is a band-split design, using carriers +of 1200Hz and 2400Hz. It is the fastest PSTN modem in general use which does not +use echo-cancellation. + +\section v22bis__page_sec_2 How does it work? +V.22bis uses 4PSK modulation at 1200 bits/second or 16QAM modulation at 2400 +bits/second. At 1200bps the symbols are so long that a fixed compromise equaliser +is sufficient to recover the 4PSK signal reliably. At 2400bps an adaptive +equaliser is necessary. + +The V.22bis training sequence includes sections which allow the modems to determine +if the far modem can support (or is willing to support) 2400bps operation. The +modems will automatically use 2400bps if both ends are willing to use that speed, +or 1200bps if one or both ends to not acknowledge that 2400bps is OK. +*/ + +#if !defined(_SPANDSP_V22BIS_H_) +#define _SPANDSP_V22BIS_H_ + +enum +{ + V22BIS_GUARD_TONE_NONE, + V22BIS_GUARD_TONE_550HZ, + V22BIS_GUARD_TONE_1800HZ +}; + +/*! + V.22bis modem descriptor. This defines the working state for a single instance + of a V.22bis modem. +*/ +typedef struct v22bis_state_s v22bis_state_t; + +extern const complexf_t v22bis_constellation[16]; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Process a block of received V.22bis modem audio samples. + \brief Process a block of received V.22bis modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE_NONSTD(int) v22bis_rx(v22bis_state_t *s, const int16_t amp[], int len); + +/*! Fake processing of a missing block of received V.22bis modem audio samples. + (e.g due to packet loss). + \brief Fake processing of a missing block of received V.22bis modem audio samples. + \param s The modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) v22bis_rx_fillin(v22bis_state_t *s, int len); + +/*! Get a snapshot of the current equalizer coefficients. + \brief Get a snapshot of the current equalizer coefficients. + \param coeffs The vector of complex coefficients. + \return The number of coefficients in the vector. */ +SPAN_DECLARE(int) v22bis_rx_equalizer_state(v22bis_state_t *s, complexf_t **coeffs); + +/*! Get the current received carrier frequency. + \param s The modem context. + \return The frequency, in Hertz. */ +SPAN_DECLARE(float) v22bis_rx_carrier_frequency(v22bis_state_t *s); + +/*! Get the current symbol timing correction since startup. + \param s The modem context. + \return The correction. */ +SPAN_DECLARE(float) v22bis_rx_symbol_timing_correction(v22bis_state_t *s); + +/*! Get a current received signal power. + \param s The modem context. + \return The signal power, in dBm0. */ +SPAN_DECLARE(float) v22bis_rx_signal_power(v22bis_state_t *s); + +/*! Set the power level at which the carrier detection will cut in + \param s The modem context. + \param cutoff The signal cutoff power, in dBm0. */ +SPAN_DECLARE(void) v22bis_rx_signal_cutoff(v22bis_state_t *s, float cutoff); + +/*! Set a handler routine to process QAM status reports + \param s The modem context. + \param handler The handler routine. + \param user_data An opaque pointer passed to the handler routine. */ +SPAN_DECLARE(void) v22bis_rx_set_qam_report_handler(v22bis_state_t *s, qam_report_handler_t handler, void *user_data); + +/*! Generate a block of V.22bis modem audio samples. + \brief Generate a block of V.22bis modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. */ +SPAN_DECLARE_NONSTD(int) v22bis_tx(v22bis_state_t *s, int16_t amp[], int len); + +/*! Adjust a V.22bis modem transmit context's power output. + \brief Adjust a V.22bis modem transmit context's output power. + \param s The modem context. + \param power The power level, in dBm0 */ +SPAN_DECLARE(void) v22bis_tx_power(v22bis_state_t *s, float power); + +/*! Reinitialise an existing V.22bis modem context, so it may be reused. + \brief Reinitialise an existing V.22bis modem context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 1200 and 2400. + \return 0 for OK, -1 for bad parameter. */ +SPAN_DECLARE(int) v22bis_restart(v22bis_state_t *s, int bit_rate); + +/*! Request a retrain for a V.22bis modem context. A rate change may also be requested. + \brief Request a retrain for a V.22bis modem context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 1200 and 2400. + \return 0 for OK, -1 for request rejected. */ +SPAN_DECLARE(int) v22bis_request_retrain(v22bis_state_t *s, int bit_rate); + +/*! Request a loopback 2 for a V.22bis modem context. + \brief Request a loopback 2 for a V.22bis modem context. + \param s The modem context. + \param enable TRUE to enable loopback, or FALSE to disable it. + \return 0 for OK, -1 for request reject. */ +SPAN_DECLARE(int) v22bis_remote_loopback(v22bis_state_t *s, int enable); + +/*! Report the current operating bit rate of a V.22bis modem context. + \brief Report the current operating bit rate of a V.22bis modem context + \param s The modem context. */ +SPAN_DECLARE(int) v22bis_current_bit_rate(v22bis_state_t *s); + +/*! Initialise a V.22bis modem context. This must be called before the first + use of the context, to initialise its contents. + \brief Initialise a V.22bis modem context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 1200 and 2400. + \param guard The guard tone option. 0 = none, 1 = 550Hz, 2 = 1800Hz. + \param calling_party TRUE if this is the calling modem. + \param get_bit The callback routine used to get the data to be transmitted. + \param put_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer, passed in calls to the get and put routines. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v22bis_state_t *) v22bis_init(v22bis_state_t *s, + int bit_rate, + int guard, + int calling_party, + get_bit_func_t get_bit, + void *get_bit_user_data, + put_bit_func_t put_bit, + void *put_bit_user_data); + +/*! Release a V.22bis modem receive context. + \brief Release a V.22bis modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v22bis_release(v22bis_state_t *s); + +/*! Free a V.22bis modem receive context. + \brief Free a V.22bis modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v22bis_free(v22bis_state_t *s); + +/*! Get the logging context associated with a V.22bis modem context. + \brief Get the logging context associated with a V.22bis modem context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v22bis_get_logging_state(v22bis_state_t *s); + +/*! Change the get_bit function associated with a V.22bis modem context. + \brief Change the get_bit function associated with a V.22bis modem context. + \param s The modem context. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v22bis_set_get_bit(v22bis_state_t *s, get_bit_func_t get_bit, void *user_data); + +/*! Change the get_bit function associated with a V.22bis modem context. + \brief Change the put_bit function associated with a V.22bis modem context. + \param s The modem context. + \param put_bit The callback routine used to process the data received. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v22bis_set_put_bit(v22bis_state_t *s, put_bit_func_t put_bit, void *user_data); + +/*! Change the modem status report function associated with a V.22bis modem receive context. + \brief Change the modem status report function associated with a V.22bis modem receive context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v22bis_set_modem_status_handler(v22bis_state_t *s, modem_rx_status_func_t handler, void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v27ter_rx.h b/Libraries/spandsp/spandsp/spandsp/v27ter_rx.h new file mode 100644 index 000000000..bf365df9a --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v27ter_rx.h @@ -0,0 +1,164 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v27ter_rx.h - ITU V.27ter modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v27ter_rx.h,v 1.61 2009/07/09 13:52:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V27TER_RX_H_) +#define _SPANDSP_V27TER_RX_H_ + +/*! \page v27ter_rx_page The V.27ter receiver + +\section v27ter_rx_page_sec_1 What does it do? +The V.27ter receiver implements the receive side of a V.27ter modem. This can operate +at data rates of 4800 and 2400 bits/s. The audio input is a stream of 16 bit samples, +at 8000 samples/second. The transmit and receive side of V.27ter modems operate +independantly. V.27ter is mostly used for FAX transmission, where it provides the +standard 4800 bits/s rate (the 2400 bits/s mode is not used for FAX). + +\section v27ter_rx_page_sec_2 How does it work? +V.27ter defines two modes of operation. One uses 8-PSK at 1600 baud, giving 4800bps. +The other uses 4-PSK at 1200 baud, giving 2400bps. A training sequence is specified +at the start of transmission, which makes the design of a V.27ter receiver relatively +straightforward. +*/ + +/*! + V.27ter modem receive side descriptor. This defines the working state for a + single instance of a V.27ter modem receiver. +*/ +typedef struct v27ter_rx_state_s v27ter_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a V.27ter modem receive context. + \brief Initialise a V.27ter modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 2400 and 4800. + \param put_bit The callback routine used to put the received data. + \param user_data An opaque pointer passed to the put_bit routine. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v27ter_rx_state_t *) v27ter_rx_init(v27ter_rx_state_t *s, int bit_rate, put_bit_func_t put_bit, void *user_data); + +/*! Reinitialise an existing V.27ter modem receive context. + \brief Reinitialise an existing V.27ter modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 2400 and 4800. + \param old_train TRUE if a previous trained values are to be reused. + \return 0 for OK, -1 for bad parameter */ +SPAN_DECLARE(int) v27ter_rx_restart(v27ter_rx_state_t *s, int bit_rate, int old_train); + +/*! Release a V.27ter modem receive context. + \brief Release a V.27ter modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v27ter_rx_release(v27ter_rx_state_t *s); + +/*! Free a V.27ter modem receive context. + \brief Free a V.27ter modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v27ter_rx_free(v27ter_rx_state_t *s); + +/*! Get the logging context associated with a V.27ter modem receive context. + \brief Get the logging context associated with a V.27ter modem receive context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v27ter_rx_get_logging_state(v27ter_rx_state_t *s); + +/*! Change the put_bit function associated with a V.27ter modem receive context. + \brief Change the put_bit function associated with a V.27ter modem receive context. + \param s The modem context. + \param put_bit The callback routine used to handle received bits. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v27ter_rx_set_put_bit(v27ter_rx_state_t *s, put_bit_func_t put_bit, void *user_data); + +/*! Change the modem status report function associated with a V.27ter modem receive context. + \brief Change the modem status report function associated with a V.27ter modem receive context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v27ter_rx_set_modem_status_handler(v27ter_rx_state_t *s, modem_rx_status_func_t handler, void *user_data); + +/*! Process a block of received V.27ter modem audio samples. + \brief Process a block of received V.27ter modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE_NONSTD(int) v27ter_rx(v27ter_rx_state_t *s, const int16_t amp[], int len); + +/*! Fake processing of a missing block of received V.27ter modem audio samples. + (e.g due to packet loss). + \brief Fake processing of a missing block of received V.27ter modem audio samples. + \param s The modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. +*/ +SPAN_DECLARE(int) v27ter_rx_fillin(v27ter_rx_state_t *s, int len); + +/*! Get a snapshot of the current equalizer coefficients. + \brief Get a snapshot of the current equalizer coefficients. + \param coeffs The vector of complex coefficients. + \return The number of coefficients in the vector. */ +SPAN_DECLARE(int) v27ter_rx_equalizer_state(v27ter_rx_state_t *s, complexf_t **coeffs); + +/*! Get the current received carrier frequency. + \param s The modem context. + \return The frequency, in Hertz. */ +SPAN_DECLARE(float) v27ter_rx_carrier_frequency(v27ter_rx_state_t *s); + +/*! Get the current symbol timing correction since startup. + \param s The modem context. + \return The correction. */ +SPAN_DECLARE(float) v27ter_rx_symbol_timing_correction(v27ter_rx_state_t *s); + +/*! Get a current received signal power. + \param s The modem context. + \return The signal power, in dBm0. */ +SPAN_DECLARE(float) v27ter_rx_signal_power(v27ter_rx_state_t *s); + +/*! Set the power level at which the carrier detection will cut in + \param s The modem context. + \param cutoff The signal cutoff power, in dBm0. */ +SPAN_DECLARE(void) v27ter_rx_signal_cutoff(v27ter_rx_state_t *s, float cutoff); + +/*! Set a handler routine to process QAM status reports + \param s The modem context. + \param handler The handler routine. + \param user_data An opaque pointer passed to the handler routine. */ +SPAN_DECLARE(void) v27ter_rx_set_qam_report_handler(v27ter_rx_state_t *s, qam_report_handler_t handler, void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v27ter_tx.h b/Libraries/spandsp/spandsp/spandsp/v27ter_tx.h new file mode 100644 index 000000000..0d332e347 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v27ter_tx.h @@ -0,0 +1,148 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v27ter_tx.h - ITU V.27ter modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v27ter_tx.h,v 1.43 2009/07/09 13:52:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V27TER_TX_H_) +#define _SPANDSP_V27TER_TX_H_ + +/*! \page v27ter_tx_page The V.27ter transmitter +\section v27ter_tx_page_sec_1 What does it do? +The V.27ter transmitter implements the transmit side of a V.27ter modem. This +can operate at data rates of 4800 and 2400 bits/s. The audio output is a stream +of 16 bit samples, at 8000 samples/second. The transmit and receive side of +V.27ter modems operate independantly. V.27ter is used for FAX transmission, +where it provides the standard 4800 and 2400 bits/s rates. + +\section v27ter_tx_page_sec_2 How does it work? +V.27ter uses DPSK modulation. A common method of producing a DPSK modulated +signal is to use a sampling rate which is a multiple of the baud rate. The raw +signal is then a series of complex pulses, each an integer number of samples +long. These can be shaped, using a suitable complex filter, and multiplied by a +complex carrier signal to produce the final DPSK signal for transmission. + +The pulse shaping filter for V.27ter is defined in the spec. It is a root raised +cosine filter with 50% excess bandwidth. + +The sampling rate for our transmitter is defined by the channel - 8000 samples/s. +This is a multiple of the baud rate at 4800 bits/s (8-PSK at 1600 baud, 5 samples per +symbol), but not at 2400 bits/s (4-PSK at 1200 baud, 20/3 samples per symbol). The baud +interval is actually 20/3 sample periods at 2400bis/s. A symmetric FIR is used to +apply root raised cosine filtering in the 4800bits/s mode. In the 2400bits/s mode +a polyphase FIR filter is used. This consists of 20 sets of coefficients, offering +zero to 19/20ths of a baud phase shift as well as root raised cosine filtering. +The appropriate coefficient set is chosen for each signal sample generated. + +The carrier is generated using the DDS method. Using 2 second order resonators, +started in quadrature, might be more efficient, as it would have less impact on +the processor cache than a table lookup approach. However, the DDS approach +suits the receiver better, so then same signal generator is also used for the +transmitter. +*/ + +/*! + V.27ter modem transmit side descriptor. This defines the working state for a + single instance of a V.27ter modem transmitter. +*/ +typedef struct v27ter_tx_state_s v27ter_tx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Adjust a V.27ter modem transmit context's power output. + \brief Adjust a V.27ter modem transmit context's output power. + \param s The modem context. + \param power The power level, in dBm0 */ +SPAN_DECLARE(void) v27ter_tx_power(v27ter_tx_state_t *s, float power); + +/*! Initialise a V.27ter modem transmit context. + \brief Initialise a V.27ter modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 2400 and 4800. + \param tep TRUE is the optional TEP tone is to be transmitted. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v27ter_tx_state_t *) v27ter_tx_init(v27ter_tx_state_t *s, int bit_rate, int tep, get_bit_func_t get_bit, void *user_data); + +/*! Reinitialise an existing V.27ter modem transmit context, so it may be reused. + \brief Reinitialise an existing V.27ter modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 2400 and 4800. + \param tep TRUE is the optional TEP tone is to be transmitted. + \return 0 for OK, -1 for bad parameter */ +SPAN_DECLARE(int) v27ter_tx_restart(v27ter_tx_state_t *s, int bit_rate, int tep); + +/*! Release a V.27ter modem transmit context. + \brief Release a V.27ter modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v27ter_tx_release(v27ter_tx_state_t *s); + +/*! Free a V.27ter modem transmit context. + \brief Free a V.27ter modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v27ter_tx_free(v27ter_tx_state_t *s); + +/*! Get the logging context associated with a V.27ter modem transmit context. + \brief Get the logging context associated with a V.27ter modem transmit context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v27ter_tx_get_logging_state(v27ter_tx_state_t *s); + +/*! Change the get_bit function associated with a V.27ter modem transmit context. + \brief Change the get_bit function associated with a V.27ter modem transmit context. + \param s The modem context. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v27ter_tx_set_get_bit(v27ter_tx_state_t *s, get_bit_func_t get_bit, void *user_data); + +/*! Change the modem status report function associated with a V.27ter modem transmit context. + \brief Change the modem status report function associated with a V.27ter modem transmit context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v27ter_tx_set_modem_status_handler(v27ter_tx_state_t *s, modem_tx_status_func_t handler, void *user_data); + +/*! Generate a block of V.27ter modem audio samples. + \brief Generate a block of V.27ter modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) v27ter_tx(v27ter_tx_state_t *s, int16_t amp[], int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v29rx.h b/Libraries/spandsp/spandsp/spandsp/v29rx.h new file mode 100644 index 000000000..7ee67b763 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v29rx.h @@ -0,0 +1,243 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v29rx.h - ITU V.29 modem receive part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v29rx.h,v 1.72 2009/07/09 13:52:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V29RX_H_) +#define _SPANDSP_V29RX_H_ + +/*! \page v29rx_page The V.29 receiver +\section v29rx_page_sec_1 What does it do? +The V.29 receiver implements the receive side of a V.29 modem. This can operate +at data rates of 9600, 7200 and 4800 bits/s. The audio input is a stream of 16 +bit samples, at 8000 samples/second. The transmit and receive side of V.29 +modems operate independantly. V.29 is mostly used for FAX transmission, where it +provides the standard 9600 and 7200 bits/s rates (the 4800 bits/s mode is not +used for FAX). + +\section v29rx_page_sec_2 How does it work? +V.29 operates at 2400 baud for all three bit rates. It uses 16-QAM modulation for +9600bps, 8-QAM for 7200bps, and 4-PSK for 4800bps. A training sequence is specified +at the start of transmission, which makes the design of a V.29 receiver relatively +straightforward. + +The first stage of the training sequence consists of 128 +symbols, alternating between two constellation positions. The receiver monitors +the signal power, to sense the possible presence of a valid carrier. When the +alternating signal begins, the power rising above a minimum threshold (-26dBm0) +causes the main receiver computation to begin. The initial measured power is +used to quickly set the gain of the receiver. After this initial settling, the +front end gain is locked, and the adaptive equalizer tracks any subsequent +signal level variation. The signal is oversampled to 24000 samples/second (i.e. +signal, zero, zero, signal, zero, zero, ...) and fed to a complex root raised +cosine pulse shaping filter. This filter has been modified from the conventional +root raised cosine filter, by shifting it up the band, to be centred at the nominal +carrier frequency. This filter interpolates the samples, pulse shapes, and performs +a fractional sample delay at the same time. 48 sets of filter coefficients are used to +achieve a set of finely spaces fractional sample delays, between zero and +one sample. By choosing every fifth sample, and the appropriate set of filter +coefficients, the properly tuned symbol tracker can select data samples at 4800 +samples/second from points within 1.125 degrees of the centre and mid-points of +each symbol. The output of the filter is multiplied by a complex carrier, generated +by a DDS. The result is a baseband signal, requiring no further filtering, apart from +an adaptive equalizer. The baseband signal is fed to a T/2 adaptive equalizer. +A band edge component maximisation algorithm is used to tune the sampling, so the samples +fed to the equalizer are close to the mid point and edges of each symbol. Initially +the algorithm is very lightly damped, to ensure the symbol alignment pulls in +quickly. Because the sampling rate will not be precisely the same as the +transmitter's (the spec. says the symbol timing should be within 0.01%), the +receiver constantly evaluates and corrects this sampling throughout its +operation. During the symbol timing maintainence phase, the algorithm uses +a heavier damping. + +The carrier is specified as 1700Hz +-1Hz at the transmitter, and 1700 +-7Hz at +the receiver. The receive carrier would only be this inaccurate if the link +includes FDM sections. These are being phased out, but the design must still +allow for the worst case. Using an initial 1700Hz signal for demodulation gives +a worst case rotation rate for the constellation of about one degree per symbol. +Once the symbol timing synchronisation algorithm has been given time to lock to +the symbol timing of the initial alternating pattern, the phase of the demodulated +signal is recorded on two successive symbols - once for each of the constellation +positions. The receiver then tracks the symbol alternations, until a large phase jump +occurs. This signifies the start of the next phase of the training sequence. At this +point the total phase shift between the original recorded symbol phase, and the +symbol phase just before the phase jump occurred is used to provide a coarse +estimation of the rotation rate of the constellation, and it current absolute +angle of rotation. These are used to update the current carrier phase and phase +update rate in the carrier DDS. The working data already in the pulse shaping +filter and equalizer buffers is given a similar step rotation to pull it all +into line. From this point on, a heavily damped integrate and dump approach, +based on the angular difference between each received constellation position and +its expected position, is sufficient to track the carrier, and maintain phase +alignment. A fast rough approximator for the arc-tangent function is adequate +for the estimation of the angular error. + +The next phase of the training sequence is a scrambled sequence of two +particular symbols. We train the T/2 adaptive equalizer using this sequence. The +scrambling makes the signal sufficiently diverse to ensure the equalizer +converges to the proper generalised solution. At the end of this sequence, the +equalizer should be sufficiently well adapted that is can correctly resolve the +full QAM constellation. However, the equalizer continues to adapt throughout +operation of the modem, fine tuning on the more complex data patterns of the +full QAM constellation. + +In the last phase of the training sequence, the modem enters normal data +operation, with a short defined period of all ones as data. As in most high +speed modems, data in a V.29 modem passes through a scrambler, to whiten the +spectrum of the signal. The transmitter should initialise its data scrambler, +and pass the ones through it. At the end of the ones, real data begins to pass +through the scrambler, and the transmit modem is in normal operation. The +receiver tests that ones are really received, in order to verify the modem +trained correctly. If all is well, the data following the ones is fed to the +application, and the receive modem is up and running. Unfortunately, some +transmit side of some real V.29 modems fail to initialise their scrambler before +sending the ones. This means the first 23 received bits (the length of the +scrambler register) cannot be trusted for the test. The receive modem, +therefore, only tests that bits starting at bit 24 are really ones. +*/ + +typedef void (*qam_report_handler_t)(void *user_data, const complexf_t *constel, const complexf_t *target, int symbol); + +/*! + V.29 modem receive side descriptor. This defines the working state for a + single instance of a V.29 modem receiver. +*/ +typedef struct v29_rx_state_s v29_rx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Initialise a V.29 modem receive context. + \brief Initialise a V.29 modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 4800, 7200 and 9600. + \param put_bit The callback routine used to put the received data. + \param user_data An opaque pointer passed to the put_bit routine. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v29_rx_state_t *) v29_rx_init(v29_rx_state_t *s, int bit_rate, put_bit_func_t put_bit, void *user_data); + +/*! Reinitialise an existing V.29 modem receive context. + \brief Reinitialise an existing V.29 modem receive context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 4800, 7200 and 9600. + \param old_train TRUE if a previous trained values are to be reused. + \return 0 for OK, -1 for bad parameter */ +SPAN_DECLARE(int) v29_rx_restart(v29_rx_state_t *s, int bit_rate, int old_train); + +/*! Release a V.29 modem receive context. + \brief Release a V.29 modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v29_rx_release(v29_rx_state_t *s); + +/*! Free a V.29 modem receive context. + \brief Free a V.29 modem receive context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v29_rx_free(v29_rx_state_t *s); + +/*! Get the logging context associated with a V.29 modem receive context. + \brief Get the logging context associated with a V.29 modem receive context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v29_rx_get_logging_state(v29_rx_state_t *s); + +/*! Change the put_bit function associated with a V.29 modem receive context. + \brief Change the put_bit function associated with a V.29 modem receive context. + \param s The modem context. + \param put_bit The callback routine used to handle received bits. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v29_rx_set_put_bit(v29_rx_state_t *s, put_bit_func_t put_bit, void *user_data); + +/*! Change the modem status report function associated with a V.29 modem receive context. + \brief Change the modem status report function associated with a V.29 modem receive context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v29_rx_set_modem_status_handler(v29_rx_state_t *s, modem_rx_status_func_t handler, void *user_data); + +/*! Process a block of received V.29 modem audio samples. + \brief Process a block of received V.29 modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. + \return The number of samples unprocessed. */ +SPAN_DECLARE_NONSTD(int) v29_rx(v29_rx_state_t *s, const int16_t amp[], int len); + +/*! Fake processing of a missing block of received V.29 modem audio samples. + (e.g due to packet loss). + \brief Fake processing of a missing block of received V.29 modem audio samples. + \param s The modem context. + \param len The number of samples to fake. + \return The number of samples unprocessed. */ +SPAN_DECLARE(int) v29_rx_fillin(v29_rx_state_t *s, int len); + +/*! Get a snapshot of the current equalizer coefficients. + \brief Get a snapshot of the current equalizer coefficients. + \param s The modem context. + \param coeffs The vector of complex coefficients. + \return The number of coefficients in the vector. */ +#if defined(SPANDSP_USE_FIXED_POINT) +SPAN_DECLARE(int) v29_rx_equalizer_state(v29_rx_state_t *s, complexi16_t **coeffs); +#else +SPAN_DECLARE(int) v29_rx_equalizer_state(v29_rx_state_t *s, complexf_t **coeffs); +#endif + +/*! Get the current received carrier frequency. + \param s The modem context. + \return The frequency, in Hertz. */ +SPAN_DECLARE(float) v29_rx_carrier_frequency(v29_rx_state_t *s); + +/*! Get the current symbol timing correction since startup. + \param s The modem context. + \return The correction. */ +SPAN_DECLARE(float) v29_rx_symbol_timing_correction(v29_rx_state_t *s); + +/*! Get the current received signal power. + \param s The modem context. + \return The signal power, in dBm0. */ +SPAN_DECLARE(float) v29_rx_signal_power(v29_rx_state_t *s); + +/*! Set the power level at which the carrier detection will cut in + \param s The modem context. + \param cutoff The signal cutoff power, in dBm0. */ +SPAN_DECLARE(void) v29_rx_signal_cutoff(v29_rx_state_t *s, float cutoff); + +/*! Set a handler routine to process QAM status reports + \param s The modem context. + \param handler The handler routine. + \param user_data An opaque pointer passed to the handler routine. */ +SPAN_DECLARE(void) v29_rx_set_qam_report_handler(v29_rx_state_t *s, qam_report_handler_t handler, void *user_data); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v29tx.h b/Libraries/spandsp/spandsp/spandsp/v29tx.h new file mode 100644 index 000000000..522eee7e4 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v29tx.h @@ -0,0 +1,179 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v29tx.h - ITU V.29 modem transmit part + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v29tx.h,v 1.41 2009/07/09 13:52:09 steveu Exp $ + */ + +/*! \file */ + +#if !defined(_SPANDSP_V29TX_H_) +#define _SPANDSP_V29TX_H_ + +/*! \page v29tx_page The V.29 transmitter +\section v29tx_page_sec_1 What does it do? +The V.29 transmitter implements the transmit side of a V.29 modem. This can +operate at data rates of 9600, 7200 and 4800 bits/s. The audio output is a +stream of 16 bit samples, at 8000 samples/second. The transmit and receive side +of V.29 modems operate independantly. V.29 is mostly used for FAX transmission, +where it provides the standard 9600 and 7200 bits/s rates (the 4800 bits/s mode +is not used for FAX). + +\section v29tx_page_sec_2 How does it work? +V.29 uses QAM modulation. The standard method of producing a QAM modulated +signal is to use a sampling rate which is a multiple of the baud rate. The raw +signal is then a series of complex pulses, each an integer number of samples +long. These can be shaped, using a suitable complex filter, and multiplied by a +complex carrier signal to produce the final QAM signal for transmission. + +The pulse shaping filter is only vaguely defined by the V.29 spec. Some of the +other ITU modem specs. fully define the filter, typically specifying a root +raised cosine filter, with 50% excess bandwidth. This is a pity, since it +increases the variability of the received signal. However, the receiver's +adaptive equalizer will compensate for these differences. The current +design uses a root raised cosine filter with 25% excess bandwidth. Greater +excess bandwidth will not allow the tranmitted signal to meet the spectral +requirements. + +The sampling rate for our transmitter is defined by the channel - 8000 per +second. This is not a multiple of the baud rate (i.e. 2400 baud). The baud +interval is actually 10/3 sample periods. Instead of using a symmetric +FIR to pulse shape the signal, a polyphase filter is used. This consists of +10 sets of coefficients, offering zero to 9/10ths of a baud phase shift as well +as root raised cosine filtering. The appropriate coefficient set is chosen for +each signal sample generated. + +The carrier is generated using the DDS method. Using two second order resonators, +started in quadrature, might be more efficient, as it would have less impact on +the processor cache than a table lookup approach. However, the DDS approach +suits the receiver better, so the same signal generator is also used for the +transmitter. + +The equation defining QAM modulation is: + + s(n) = A*cos(2*pi*f*n + phi(n)) + +where phi(n) is the phase of the information, and A is the amplitude of the information + +using the identity + + cos(x + y) = cos(x)*cos(y) - sin(x)*sin(y) + +we get + + s(n) = A {cos(2*pi*f*n)*cos(phi(n)) - sin(2*pi*f*n)*sin(phi(n))} + +substituting with the constellation positions + + I(n) = A*cos(phi(n)) + Q(n) = A*sin(phi(n)) + +gives + + s(n) = I(n)*cos(2*pi*f*n) - Q(n)*sin(2*pi*f*n) + +*/ + +/*! + V.29 modem transmit side descriptor. This defines the working state for a + single instance of a V.29 modem transmitter. +*/ +typedef struct v29_tx_state_s v29_tx_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Adjust a V.29 modem transmit context's power output. + \brief Adjust a V.29 modem transmit context's output power. + \param s The modem context. + \param power The power level, in dBm0 */ +SPAN_DECLARE(void) v29_tx_power(v29_tx_state_t *s, float power); + +/*! Initialise a V.29 modem transmit context. This must be called before the first + use of the context, to initialise its contents. + \brief Initialise a V.29 modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 4800, 7200 and 9600. + \param tep TRUE is the optional TEP tone is to be transmitted. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. + \return A pointer to the modem context, or NULL if there was a problem. */ +SPAN_DECLARE(v29_tx_state_t *) v29_tx_init(v29_tx_state_t *s, int bit_rate, int tep, get_bit_func_t get_bit, void *user_data); + +/*! Reinitialise an existing V.29 modem transmit context, so it may be reused. + \brief Reinitialise an existing V.29 modem transmit context. + \param s The modem context. + \param bit_rate The bit rate of the modem. Valid values are 4800, 7200 and 9600. + \param tep TRUE is the optional TEP tone is to be transmitted. + \return 0 for OK, -1 for bad parameter */ +SPAN_DECLARE(int) v29_tx_restart(v29_tx_state_t *s, int bit_rate, int tep); + +/*! Release a V.29 modem transmit context. + \brief Release a V.29 modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v29_tx_release(v29_tx_state_t *s); + +/*! Free a V.29 modem transmit context. + \brief Free a V.29 modem transmit context. + \param s The modem context. + \return 0 for OK */ +SPAN_DECLARE(int) v29_tx_free(v29_tx_state_t *s); + +/*! Get the logging context associated with a V.29 modem transmit context. + \brief Get the logging context associated with a V.29 modem transmit context. + \param s The modem context. + \return A pointer to the logging context */ +SPAN_DECLARE(logging_state_t *) v29_tx_get_logging_state(v29_tx_state_t *s); + +/*! Change the get_bit function associated with a V.29 modem transmit context. + \brief Change the get_bit function associated with a V.29 modem transmit context. + \param s The modem context. + \param get_bit The callback routine used to get the data to be transmitted. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v29_tx_set_get_bit(v29_tx_state_t *s, get_bit_func_t get_bit, void *user_data); + +/*! Change the modem status report function associated with a V.29 modem transmit context. + \brief Change the modem status report function associated with a V.29 modem transmit context. + \param s The modem context. + \param handler The callback routine used to report modem status changes. + \param user_data An opaque pointer. */ +SPAN_DECLARE(void) v29_tx_set_modem_status_handler(v29_tx_state_t *s, modem_tx_status_func_t handler, void *user_data); + +/*! Generate a block of V.29 modem audio samples. + \brief Generate a block of V.29 modem audio samples. + \param s The modem context. + \param amp The audio sample buffer. + \param len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) v29_tx(v29_tx_state_t *s, int16_t amp[], int len); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v42.h b/Libraries/spandsp/spandsp/spandsp/v42.h new file mode 100644 index 000000000..bba70af5b --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v42.h @@ -0,0 +1,160 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v42.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v42.h,v 1.31 2009/11/04 15:52:06 steveu Exp $ + */ + +/*! \page v42_page V.42 modem error correction +\section v42_page_sec_1 What does it do? +The V.42 specification defines an error correcting protocol for PSTN modems, based on +HDLC and LAP. This makes it similar to an X.25 link. A special variant of LAP, known +as LAP-M, is defined in the V.42 specification. A means for modems to determine if the +far modem supports V.42 is also defined. + +\section v42_page_sec_2 How does it work? +*/ + +#if !defined(_SPANDSP_V42_H_) +#define _SPANDSP_V42_H_ + +enum +{ + LAPM_DETECT = 0, + LAPM_ESTABLISH = 1, + LAPM_DATA = 2, + LAPM_RELEASE = 3, + LAPM_SIGNAL = 4, + LAPM_SETPARM = 5, + LAPM_TEST = 6, + LAPM_UNSUPPORTED = 7 +}; + +typedef void (*v42_status_func_t)(void *user_data, int status); +typedef void (*v42_frame_handler_t)(void *user_data, const uint8_t *pkt, int len); + +typedef struct lapm_frame_queue_s +{ + struct lapm_frame_queue_s *next; + int len; + uint8_t frame[]; +} lapm_frame_queue_t; + +/*! + LAP-M descriptor. This defines the working state for a single instance of LAP-M. +*/ +typedef struct lapm_state_s lapm_state_t; + +/*! + V.42 descriptor. This defines the working state for a single instance of V.42. +*/ +typedef struct v42_state_s v42_state_t; + +/*! Log the raw HDLC frames */ +#define LAPM_DEBUG_LAPM_RAW (1 << 0) +/*! Log the interpreted frames */ +#define LAPM_DEBUG_LAPM_DUMP (1 << 1) +/*! Log state machine changes */ +#define LAPM_DEBUG_LAPM_STATE (1 << 2) + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(const char *) lapm_status_to_str(int status); + +/*! Dump LAP.M frames in a raw and/or decoded forms + \param frame The frame itself + \param len The length of the frame, in octets + \param showraw TRUE if the raw octets should be dumped + \param txrx TRUE if tx, FALSE if rx. Used to highlight the packet's direction. +*/ +SPAN_DECLARE(void) lapm_dump(lapm_state_t *s, const uint8_t *frame, int len, int showraw, int txrx); + +/*! Accept an HDLC packet +*/ +SPAN_DECLARE_NONSTD(void) lapm_receive(void *user_data, const uint8_t *buf, int len, int ok); + +/*! Transmit a LAP.M frame +*/ +SPAN_DECLARE(int) lapm_tx(lapm_state_t *s, const void *buf, int len); + +/*! Transmit a LAP.M information frame +*/ +SPAN_DECLARE(int) lapm_tx_iframe(lapm_state_t *s, const void *buf, int len, int cr); + +/*! Send a break over a LAP.M connection +*/ +SPAN_DECLARE(int) lapm_break(lapm_state_t *s, int enable); + +/*! Initiate an orderly release of a LAP.M connection +*/ +SPAN_DECLARE(int) lapm_release(lapm_state_t *s); + +/*! Enable or disable loopback of a LAP.M connection +*/ +SPAN_DECLARE(int) lapm_loopback(lapm_state_t *s, int enable); + +/*! Assign or remove a callback routine used to deal with V.42 status changes. +*/ +SPAN_DECLARE(void) v42_set_status_callback(v42_state_t *s, v42_status_func_t callback, void *user_data); + +/*! Process a newly received bit for a V.42 context. +*/ +SPAN_DECLARE(void) v42_rx_bit(void *user_data, int bit); + +/*! Get the next transmit bit for a V.42 context. +*/ +SPAN_DECLARE(int) v42_tx_bit(void *user_data); + +/*! Initialise a V.42 context. + \param s The V.42 context. + \param calling_party TRUE if caller mode, else answerer mode. + \param frame_handler A callback function to handle received frames of data. + \param user_data An opaque pointer passed to the frame handler routine. + \return ??? +*/ +SPAN_DECLARE(v42_state_t *) v42_init(v42_state_t *s, int calling_party, int detect, v42_frame_handler_t frame_handler, void *user_data); + +/*! Restart a V.42 context. + \param s The V.42 context. +*/ +SPAN_DECLARE(void) v42_restart(v42_state_t *s); + +/*! Release a V.42 context. + \param s The V.42 context. + \return 0 if OK */ +SPAN_DECLARE(int) v42_release(v42_state_t *s); + +/*! Free a V.42 context. + \param s The V.42 context. + \return 0 if OK */ +SPAN_DECLARE(int) v42_free(v42_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v42bis.h b/Libraries/spandsp/spandsp/spandsp/v42bis.h new file mode 100644 index 000000000..f13e5c5ac --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v42bis.h @@ -0,0 +1,143 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v42bis.h + * + * Written by Steve Underwood + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v42bis.h,v 1.27 2009/04/11 18:11:19 steveu Exp $ + */ + +/*! \page v42bis_page V.42bis modem data compression +\section v42bis_page_sec_1 What does it do? +The v.42bis specification defines a data compression scheme, to work in +conjunction with the error correction scheme defined in V.42. + +\section v42bis_page_sec_2 How does it work? +*/ + +#if !defined(_SPANDSP_V42BIS_H_) +#define _SPANDSP_V42BIS_H_ + +#define V42BIS_MAX_BITS 12 +#define V42BIS_MAX_CODEWORDS 4096 /* 2^V42BIS_MAX_BITS */ +#define V42BIS_TABLE_SIZE 5021 /* This should be a prime >(2^V42BIS_MAX_BITS) */ +#define V42BIS_MAX_STRING_SIZE 250 + +enum +{ + V42BIS_P0_NEITHER_DIRECTION = 0, + V42BIS_P0_INITIATOR_RESPONDER, + V42BIS_P0_RESPONDER_INITIATOR, + V42BIS_P0_BOTH_DIRECTIONS +}; + +enum +{ + V42BIS_COMPRESSION_MODE_DYNAMIC = 0, + V42BIS_COMPRESSION_MODE_ALWAYS, + V42BIS_COMPRESSION_MODE_NEVER +}; + +typedef void (*v42bis_frame_handler_t)(void *user_data, const uint8_t *pkt, int len); +typedef void (*v42bis_data_handler_t)(void *user_data, const uint8_t *buf, int len); + +/*! + V.42bis compression/decompression descriptor. This defines the working state for a + single instance of V.42bis compress/decompression. +*/ +typedef struct v42bis_state_s v42bis_state_t; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/*! Compress a block of octets. + \param s The V.42bis context. + \param buf The data to be compressed. + \param len The length of the data buffer. + \return 0 */ +SPAN_DECLARE(int) v42bis_compress(v42bis_state_t *s, const uint8_t *buf, int len); + +/*! Flush out any data remaining in a compression buffer. + \param s The V.42bis context. + \return 0 */ +SPAN_DECLARE(int) v42bis_compress_flush(v42bis_state_t *s); + +/*! Decompress a block of octets. + \param s The V.42bis context. + \param buf The data to be decompressed. + \param len The length of the data buffer. + \return 0 */ +SPAN_DECLARE(int) v42bis_decompress(v42bis_state_t *s, const uint8_t *buf, int len); + +/*! Flush out any data remaining in the decompression buffer. + \param s The V.42bis context. + \return 0 */ +SPAN_DECLARE(int) v42bis_decompress_flush(v42bis_state_t *s); + +/*! Set the compression mode. + \param s The V.42bis context. + \param mode One of the V.42bis compression modes - + V42BIS_COMPRESSION_MODE_DYNAMIC, + V42BIS_COMPRESSION_MODE_ALWAYS, + V42BIS_COMPRESSION_MODE_NEVER */ +SPAN_DECLARE(void) v42bis_compression_control(v42bis_state_t *s, int mode); + +/*! Initialise a V.42bis context. + \param s The V.42bis context. + \param negotiated_p0 The negotiated P0 parameter, from the V.42bis spec. + \param negotiated_p1 The negotiated P1 parameter, from the V.42bis spec. + \param negotiated_p2 The negotiated P2 parameter, from the V.42bis spec. + \param frame_handler Frame callback handler. + \param frame_user_data An opaque pointer passed to the frame callback handler. + \param max_frame_len The maximum length that should be passed to the frame handler. + \param data_handler data callback handler. + \param data_user_data An opaque pointer passed to the data callback handler. + \param max_data_len The maximum length that should be passed to the data handler. + \return The V.42bis context. */ +SPAN_DECLARE(v42bis_state_t *) v42bis_init(v42bis_state_t *s, + int negotiated_p0, + int negotiated_p1, + int negotiated_p2, + v42bis_frame_handler_t frame_handler, + void *frame_user_data, + int max_frame_len, + v42bis_data_handler_t data_handler, + void *data_user_data, + int max_data_len); + +/*! Release a V.42bis context. + \param s The V.42bis context. + \return 0 if OK */ +SPAN_DECLARE(int) v42bis_release(v42bis_state_t *s); + +/*! Free a V.42bis context. + \param s The V.42bis context. + \return 0 if OK */ +SPAN_DECLARE(int) v42bis_free(v42bis_state_t *s); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/v8.h b/Libraries/spandsp/spandsp/spandsp/v8.h new file mode 100644 index 000000000..a8bd2e4d4 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/v8.h @@ -0,0 +1,190 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v8.h - V.8 modem negotiation processing. + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v8.h,v 1.31.4.1 2009/12/28 12:20:47 steveu Exp $ + */ + +/*! \file */ + +/*! \page v8_page The V.8 modem negotiation protocol +\section v8_page_sec_1 What does it do? +The V.8 specification defines a procedure to be used as PSTN modem answer phone calls, +which allows the modems to negotiate the optimum modem standard, which both ends can +support. + +\section v8_page_sec_2 How does it work? +At startup the modems communicate using the V.21 standard at 300 bits/second. They +exchange simple messages about their capabilities, and choose the modem standard they +will use for data communication. The V.8 protocol then terminates, and the modems +being negotiating and training with their chosen modem standard. +*/ + +#if !defined(_SPANDSP_V8_H_) +#define _SPANDSP_V8_H_ + +typedef struct v8_parms_s v8_parms_t; + +typedef void (v8_result_handler_t)(void *user_data, v8_parms_t *result); + +enum v8_call_function_e +{ + V8_CALL_TBS = 0, + V8_CALL_H324 = 1, + V8_CALL_V18 = 2, + V8_CALL_T101 = 3, + V8_CALL_T30_TX = 4, + V8_CALL_T30_RX = 5, + V8_CALL_V_SERIES = 6, + V8_CALL_FUNCTION_EXTENSION = 7 +}; + +enum v8_modulation_e +{ + V8_MOD_V17 = (1 << 0), /* V.17 half-duplex */ + V8_MOD_V21 = (1 << 1), /* V.21 duplex */ + V8_MOD_V22 = (1 << 2), /* V.22/V22.bis duplex */ + V8_MOD_V23HALF = (1 << 3), /* V.23 half-duplex */ + V8_MOD_V23 = (1 << 4), /* V.23 duplex */ + V8_MOD_V26BIS = (1 << 5), /* V.23 duplex */ + V8_MOD_V26TER = (1 << 6), /* V.23 duplex */ + V8_MOD_V27TER = (1 << 7), /* V.23 duplex */ + V8_MOD_V29 = (1 << 8), /* V.29 half-duplex */ + V8_MOD_V32 = (1 << 9), /* V.32/V32.bis duplex */ + V8_MOD_V34HALF = (1 << 10), /* V.34 half-duplex */ + V8_MOD_V34 = (1 << 11), /* V.34 duplex */ + V8_MOD_V90 = (1 << 12), /* V.90 duplex */ + V8_MOD_V92 = (1 << 13), /* V.92 duplex */ + + V8_MOD_FAILED = (1 << 15) /* Indicates failure to negotiate */ +}; + +enum v8_protocol_e +{ + V8_PROTOCOL_NONE = 0, + V8_PROTOCOL_LAPM_V42 = 1, + V8_PROTOCOL_EXTENSION = 7 +}; + +enum v8_pstn_access_e +{ + V8_PSTN_ACCESS_CALL_DCE_CELLULAR = 0x01, + V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR = 0x02, + V8_PSTN_ACCESS_DCE_ON_DIGITAL = 0x04 +}; + +enum v8_pcm_modem_availability_e +{ + V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE = 0x01, + V8_PSTN_PCM_MODEM_V90_V92_DIGITAL = 0x02, + V8_PSTN_PCM_MODEM_V91 = 0x04 +}; + +typedef struct v8_state_s v8_state_t; + +struct v8_parms_s +{ + int modem_connect_tone; + int call_function; + unsigned int modulations; + int protocol; + int pstn_access; + int pcm_modem_availability; + int nsf; + int t66; +}; + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(int) v8_restart(v8_state_t *s, + int calling_party, + v8_parms_t *parms); + +/*! Initialise a V.8 context. + \brief Initialise a V.8 context. + \param s The V.8 context. + \param calling_party TRUE if caller mode, else answerer mode. + \param parms The allowed parameters for the call. + \param result_handler The callback routine used to handle the results of negotiation. + \param user_data An opaque pointer passed to the result_handler routine. + \return A pointer to the V.8 context, or NULL if there was a problem. */ +SPAN_DECLARE(v8_state_t *) v8_init(v8_state_t *s, + int calling_party, + v8_parms_t *parms, + v8_result_handler_t *result_handler, + void *user_data); + +/*! Release a V.8 context. + \brief Release a V.8 context. + \param s The V.8 context. + \return 0 for OK. */ +SPAN_DECLARE(int) v8_release(v8_state_t *s); + +/*! Free a V.8 context. + \brief Release a V.8 context. + \param s The V.8 context. + \return 0 for OK. */ +SPAN_DECLARE(int) v8_free(v8_state_t *s); + +SPAN_DECLARE(logging_state_t *) v8_get_logging_state(v8_state_t *s); + +/*! Generate a block of V.8 audio samples. + \brief Generate a block of V.8 audio samples. + \param s The V.8 context. + \param amp The audio sample buffer. + \param max_len The number of samples to be generated. + \return The number of samples actually generated. +*/ +SPAN_DECLARE_NONSTD(int) v8_tx(v8_state_t *s, int16_t *amp, int max_len); + +/*! Process a block of received V.8 audio samples. + \brief Process a block of received V.8 audio samples. + \param s The V.8 context. + \param amp The audio sample buffer. + \param len The number of samples in the buffer. +*/ +SPAN_DECLARE_NONSTD(int) v8_rx(v8_state_t *s, const int16_t *amp, int len); + +/*! Log the list of supported modulations. + \brief Log the list of supported modulations. + \param s The V.8 context. + \param modulation_schemes The list of supported modulations. */ +SPAN_DECLARE(void) v8_log_supported_modulations(v8_state_t *s, int modulation_schemes); + +SPAN_DECLARE(const char *) v8_call_function_to_str(int call_function); +SPAN_DECLARE(const char *) v8_modulation_to_str(int modulation_scheme); +SPAN_DECLARE(const char *) v8_protocol_to_str(int protocol); +SPAN_DECLARE(const char *) v8_pstn_access_to_str(int pstn_access); +SPAN_DECLARE(const char *) v8_nsf_to_str(int nsf); +SPAN_DECLARE(const char *) v8_pcm_modem_availability_to_str(int pcm_modem_availability); +SPAN_DECLARE(const char *) v8_t66_to_str(int t66); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/vector_float.h b/Libraries/spandsp/spandsp/spandsp/vector_float.h new file mode 100644 index 000000000..d9aa6616a --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/vector_float.h @@ -0,0 +1,197 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * vector_float.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: vector_float.h,v 1.15 2009/01/31 08:48:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_VECTOR_FLOAT_H_) +#define _SPANDSP_VECTOR_FLOAT_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +SPAN_DECLARE(void) vec_copyf(float z[], const float x[], int n); + +SPAN_DECLARE(void) vec_copy(double z[], const double x[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_copyl(long double z[], const long double x[], int n); +#endif + +SPAN_DECLARE(void) vec_negatef(float z[], const float x[], int n); + +SPAN_DECLARE(void) vec_negate(double z[], const double x[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_negatel(long double z[], const long double x[], int n); +#endif + +SPAN_DECLARE(void) vec_zerof(float z[], int n); + +SPAN_DECLARE(void) vec_zero(double z[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_zerol(long double z[], int n); +#endif + +SPAN_DECLARE(void) vec_setf(float z[], float x, int n); + +SPAN_DECLARE(void) vec_set(double z[], double x, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_setl(long double z[], long double x, int n); +#endif + +SPAN_DECLARE(void) vec_addf(float z[], const float x[], const float y[], int n); + +SPAN_DECLARE(void) vec_add(double z[], const double x[], const double y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_addl(long double z[], const long double x[], const long double y[], int n); +#endif + +SPAN_DECLARE(void) vec_scaledxy_addf(float z[], const float x[], float x_scale, const float y[], float y_scale, int n); + +SPAN_DECLARE(void) vec_scaledxy_add(double z[], const double x[], double x_scale, const double y[], double y_scale, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scaledxy_addl(long double z[], const long double x[], long double x_scale, const long double y[], long double y_scale, int n); +#endif + +SPAN_DECLARE(void) vec_scaledy_addf(float z[], const float x[], const float y[], float y_scale, int n); + +SPAN_DECLARE(void) vec_scaledy_add(double z[], const double x[], const double y[], double y_scale, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scaledy_addl(long double z[], const long double x[], const long double y[], long double y_scale, int n); +#endif + +SPAN_DECLARE(void) vec_subf(float z[], const float x[], const float y[], int n); + +SPAN_DECLARE(void) vec_sub(double z[], const double x[], const double y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_subl(long double z[], const long double x[], const long double y[], int n); +#endif + +SPAN_DECLARE(void) vec_scaledxy_subf(float z[], const float x[], float x_scale, const float y[], float y_scale, int n); + +SPAN_DECLARE(void) vec_scaledxy_sub(double z[], const double x[], double x_scale, const double y[], double y_scale, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scaledxy_subl(long double z[], const long double x[], long double x_scale, const long double y[], long double y_scale, int n); +#endif + +SPAN_DECLARE(void) vec_scaledx_subf(float z[], const float x[], float x_scale, const float y[], int n); + +SPAN_DECLARE(void) vec_scaledx_sub(double z[], const double x[], double x_scale, const double y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scaledx_subl(long double z[], const long double x[], long double x_scale, const long double y[], int n); +#endif + +SPAN_DECLARE(void) vec_scaledy_subf(float z[], const float x[], const float y[], float y_scale, int n); + +SPAN_DECLARE(void) vec_scaledy_sub(double z[], const double x[], const double y[], double y_scale, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scaledy_subl(long double z[], const long double x[], const long double y[], long double y_scale, int n); +#endif + +SPAN_DECLARE(void) vec_scalar_mulf(float z[], const float x[], float y, int n); + +SPAN_DECLARE(void) vec_scalar_mul(double z[], const double x[], double y, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scalar_mull(long double z[], const long double x[], long double y, int n); +#endif + +SPAN_DECLARE(void) vec_scalar_addf(float z[], const float x[], float y, int n); + +SPAN_DECLARE(void) vec_scalar_add(double z[], const double x[], double y, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scalar_addl(long double z[], const long double x[], long double y, int n); +#endif + +SPAN_DECLARE(void) vec_scalar_subf(float z[], const float x[], float y, int n); + +SPAN_DECLARE(void) vec_scalar_sub(double z[], const double x[], double y, int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_scalar_subl(long double z[], const long double x[], long double y, int n); +#endif + +SPAN_DECLARE(void) vec_mulf(float z[], const float x[], const float y[], int n); + +SPAN_DECLARE(void) vec_mul(double z[], const double x[], const double y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +SPAN_DECLARE(void) vec_mull(long double z[], const long double x[], const long double y[], int n); +#endif + +/*! \brief Find the dot product of two float vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(float) vec_dot_prodf(const float x[], const float y[], int n); + +/*! \brief Find the dot product of two double vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(double) vec_dot_prod(const double x[], const double y[], int n); + +#if defined(HAVE_LONG_DOUBLE) +/*! \brief Find the dot product of two long double vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(long double) vec_dot_prodl(const long double x[], const long double y[], int n); +#endif + +/*! \brief Find the dot product of two float vectors, where the first is a circular buffer + with an offset for the starting position. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \param pos The starting position in the x vector. + \return The dot product of the two vectors. */ +SPAN_DECLARE(float) vec_circular_dot_prodf(const float x[], const float y[], int n, int pos); + +SPAN_DECLARE(void) vec_lmsf(const float x[], float y[], int n, float error); + +SPAN_DECLARE(void) vec_circular_lmsf(const float x[], float y[], int n, int pos, float error); + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/vector_int.h b/Libraries/spandsp/spandsp/spandsp/vector_int.h new file mode 100644 index 000000000..04a632fe1 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/vector_int.h @@ -0,0 +1,180 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * vector_int.h + * + * Written by Steve Underwood + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: vector_int.h,v 1.14 2009/01/31 08:48:11 steveu Exp $ + */ + +#if !defined(_SPANDSP_VECTOR_INT_H_) +#define _SPANDSP_VECTOR_INT_H_ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +static __inline__ void vec_copyi(int z[], const int x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_copyi16(int16_t z[], const int16_t x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_copyi32(int32_t z[], const int32_t x[], int n) +{ + memcpy(z, x, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_zeroi(int z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_zeroi16(int16_t z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_zeroi32(int32_t z[], int n) +{ + memset(z, 0, n*sizeof(z[0])); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_seti(int z[], int x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_seti16(int16_t z[], int16_t x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_seti32(int32_t z[], int32_t x, int n) +{ + int i; + + for (i = 0; i < n; i++) + z[i] = x; +} +/*- End of function --------------------------------------------------------*/ + +/*! \brief Find the dot product of two int16_t vectors. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \return The dot product of the two vectors. */ +SPAN_DECLARE(int32_t) vec_dot_prodi16(const int16_t x[], const int16_t y[], int n); + +/*! \brief Find the dot product of two int16_t vectors, where the first is a circular buffer + with an offset for the starting position. + \param x The first vector. + \param y The first vector. + \param n The number of elements in the vectors. + \param pos The starting position in the x vector. + \return The dot product of the two vectors. */ +SPAN_DECLARE(int32_t) vec_circular_dot_prodi16(const int16_t x[], const int16_t y[], int n, int pos); + +SPAN_DECLARE(void) vec_lmsi16(const int16_t x[], int16_t y[], int n, int16_t error); + +SPAN_DECLARE(void) vec_circular_lmsi16(const int16_t x[], int16_t y[], int n, int pos, int16_t error); + +/*! \brief Find the minimum and maximum values in an int16_t vector. + \param x The vector to be searched. + \param n The number of elements in the vector. + \param out A two element vector. The first will receive the + maximum. The second will receive the minimum. This parameter + may be set to NULL. + \return The absolute maximum value. Since the range of negative numbers + exceeds the range of positive one, the returned integer is longer + than the ones being searched. */ +SPAN_DECLARE(int32_t) vec_min_maxi16(const int16_t x[], int n, int16_t out[]); + +static __inline__ int vec_norm2i16(const int16_t *vec, int len) +{ + int i; + int sum; + + sum = 0; + for (i = 0; i < len; i++) + sum += vec[i]*vec[i]; + return sum; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void vec_sari16(int16_t *vec, int len, int shift) +{ + int i; + + for (i = 0; i < len; i++) + vec[i] >>= shift; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int vec_max_bitsi16(const int16_t *vec, int len) +{ + int i; + int max; + int v; + int b; + + max = 0; + for (i = 0; i < len; i++) + { + v = abs(vec[i]); + if (v > max) + max = v; + } + b = 0; + while (max != 0) + { + b++; + max >>= 1; + } + return b; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(__cplusplus) +} +#endif + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/version.h b/Libraries/spandsp/spandsp/spandsp/version.h new file mode 100644 index 000000000..611d32104 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/version.h @@ -0,0 +1,38 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * version.h - A tag file, so the exact installed revision can be assertained. + * + * Written by Steve Underwood + * + * Copyright (C) 2007 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: version.h.in,v 1.3.4.1 2009/12/19 09:47:56 steveu Exp $ + */ + +#if !defined(_SPANDSP_VERSION_H_) +#define _SPANDSP_VERSION_H_ + +/* The date and time of the version are in UTC form. */ + +#define SPANDSP_RELEASE_DATE 20091228 +#define SPANDSP_RELEASE_TIME 123351 +#define SPANDSP_RELEASE_DATETIME_STRING "20091228 123351" + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/spandsp/version.h.in b/Libraries/spandsp/spandsp/spandsp/version.h.in new file mode 100644 index 000000000..8a547f1e1 --- /dev/null +++ b/Libraries/spandsp/spandsp/spandsp/version.h.in @@ -0,0 +1,38 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * version.h - A tag file, so the exact installed revision can be assertained. + * + * Written by Steve Underwood + * + * Copyright (C) 2007 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: version.h.in,v 1.3.4.1 2009/12/19 09:47:56 steveu Exp $ + */ + +#if !defined(_SPANDSP_VERSION_H_) +#define _SPANDSP_VERSION_H_ + +/* The date and time of the version are in UTC form. */ + +#define SPANDSP_RELEASE_DATE $SPANDSP_RELEASE_DATE +#define SPANDSP_RELEASE_TIME $SPANDSP_RELEASE_TIME +#define SPANDSP_RELEASE_DATETIME_STRING "$SPANDSP_RELEASE_DATE $SPANDSP_RELEASE_TIME" + +#endif +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/spandsp/spandsp/time_scale.c b/Libraries/spandsp/spandsp/time_scale.c new file mode 100644 index 000000000..e1f535d67 --- /dev/null +++ b/Libraries/spandsp/spandsp/time_scale.c @@ -0,0 +1,300 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * time_scale.c - Time scaling for linear speech data + * + * Written by Steve Underwood + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: time_scale.c,v 1.30 2009/02/10 13:06:47 steveu Exp $ + */ + +/*! \file */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_TGMATH_H) +#include +#endif +#if defined(HAVE_MATH_H) +#include +#endif +//#include "floating_fudge.h" + +#include "spandsp/telephony.h" +#include "spandsp/fast_convert.h" +#include "spandsp/time_scale.h" +#include "spandsp/saturated.h" + +#include "spandsp/private/time_scale.h" + +/* + Time scaling for speech, based on the Pointer Interval Controlled + OverLap and Add (PICOLA) method, developed by Morita Naotaka. + */ + +static __inline__ int amdf_pitch(int min_pitch, int max_pitch, int16_t amp[], int len) +{ + int i; + int j; + int acc; + int min_acc; + int pitch; + + pitch = min_pitch; + min_acc = INT_MAX; + for (i = max_pitch; i <= min_pitch; i++) + { + acc = 0; + for (j = 0; j < len; j++) + acc += abs(amp[i + j] - amp[j]); + if (acc < min_acc) + { + min_acc = acc; + pitch = i; + } + } + return pitch; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void overlap_add(int16_t amp1[], int16_t amp2[], int len) +{ + int i; + float weight; + float step; + + step = 1.0f/len; + weight = 0.0f; + for (i = 0; i < len; i++) + { + /* TODO: saturate */ + amp2[i] = (int16_t) ((float) amp1[i]*(1.0f - weight) + (float) amp2[i]*weight); + weight += step; + } +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) time_scale_rate(time_scale_state_t *s, float playout_rate) +{ + if (playout_rate <= 0.0f) + return -1; + /*endif*/ + if (playout_rate >= 0.99f && playout_rate <= 1.01f) + { + /* Treat rate close to normal speed as exactly normal speed, and + avoid divide by zero, and other numerical problems. */ + playout_rate = 1.0f; + s->lcp=0; + } + else if (playout_rate < 1.0f) + { + s->rcomp = playout_rate/(1.0f - playout_rate); + } + else + { + s->rcomp = 1.0f/(playout_rate - 1.0f); + } + /*endif*/ + s->playout_rate = playout_rate; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(time_scale_state_t *) time_scale_init(time_scale_state_t *s, int sample_rate, float playout_rate) +{ + int alloced; + + if (sample_rate > TIME_SCALE_MAX_SAMPLE_RATE) + return NULL; + alloced = FALSE; + if (s == NULL) + { + if ((s = (time_scale_state_t *) malloc(sizeof (*s))) == NULL) + return NULL; + /*endif*/ + alloced = TRUE; + } + /*endif*/ + s->sample_rate = sample_rate; + s->min_pitch = sample_rate/TIME_SCALE_MIN_PITCH; + s->max_pitch = sample_rate/TIME_SCALE_MAX_PITCH; + s->buf_len = 2*sample_rate/TIME_SCALE_MIN_PITCH; + if (time_scale_rate(s, playout_rate)) + { + if (alloced) + free(s); + return NULL; + } + /*endif*/ + s->rate_nudge = 0.0f; + s->fill = 0; + s->lcp = 0; + return s; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) time_scale_release(time_scale_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) time_scale_free(time_scale_state_t *s) +{ + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) time_scale(time_scale_state_t *s, int16_t out[], int16_t in[], int len) +{ + double lcpf; + int pitch; + int out_len; + int in_len; + int k; + + out_len = 0; + in_len = 0; + + // loge( "ts: %f %d", s->playout_rate, s->lcp ); + + //just copy on straight playout + if( s->playout_rate == 1.0f ) { + memcpy( out, s->buf, s->fill * sizeof(int16_t) ); + memcpy( out+s->fill, in, len * sizeof(int16_t) ); + out_len = len + s->fill; + s->fill = 0; + // loge( "tsr1: %d", out_len ); + return out_len; + } + + /* Top up the buffer */ + if (s->fill + len < s->buf_len) + { + /* Cannot continue without more samples */ + memcpy(s->buf + s->fill, in, sizeof(int16_t)*len); + s->fill += len; + return out_len; + } + k = s->buf_len - s->fill; + memcpy(s->buf + s->fill, in, sizeof(int16_t)*k); + in_len += k; + s->fill = s->buf_len; + while (s->fill == s->buf_len) + { + while (s->lcp >= s->buf_len) + { + memcpy(out + out_len, s->buf, sizeof(int16_t)*s->buf_len); + out_len += s->buf_len; + if (len - in_len < s->buf_len) + { + /* Cannot continue without more samples */ + memcpy(s->buf, in + in_len, sizeof(int16_t)*(len - in_len)); + s->fill = len - in_len; + s->lcp -= s->buf_len; + return out_len; + } + memcpy(s->buf, in + in_len, sizeof(int16_t)*s->buf_len); + in_len += s->buf_len; + s->lcp -= s->buf_len; + } + if (s->lcp > 0) + { + memcpy(out + out_len, s->buf, sizeof(int16_t)*s->lcp); + out_len += s->lcp; + memcpy(s->buf, s->buf + s->lcp, sizeof(int16_t)*(s->buf_len - s->lcp)); + if (len - in_len < s->lcp) + { + /* Cannot continue without more samples */ + memcpy(s->buf + (s->buf_len - s->lcp), in + in_len, sizeof(int16_t)*(len - in_len)); + s->fill = s->buf_len - s->lcp + len - in_len; + s->lcp = 0; + return out_len; + } + memcpy(s->buf + (s->buf_len - s->lcp), in + in_len, sizeof(int16_t)*s->lcp); + in_len += s->lcp; + s->lcp = 0; + } + if (s->playout_rate == 1.0f) + { + s->lcp = 0;//0x7FFFFFFF; + } + else + { + pitch = amdf_pitch(s->min_pitch, s->max_pitch, s->buf, s->min_pitch); + lcpf = (double) pitch*s->rcomp; + /* Nudge around to compensate for fractional samples */ + s->lcp = (int) lcpf; + /* Note that s->lcp and lcpf are not the same, as lcpf has a fractional part, and s->lcp doesn't */ + s->rate_nudge += s->lcp - lcpf; + if (s->rate_nudge >= 0.5f) + { + s->lcp--; + s->rate_nudge -= 1.0f; + } + else if (s->rate_nudge <= -0.5f) + { + s->lcp++; + s->rate_nudge += 1.0f; + } + if (s->playout_rate < 1.0f) + { + /* Speed up - drop a chunk of data */ + overlap_add(s->buf, s->buf + pitch, pitch); + memcpy(&s->buf[pitch], &s->buf[2*pitch], sizeof(int16_t)*(s->buf_len - 2*pitch)); + if (len - in_len < pitch) + { + /* Cannot continue without more samples */ + memcpy(s->buf + s->buf_len - pitch, in + in_len, sizeof(int16_t)*(len - in_len)); + s->fill += (len - in_len - pitch); + return out_len; + } + memcpy(s->buf + s->buf_len - pitch, in + in_len, sizeof(int16_t)*pitch); + in_len += pitch; + } + else + { + /* Slow down - insert a chunk of data */ + memcpy(out + out_len, s->buf, sizeof(int16_t)*pitch); + out_len += pitch; + overlap_add(s->buf + pitch, s->buf, pitch); + } + } + } + return out_len; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) time_scale_max_output_len(time_scale_state_t *s, int input_len) +{ + return (int) (input_len*s->playout_rate + s->min_pitch + 1); +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ diff --git a/Libraries/speex/_kiss_fft_guts.h b/Libraries/speex/_kiss_fft_guts.h new file mode 100644 index 000000000..6571e79c0 --- /dev/null +++ b/Libraries/speex/_kiss_fft_guts.h @@ -0,0 +1,160 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define MIN(a,b) ((a)<(b) ? (a):(b)) +#define MAX(a,b) ((a)>(b) ? (a):(b)) + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" +#include "math_approx.h" + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#include "arch.h" +# define FRACBITS 15 +# define SAMPPROD spx_int32_t +#define SAMP_MAX 32767 + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define C_MUL4(m,a,b) \ + do{ (m).r = PSHR32( smul((a).r,(b).r) - smul((a).i,(b).i),17 ); \ + (m).i = PSHR32( smul((a).r,(b).i) + smul((a).i,(b).r),17 ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) + +#define C_MUL4(m,a,b) C_MUL(m,a,b) + +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase)))) +# define KISS_FFT_SIN(phase) floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase)))) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) +#define kf_cexp2(x,phase) \ + do{ \ + (x)->r = spx_cos_norm((phase));\ + (x)->i = spx_cos_norm((phase)-32768);\ +}while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) diff --git a/Libraries/speex/arch.h b/Libraries/speex/arch.h new file mode 100644 index 000000000..d1f31e8f1 --- /dev/null +++ b/Libraries/speex/arch.h @@ -0,0 +1,239 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "config.h" +#ifndef ARCH_H +#define ARCH_H + +#ifndef SPEEX_VERSION +#define SPEEX_MAJOR_VERSION 1 /**< Major Speex version. */ +#define SPEEX_MINOR_VERSION 1 /**< Minor Speex version. */ +#define SPEEX_MICRO_VERSION 15 /**< Micro Speex version. */ +#define SPEEX_EXTRA_VERSION "" /**< Extra Speex version. */ +#define SPEEX_VERSION "speex-1.2beta3" /**< Speex version string. */ +#endif + +/* A couple test to catch stupid option combinations */ +#ifdef FIXED_POINT + +#ifdef FLOATING_POINT +#error You cannot compile as floating point and fixed point at the same time +#endif +#ifdef _USE_SSE +#error SSE is only for floating-point +#endif +#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM)) +#error Make up your mind. What CPU do you have? +#endif +#ifdef VORBIS_PSYCHO +#error Vorbis-psy model currently not implemented in fixed-point +#endif + +#else + +#ifndef FLOATING_POINT +#error You now need to define either FIXED_POINT or FLOATING_POINT +#endif +#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) +#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? +#endif +#ifdef FIXED_POINT_DEBUG +#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" +#endif + + +#endif + +#ifndef OUTSIDE_SPEEX +#include "speex/speex_types.h" +#endif + +#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ + +#ifdef FIXED_POINT + +typedef spx_int16_t spx_word16_t; +typedef spx_int32_t spx_word32_t; +typedef spx_word32_t spx_mem_t; +typedef spx_word16_t spx_coef_t; +typedef spx_word16_t spx_lsp_t; +typedef spx_word32_t spx_sig_t; + +#define Q15ONE 32767 + +#define LPC_SCALING 8192 +#define SIG_SCALING 16384 +#define LSP_SCALING 8192. +#define GAMMA_SCALING 32768. +#define GAIN_SCALING 64 +#define GAIN_SCALING_1 0.015625 + +#define LPC_SHIFT 13 +#define LSP_SHIFT 13 +#define SIG_SHIFT 14 +#define GAIN_SHIFT 6 + +#define VERY_SMALL 0 +#define VERY_LARGE32 ((spx_word32_t)2147483647) +#define VERY_LARGE16 ((spx_word16_t)32767) +#define Q15_ONE ((spx_word16_t)32767) + + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef ARM5E_ASM +#include "fixed_arm5e.h" +#elif defined (ARM4_ASM) +#include "fixed_arm4.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#endif + +#endif + + +#else + +typedef float spx_mem_t; +typedef float spx_coef_t; +typedef float spx_lsp_t; +typedef float spx_sig_t; +typedef float spx_word16_t; +typedef float spx_word32_t; + +#define Q15ONE 1.0f +#define LPC_SCALING 1.f +#define SIG_SCALING 1.f +#define LSP_SCALING 1.f +#define GAMMA_SCALING 1.f +#define GAIN_SCALING 1.f +#define GAIN_SCALING_1 1.f + + +#define VERY_SMALL 1e-15f +#define VERY_LARGE32 1e15f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((spx_word16_t)1.f) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR16(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) +#define SATURATE16(x,a) (x) +#define SATURATE32(x,a) (x) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) +#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) + +#define MULT16_32_Q11(a,b) ((a)*(b)) +#define MULT16_32_Q13(a,b) ((a)*(b)) +#define MULT16_32_Q14(a,b) ((a)*(b)) +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_P15(a,b) ((a)*(b)) + +#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) + +#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) +#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) + + +#endif + + +#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + +/* 2 on TI C5x DSP */ +#define BYTES_PER_CHAR 2 +#define BITS_PER_CHAR 16 +#define LOG2_BITS_PER_CHAR 4 + +#else + +#define BYTES_PER_CHAR 1 +#define BITS_PER_CHAR 8 +#define LOG2_BITS_PER_CHAR 3 + +#endif + + + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + + +#endif diff --git a/Libraries/speex/bits.c b/Libraries/speex/bits.c new file mode 100644 index 000000000..e48cea65c --- /dev/null +++ b/Libraries/speex/bits.c @@ -0,0 +1,373 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_bits.c + + Handles bit packing/unpacking + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "speex/speex_bits.h" +#include "arch.h" +#include "os_support.h" + +/* Maximum size of the bit-stream (for fixed-size allocation) */ +#ifndef MAX_CHARS_PER_FRAME +#define MAX_CHARS_PER_FRAME (2000/BYTES_PER_CHAR) +#endif + +EXPORT void speex_bits_init(SpeexBits *bits) +{ + bits->chars = (char*)speex_alloc(MAX_CHARS_PER_FRAME); + if (!bits->chars) + return; + + bits->buf_size = MAX_CHARS_PER_FRAME; + + bits->owner=1; + + speex_bits_reset(bits); +} + +EXPORT void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size) +{ + bits->chars = (char*)buff; + bits->buf_size = buf_size; + + bits->owner=0; + + speex_bits_reset(bits); +} + +EXPORT void speex_bits_set_bit_buffer(SpeexBits *bits, void *buff, int buf_size) +{ + bits->chars = (char*)buff; + bits->buf_size = buf_size; + + bits->owner=0; + + bits->nbBits=buf_size<charPtr=0; + bits->bitPtr=0; + bits->overflow=0; + +} + +EXPORT void speex_bits_destroy(SpeexBits *bits) +{ + if (bits->owner) + speex_free(bits->chars); + /* Will do something once the allocation is dynamic */ +} + +EXPORT void speex_bits_reset(SpeexBits *bits) +{ + /* We only need to clear the first byte now */ + bits->chars[0]=0; + bits->nbBits=0; + bits->charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +EXPORT void speex_bits_rewind(SpeexBits *bits) +{ + bits->charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +EXPORT void speex_bits_read_from(SpeexBits *bits, char *chars, int len) +{ + int i; + int nchars = len / BYTES_PER_CHAR; + if (nchars > bits->buf_size) + { + printf("nChars of packet: %i, and allocated buffer size: %i",nchars,bits->buf_size); + speex_notify("Packet is larger than allocated buffer"); + if (bits->owner) + { + char *tmp = (char*)speex_realloc(bits->chars, nchars); + if (tmp) + { + bits->buf_size=nchars; + bits->chars=tmp; + } else { + nchars=bits->buf_size; + speex_warning("Could not resize input buffer: truncating input"); + } + } else { + speex_warning("Do not own input buffer: truncating oversize input"); + nchars=bits->buf_size; + } + } +#if (BYTES_PER_CHAR==2) +/* Swap bytes to proper endian order (could be done externally) */ +#define HTOLS(A) ((((A) >> 8)&0xff)|(((A) & 0xff)<<8)) +#else +#define HTOLS(A) (A) +#endif + for (i=0;ichars[i]=HTOLS(chars[i]); + + bits->nbBits=nchars<charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +static void speex_bits_flush(SpeexBits *bits) +{ + int nchars = ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); + if (bits->charPtr>0) + SPEEX_MOVE(bits->chars, &bits->chars[bits->charPtr], nchars-bits->charPtr); + bits->nbBits -= bits->charPtr<charPtr=0; +} + +EXPORT void speex_bits_read_whole_bytes(SpeexBits *bits, char *chars, int nbytes) +{ + int i,pos; + int nchars = nbytes/BYTES_PER_CHAR; + + if (((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR)+nchars > bits->buf_size) + { + /* Packet is larger than allocated buffer */ + if (bits->owner) + { + char *tmp = (char*)speex_realloc(bits->chars, (bits->nbBits>>LOG2_BITS_PER_CHAR)+nchars+1); + if (tmp) + { + bits->buf_size=(bits->nbBits>>LOG2_BITS_PER_CHAR)+nchars+1; + bits->chars=tmp; + } else { + nchars=bits->buf_size-(bits->nbBits>>LOG2_BITS_PER_CHAR)-1; + speex_warning("Could not resize input buffer: truncating oversize input"); + } + } else { + speex_warning("Do not own input buffer: truncating oversize input"); + nchars=bits->buf_size; + } + } + + speex_bits_flush(bits); + pos=bits->nbBits>>LOG2_BITS_PER_CHAR; + for (i=0;ichars[pos+i]=HTOLS(chars[i]); + bits->nbBits+=nchars<bitPtr; + charPtr=bits->charPtr; + nbBits=bits->nbBits; + speex_bits_insert_terminator(bits); + bits->bitPtr=bitPtr; + bits->charPtr=charPtr; + bits->nbBits=nbBits; + + if (max_nchars > ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR)) + max_nchars = ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); + + for (i=0;ichars[i]); + return max_nchars*BYTES_PER_CHAR; +} + +EXPORT int speex_bits_write_whole_bytes(SpeexBits *bits, char *chars, int max_nbytes) +{ + int max_nchars = max_nbytes/BYTES_PER_CHAR; + int i; + if (max_nchars > ((bits->nbBits)>>LOG2_BITS_PER_CHAR)) + max_nchars = ((bits->nbBits)>>LOG2_BITS_PER_CHAR); + for (i=0;ichars[i]); + + if (bits->bitPtr>0) + bits->chars[0]=bits->chars[max_nchars]; + else + bits->chars[0]=0; + bits->charPtr=0; + bits->nbBits &= (BITS_PER_CHAR-1); + return max_nchars*BYTES_PER_CHAR; +} + +EXPORT void speex_bits_pack(SpeexBits *bits, int data, int nbBits) +{ + unsigned int d=data; + //int checkSize = bits->charPtr+((nbBits+bits->bitPtr)>>LOG2_BITS_PER_CHAR); + if (bits->charPtr+((nbBits+bits->bitPtr)>>LOG2_BITS_PER_CHAR) >= bits->buf_size) + { + speex_notify("Buffer too small to pack bits"); + if (bits->owner) + { + int new_nchars = ((bits->buf_size+5)*3)>>1; + char *tmp = (char*)speex_realloc(bits->chars, new_nchars); + if (tmp) + { + bits->buf_size=new_nchars; + bits->chars=tmp; + } else { + speex_warning("Could not resize input buffer: not packing"); + return; + } + } else { + speex_warning("Do not own input buffer: not packing"); + return; + } + } + + while(nbBits) + { + int bit; + bit = (d>>(nbBits-1))&1; + bits->chars[bits->charPtr] |= bit<<(BITS_PER_CHAR-1-bits->bitPtr); + bits->bitPtr++; + + if (bits->bitPtr==BITS_PER_CHAR) + { + bits->bitPtr=0; + bits->charPtr++; + bits->chars[bits->charPtr] = 0; + } + bits->nbBits++; + nbBits--; + } +} + +EXPORT int speex_bits_unpack_signed(SpeexBits *bits, int nbBits) +{ + unsigned int d=speex_bits_unpack_unsigned(bits,nbBits); + /* If number is negative */ + if (d>>(nbBits-1)) + { + d |= (-1)<charPtr<bitPtr+nbBits>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + while(nbBits) + { + d<<=1; + d |= (bits->chars[bits->charPtr]>>(BITS_PER_CHAR-1 - bits->bitPtr))&1; + bits->bitPtr++; + if (bits->bitPtr==BITS_PER_CHAR) + { + bits->bitPtr=0; + bits->charPtr++; + } + nbBits--; + } + return d; +} + +EXPORT unsigned int speex_bits_peek_unsigned(SpeexBits *bits, int nbBits) +{ + unsigned int d=0; + int bitPtr, charPtr; + char *chars; + + if ((bits->charPtr<bitPtr+nbBits>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + + bitPtr=bits->bitPtr; + charPtr=bits->charPtr; + chars = bits->chars; + while(nbBits) + { + d<<=1; + d |= (chars[charPtr]>>(BITS_PER_CHAR-1 - bitPtr))&1; + bitPtr++; + if (bitPtr==BITS_PER_CHAR) + { + bitPtr=0; + charPtr++; + } + nbBits--; + } + return d; +} + +EXPORT int speex_bits_peek(SpeexBits *bits) +{ + if ((bits->charPtr<bitPtr+1>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + return (bits->chars[bits->charPtr]>>(BITS_PER_CHAR-1 - bits->bitPtr))&1; +} + +EXPORT void speex_bits_advance(SpeexBits *bits, int n) +{ + if (((bits->charPtr<bitPtr+n>bits->nbBits) || bits->overflow){ + bits->overflow=1; + return; + } + bits->charPtr += (bits->bitPtr+n) >> LOG2_BITS_PER_CHAR; /* divide by BITS_PER_CHAR */ + bits->bitPtr = (bits->bitPtr+n) & (BITS_PER_CHAR-1); /* modulo by BITS_PER_CHAR */ +} + +EXPORT int speex_bits_remaining(SpeexBits *bits) +{ + if (bits->overflow) + return -1; + else + return bits->nbBits-((bits->charPtr<bitPtr); +} + +EXPORT int speex_bits_nbytes(SpeexBits *bits) +{ + return ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); +} + +EXPORT void speex_bits_insert_terminator(SpeexBits *bits) +{ + if (bits->bitPtr) + speex_bits_pack(bits, 0, 1); + while (bits->bitPtr) + speex_bits_pack(bits, 1, 1); +} diff --git a/Libraries/speex/buffer.c b/Libraries/speex/buffer.c new file mode 100644 index 000000000..6cfd5a341 --- /dev/null +++ b/Libraries/speex/buffer.c @@ -0,0 +1,176 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: buffer.c + This is a very simple ring buffer implementation. It is not thread-safe + so you need to do your own locking. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "os_support.h" +#include "arch.h" +#include + +struct SpeexBuffer_ { + char *data; + int size; + int read_ptr; + int write_ptr; + int available; +}; + +EXPORT SpeexBuffer *speex_buffer_init(int size) +{ + SpeexBuffer *st = speex_alloc(sizeof(SpeexBuffer)); + st->data = speex_alloc(size); + st->size = size; + st->read_ptr = 0; + st->write_ptr = 0; + st->available = 0; + return st; +} + +EXPORT void speex_buffer_destroy(SpeexBuffer *st) +{ + speex_free(st->data); + speex_free(st); +} + +EXPORT int speex_buffer_write(SpeexBuffer *st, void *_data, int len) +{ + int end; + int end1; + char *data = _data; + if (len > st->size) + { + data += len-st->size; + len = st->size; + } + end = st->write_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_COPY(st->data + st->write_ptr, data, end1 - st->write_ptr); + if (end > st->size) + { + end -= st->size; + SPEEX_COPY(st->data, data+end1 - st->write_ptr, end); + } + st->available += len; + if (st->available > st->size) + { + st->available = st->size; + st->read_ptr = st->write_ptr; + } + st->write_ptr += len; + if (st->write_ptr > st->size) + st->write_ptr -= st->size; + return len; +} + +EXPORT int speex_buffer_writezeros(SpeexBuffer *st, int len) +{ + /* This is almost the same as for speex_buffer_write() but using + SPEEX_MEMSET() instead of SPEEX_COPY(). Update accordingly. */ + int end; + int end1; + if (len > st->size) + { + len = st->size; + } + end = st->write_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_MEMSET(st->data + st->write_ptr, 0, end1 - st->write_ptr); + if (end > st->size) + { + end -= st->size; + SPEEX_MEMSET(st->data, 0, end); + } + st->available += len; + if (st->available > st->size) + { + st->available = st->size; + st->read_ptr = st->write_ptr; + } + st->write_ptr += len; + if (st->write_ptr > st->size) + st->write_ptr -= st->size; + return len; +} + +EXPORT int speex_buffer_read(SpeexBuffer *st, void *_data, int len) +{ + int end, end1; + char *data = _data; + if (len > st->available) + { + SPEEX_MEMSET(data+st->available, 0, st->size-st->available); + len = st->available; + } + end = st->read_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_COPY(data, st->data + st->read_ptr, end1 - st->read_ptr); + + if (end > st->size) + { + end -= st->size; + SPEEX_COPY(data+end1 - st->read_ptr, st->data, end); + } + st->available -= len; + st->read_ptr += len; + if (st->read_ptr > st->size) + st->read_ptr -= st->size; + return len; +} + +EXPORT int speex_buffer_get_available(SpeexBuffer *st) +{ + return st->available; +} + +EXPORT int speex_buffer_resize(SpeexBuffer *st, int len) +{ + int old_len = st->size; + if (len > old_len) + { + st->data = speex_realloc(st->data, len); + /* FIXME: move data/pointers properly for growing the buffer */ + } else { + /* FIXME: move data/pointers properly for shrinking the buffer */ + st->data = speex_realloc(st->data, len); + } + return len; +} diff --git a/Libraries/speex/cb_search.c b/Libraries/speex/cb_search.c new file mode 100644 index 000000000..63f4c6a4b --- /dev/null +++ b/Libraries/speex/cb_search.c @@ -0,0 +1,612 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: cb_search.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cb_search.h" +#include "filters.h" +#include "stack_alloc.h" +#include "vq.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" + +#ifdef _USE_SSE +#include "cb_search_sse.h" +#elif defined(ARM4_ASM) || defined(ARM5E_ASM) +#include "cb_search_arm4.h" +#elif defined(BFIN_ASM) +#include "cb_search_bfin.h" +#endif + +#ifndef OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_word16_t *r, spx_word16_t *resp, spx_word16_t *resp2, spx_word32_t *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + VARDECL(spx_word16_t *shape); + ALLOC(shape, subvect_size, spx_word16_t); + for (i=0;isubvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + ALLOC(resp, shape_cb_size*subvect_size, spx_word16_t); +#ifdef _USE_SSE + ALLOC(resp2, (shape_cb_size*subvect_size)>>2, __m128); + ALLOC(E, shape_cb_size>>2, __m128); +#else + resp2 = resp; + ALLOC(E, shape_cb_size, spx_word32_t); +#endif + ALLOC(t, nsf, spx_word16_t); + ALLOC(e, nsf, spx_sig_t); + + /* FIXME: Do we still need to copy the target? */ + SPEEX_COPY(t, target, nsf); + + compute_weighted_codebook(shape_cb, r, resp, resp2, E, shape_cb_size, subvect_size, stack); + + for (i=0;ishape_bits+have_sign); + + { + int rind; + spx_word16_t *res; + spx_word16_t sign=1; + rind = best_index; + if (rind>=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + res = resp+rind*subvect_size; + if (sign>0) + for (m=0;m=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + + q=subvect_size-m; +#ifdef FIXED_POINT + g=sign*shape_cb[rind*subvect_size+m]; +#else + g=sign*0.03125*shape_cb[rind*subvect_size+m]; +#endif + target_update(t+subvect_size*(i+1), g, r+q, nsf-subvect_size*(i+1)); + } + } + + /* Update excitation */ + /* FIXME: We could update the excitation directly above */ + for (j=0;j10) + N=10; + /* Complexity isn't as important for the codebooks as it is for the pitch */ + N=(2*N)/3; + if (N<1) + N=1; + if (N==1) + { + split_cb_search_shape_sign_N1(target,ak,awk1,awk2,par,p,nsf,exc,r,bits,stack,update_target); + return; + } + ALLOC(ot2, N, spx_word16_t*); + ALLOC(nt2, N, spx_word16_t*); + ALLOC(oind, N, int*); + ALLOC(nind, N, int*); + + params = (const split_cb_params *) par; + subvect_size = params->subvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + ALLOC(resp, shape_cb_size*subvect_size, spx_word16_t); +#ifdef _USE_SSE + ALLOC(resp2, (shape_cb_size*subvect_size)>>2, __m128); + ALLOC(E, shape_cb_size>>2, __m128); +#else + resp2 = resp; + ALLOC(E, shape_cb_size, spx_word32_t); +#endif + ALLOC(t, nsf, spx_word16_t); + ALLOC(e, nsf, spx_sig_t); + ALLOC(ind, nb_subvect, int); + + ALLOC(tmp, 2*N*nsf, spx_word16_t); + for (i=0;im;n--) + { + ndist[n] = ndist[n-1]; + best_nind[n] = best_nind[n-1]; + best_ntarget[n] = best_ntarget[n-1]; + } + /* n is equal to m here, so they're interchangeable */ + ndist[m] = err; + best_nind[n] = best_index[k]; + best_ntarget[n] = j; + break; + } + } + } + } + if (i==0) + break; + } + for (j=0;j=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + + q=subvect_size-m; +#ifdef FIXED_POINT + g=sign*shape_cb[rind*subvect_size+m]; +#else + g=sign*0.03125*shape_cb[rind*subvect_size+m]; +#endif + target_update(nt[j]+subvect_size*(i+1), g, r+q, nsf-subvect_size*(i+1)); + } + + for (q=0;qshape_bits+have_sign); + } + + /* Put everything back together */ + for (i=0;i=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } +#ifdef FIXED_POINT + if (sign==1) + { + for (j=0;jsubvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + + ALLOC(ind, nb_subvect, int); + ALLOC(signs, nb_subvect, int); + + /* Decode codewords and gains */ + for (i=0;ishape_bits); + } + /* Compute decoded excitation */ + for (i=0;i +#include "arch.h" + +/** Split codebook parameters. */ +typedef struct split_cb_params { + int subvect_size; + int nb_subvect; + const signed char *shape_cb; + int shape_bits; + int have_sign; +} split_cb_params; + + +void split_cb_search_shape_sign( +spx_word16_t target[], /* target vector */ +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs for this subframe */ +const void *par, /* Codebook/search parameters */ +int p, /* number of LPC coeffs */ +int nsf, /* number of samples in subframe */ +spx_sig_t *exc, +spx_word16_t *r, +SpeexBits *bits, +char *stack, +int complexity, +int update_target +); + +void split_cb_shape_sign_unquant( +spx_sig_t *exc, +const void *par, /* non-overlapping codebook */ +int nsf, /* number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_int32_t *seed +); + + +void noise_codebook_quant( +spx_word16_t target[], /* target vector */ +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs for this subframe */ +const void *par, /* Codebook/search parameters */ +int p, /* number of LPC coeffs */ +int nsf, /* number of samples in subframe */ +spx_sig_t *exc, +spx_word16_t *r, +SpeexBits *bits, +char *stack, +int complexity, +int update_target +); + + +void noise_codebook_unquant( +spx_sig_t *exc, +const void *par, /* non-overlapping codebook */ +int nsf, /* number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_int32_t *seed +); + +#endif diff --git a/Libraries/speex/cb_search_arm4.h b/Libraries/speex/cb_search_arm4.h new file mode 100644 index 000000000..19b752a4b --- /dev/null +++ b/Libraries/speex/cb_search_arm4.h @@ -0,0 +1,137 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file cb_search_arm4.h + @brief Fixed codebook functions (ARM4 version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This optimization is temporaly disabled until it is fixed to account for the fact + that "r" is now a 16-bit array */ +#if 0 +#define OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_word16_t *r, spx_word16_t *resp, spx_word16_t *resp2, spx_word32_t *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + //const signed char *shape; + for (i=0;i>>= 13;\n\t" + "A1 += R0.L*R0.L (IS);\n\t" + "W[P3++] = R0;\n\t" + "P0 += 1;\n\t" + "P2 += 2;\n\t" + "LOOP_END outter%=;\n\t" + "P4 = %4;\n\t" + "R1 = A1;\n\t" + "[P4] = R1;\n\t" + : + : "m" (subvect_size), "m" (shape_cb), "m" (r), "m" (resp), "m" (E) + : "A0", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "I0", "I1", "L0", + "L1", "A0", "A1", "memory" +#if !(__GNUC__ == 3) + , "LC0", "LC1" /* gcc 3.4 doesn't know about LC registers */ +#endif + ); + shape_cb += subvect_size; + resp += subvect_size; + E++; + } +} + +#define OVERRIDE_TARGET_UPDATE +static inline void target_update(spx_word16_t *t, spx_word16_t g, spx_word16_t *r, int len) +{ + if (!len) + return; + __asm__ __volatile__ + ( + "I0 = %0;\n\t" + "I1 = %1;\n\t" + "L0 = 0;\n\t" + "L1 = 0;\n\t" + "R2 = 4096;\n\t" + "LOOP tupdate%= LC0 = %3;\n\t" + "LOOP_BEGIN tupdate%=;\n\t" + "R0.L = W[I0] || R1.L = W[I1++];\n\t" + "R1 = (A1 = R1.L*%2.L) (IS);\n\t" + "R1 = R1 + R2;\n\t" + "R1 >>>= 13;\n\t" + "R0.L = R0.L - R1.L;\n\t" + "W[I0++] = R0.L;\n\t" + "LOOP_END tupdate%=;\n\t" + : + : "a" (t), "a" (r), "d" (g), "a" (len) + : "R0", "R1", "R2", "A1", "I0", "I1", "L0", "L1" + ); +} diff --git a/Libraries/speex/cb_search_sse.h b/Libraries/speex/cb_search_sse.h new file mode 100644 index 000000000..8b039686f --- /dev/null +++ b/Libraries/speex/cb_search_sse.h @@ -0,0 +1,84 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file cb_search_sse.h + @brief Fixed codebook functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +static inline void _spx_mm_getr_ps (__m128 U, float *__Z, float *__Y, float *__X, float *__W) +{ + union { + float __a[4]; + __m128 __v; + } __u; + + __u.__v = U; + + *__Z = __u.__a[0]; + *__Y = __u.__a[1]; + *__X = __u.__a[2]; + *__W = __u.__a[3]; + +} + +#define OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_sig_t *_r, float *resp, __m128 *resp2, __m128 *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + __m128 resj, EE; + VARDECL(__m128 *r); + VARDECL(__m128 *shape); + ALLOC(r, subvect_size, __m128); + ALLOC(shape, subvect_size, __m128); + for(j=0;j>2] = EE; + } +} diff --git a/Libraries/speex/exc_10_16_table.c b/Libraries/speex/exc_10_16_table.c new file mode 100644 index 000000000..98ae357d8 --- /dev/null +++ b/Libraries/speex/exc_10_16_table.c @@ -0,0 +1,50 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_10_16_table.c + Codebook for excitation in narrowband CELP mode (3200 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_10_16_table[160] = { +22,39,14,44,11,35,-2,23,-4,6, +46,-28,13,-27,-23,12,4,20,-5,9, +37,-18,-23,23,0,9,-6,-20,4,-1, +-17,-5,-4,17,0,1,9,-2,1,2, +2,-12,8,-25,39,15,9,16,-55,-11, +9,11,5,10,-2,-60,8,13,-6,11, +-16,27,-47,-12,11,1,16,-7,9,-3, +-29,9,-14,25,-19,34,36,12,40,-10, +-3,-24,-14,-37,-21,-35,-2,-36,3,-6, +67,28,6,-17,-3,-12,-16,-15,-17,-7, +-59,-36,-13,1,7,1,2,10,2,11, +13,10,8,-2,7,3,5,4,2,2, +-3,-8,4,-5,6,7,-42,15,35,-2, +-46,38,28,-20,-9,1,7,-3,0,-2, +0,0,0,0,0,0,0,0,0,0, +-15,-28,52,32,5,-5,-17,-20,-10,-1}; diff --git a/Libraries/speex/exc_10_32_table.c b/Libraries/speex/exc_10_32_table.c new file mode 100644 index 000000000..1ee56a259 --- /dev/null +++ b/Libraries/speex/exc_10_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_10_32_table.c + Codebook for excitation in narrowband CELP mode (4000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_10_32_table[320] = { +7,17,17,27,25,22,12,4,-3,0, +28,-36,39,-24,-15,3,-9,15,-5,10, +31,-28,11,31,-21,9,-11,-11,-2,-7, +-25,14,-22,31,4,-14,19,-12,14,-5, +4,-7,4,-5,9,0,-2,42,-47,-16, +1,8,0,9,23,-57,0,28,-11,6, +-31,55,-45,3,-5,4,2,-2,4,-7, +-3,6,-2,7,-3,12,5,8,54,-10, +8,-7,-8,-24,-25,-27,-14,-5,8,5, +44,23,5,-9,-11,-11,-13,-9,-12,-8, +-29,-8,-22,6,-15,3,-12,-1,-5,-3, +34,-1,29,-16,17,-4,12,2,1,4, +-2,-4,2,-1,11,-3,-52,28,30,-9, +-32,25,44,-20,-24,4,6,-1,0,0, +0,0,0,0,0,0,0,0,0,0, +-25,-10,22,29,13,-13,-22,-13,-4,0, +-4,-16,10,15,-36,-24,28,25,-1,-3, +66,-33,-11,-15,6,0,3,4,-2,5, +24,-20,-47,29,19,-2,-4,-1,0,-1, +-2,3,1,8,-11,5,5,-57,28,28, +0,-16,4,-4,12,-6,-1,2,-20,61, +-9,24,-22,-42,29,6,17,8,4,2, +-65,15,8,10,5,6,5,3,2,-2, +-3,5,-9,4,-5,23,13,23,-3,-63, +3,-5,-4,-6,0,-3,23,-36,-46,9, +5,5,8,4,9,-5,1,-3,10,1, +-6,10,-11,24,-47,31,22,-12,14,-10, +6,11,-7,-7,7,-31,51,-12,-6,7, +6,-17,9,-11,-20,52,-19,3,-6,-6, +-8,-5,23,-41,37,1,-21,10,-14,8, +7,5,-15,-15,23,39,-26,-33,7,2, +-32,-30,-21,-8,4,12,17,15,14,11}; diff --git a/Libraries/speex/exc_20_32_table.c b/Libraries/speex/exc_20_32_table.c new file mode 100644 index 000000000..e4098b8d1 --- /dev/null +++ b/Libraries/speex/exc_20_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_20_32_table.c + Codebook for excitation in narrowband CELP mode (2000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_20_32_table[640] = { +12,32,25,46,36,33,9,14,-3,6,1,-8,0,-10,-5,-7,-7,-7,-5,-5, +31,-27,24,-32,-4,10,-11,21,-3,19,23,-9,22,24,-10,-1,-10,-13,-7,-11, +42,-33,31,19,-8,0,-10,-16,1,-21,-17,10,-8,14,8,4,11,-2,5,-2, +-33,11,-16,33,11,-4,9,-4,11,2,6,-5,8,-5,11,-4,-6,26,-36,-16, +0,4,-2,-8,12,6,-1,34,-46,-22,9,9,21,9,5,-66,-5,26,2,10, +13,2,19,9,12,-81,3,13,13,0,-14,22,-35,6,-7,-4,6,-6,10,-6, +-31,38,-33,0,-10,-11,5,-12,12,-17,5,0,-6,13,-9,10,8,25,33,2, +-12,8,-6,10,-2,21,7,17,43,5,11,-7,-9,-20,-36,-20,-23,-4,-4,-3, +27,-9,-9,-49,-39,-38,-11,-9,6,5,23,25,5,3,3,4,1,2,-3,-1, +87,39,17,-21,-9,-19,-9,-15,-13,-14,-17,-11,-10,-11,-8,-6,-1,-3,-3,-1, +-54,-34,-27,-8,-11,-4,-5,0,0,4,8,6,9,7,9,7,6,5,5,5, +48,10,19,-10,12,-1,9,-3,2,5,-3,2,-2,-2,0,-2,-26,6,9,-7, +-16,-9,2,7,7,-5,-43,11,22,-11,-9,34,37,-15,-13,-6,1,-1,1,1, +-64,56,52,-11,-27,5,4,3,1,2,1,3,-1,-4,-4,-10,-7,-4,-4,2, +-1,-7,-7,-12,-10,-15,-9,-5,-5,-11,-16,-13,6,16,4,-13,-16,-10,-4,2, +-47,-13,25,47,19,-14,-20,-8,-17,0,-3,-13,1,6,-17,-14,15,1,10,6, +-24,0,-10,19,-69,-8,14,49,17,-5,33,-29,3,-4,0,2,-8,5,-6,2, +120,-56,-12,-47,23,-9,6,-5,1,2,-5,1,-10,4,-1,-1,4,-1,0,-3, +30,-52,-67,30,22,11,-1,-4,3,0,7,2,0,1,-10,-4,-8,-13,5,1, +1,-1,5,13,-9,-3,-10,-62,22,48,-4,-6,2,3,5,1,1,4,1,13, +3,-20,10,-9,13,-2,-4,9,-20,44,-1,20,-32,-67,19,0,28,11,8,2, +-11,15,-19,-53,31,2,34,10,6,-4,-58,8,10,13,14,1,12,2,0,0, +-128,37,-8,44,-9,26,-3,18,2,6,11,-1,9,1,5,3,0,1,1,2, +12,3,-2,-3,7,25,9,18,-6,-37,3,-8,-16,3,-10,-7,17,-34,-44,11, +17,-15,-3,-16,-1,-13,11,-46,-65,-2,8,13,2,4,4,5,15,5,9,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +-9,19,-12,12,-28,38,29,-1,12,2,5,23,-10,3,4,-15,21,-4,3,3, +6,17,-9,-4,-8,-20,26,5,-10,6,1,-19,18,-15,-12,47,-6,-2,-7,-9, +-1,-17,-2,-2,-14,30,-14,2,-7,-4,-1,-12,11,-25,16,-3,-12,11,-7,7, +-17,1,19,-28,31,-7,-10,7,-10,3,12,5,-16,6,24,41,-29,-54,0,1, +7,-1,5,-6,13,10,-4,-8,8,-9,-27,-53,-38,-1,10,19,17,16,12,12, +0,3,-7,-4,13,12,-31,-14,6,-5,3,5,17,43,50,25,10,1,-6,-2}; diff --git a/Libraries/speex/exc_5_256_table.c b/Libraries/speex/exc_5_256_table.c new file mode 100644 index 000000000..4137996d4 --- /dev/null +++ b/Libraries/speex/exc_5_256_table.c @@ -0,0 +1,290 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_5_256_table.c + Codebook for excitation in narrowband CELP mode (12800 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_5_256_table[1280] = { +-8,-37,5,-43,5, +73,61,39,12,-3, +-61,-32,2,42,30, +-3,17,-27,9,34, +20,-1,-5,2,23, +-7,-46,26,53,-47, +20,-2,-33,-89,-51, +-64,27,11,15,-34, +-5,-56,25,-9,-1, +-29,1,40,67,-23, +-16,16,33,19,7, +14,85,22,-10,-10, +-12,-7,-1,52,89, +29,11,-20,-37,-46, +-15,17,-24,-28,24, +2,1,0,23,-101, +23,14,-1,-23,-18, +9,5,-13,38,1, +-28,-28,4,27,51, +-26,34,-40,35,47, +54,38,-54,-26,-6, +42,-25,13,-30,-36, +18,41,-4,-33,23, +-32,-7,-4,51,-3, +17,-52,56,-47,36, +-2,-21,36,10,8, +-33,31,19,9,-5, +-40,10,-9,-21,19, +18,-78,-18,-5,0, +-26,-36,-47,-51,-44, +18,40,27,-2,29, +49,-26,2,32,-54, +30,-73,54,3,-5, +36,22,53,10,-1, +-84,-53,-29,-5,3, +-44,53,-51,4,22, +71,-35,-1,33,-5, +-27,-7,36,17,-23, +-39,16,-9,-55,-15, +-20,39,-35,6,-39, +-14,18,48,-64,-17, +-15,9,39,81,37, +-68,37,47,-21,-6, +-104,13,6,9,-2, +35,8,-23,18,42, +45,21,33,-5,-49, +9,-6,-43,-56,39, +2,-16,-25,87,1, +-3,-9,17,-25,-11, +-9,-1,10,2,-14, +-14,4,-1,-10,28, +-23,40,-32,26,-9, +26,4,-27,-23,3, +42,-60,1,49,-3, +27,10,-52,-40,-2, +18,45,-23,17,-44, +3,-3,17,-46,52, +-40,-47,25,75,31, +-49,53,30,-30,-32, +-36,38,-6,-15,-16, +54,-27,-48,3,38, +-29,-32,-22,-14,-4, +-23,-13,32,-39,9, +8,-45,-13,34,-16, +49,40,32,31,28, +23,23,32,47,59, +-68,8,62,44,25, +-14,-24,-65,-16,36, +67,-25,-38,-21,4, +-33,-2,42,5,-63, +40,11,26,-42,-23, +-61,79,-31,23,-20, +10,-32,53,-25,-36, +10,-26,-5,3,0, +-71,5,-10,-37,1, +-24,21,-54,-17,1, +-29,-25,-15,-27,32, +68,45,-16,-37,-18, +-5,1,0,-77,71, +-6,3,-20,71,-67, +29,-35,10,-30,19, +4,16,17,5,0, +-14,19,2,28,26, +59,3,2,24,39, +55,-50,-45,-18,-17, +33,-35,14,-1,1, +8,87,-35,-29,0, +-27,13,-7,23,-13, +37,-40,50,-35,14, +19,-7,-14,49,54, +-5,22,-2,-29,-8, +-27,38,13,27,48, +12,-41,-21,-15,28, +7,-16,-24,-19,-20, +11,-20,9,2,13, +23,-20,11,27,-27, +71,-69,8,2,-6, +22,12,16,16,9, +-16,-8,-17,1,25, +1,40,-37,-33,66, +94,53,4,-22,-25, +-41,-42,25,35,-16, +-15,57,31,-29,-32, +21,16,-60,45,15, +-1,7,57,-26,-47, +-29,11,8,15,19, +-105,-8,54,27,10, +-17,6,-12,-1,-10, +4,0,23,-10,31, +13,11,10,12,-64, +23,-3,-8,-19,16, +52,24,-40,16,10, +40,5,9,0,-13, +-7,-21,-8,-6,-7, +-21,59,16,-53,18, +-60,11,-47,14,-18, +25,-13,-24,4,-39, +16,-28,54,26,-67, +30,27,-20,-52,20, +-12,55,12,18,-16, +39,-14,-6,-26,56, +-88,-55,12,25,26, +-37,6,75,0,-34, +-81,54,-30,1,-7, +49,-23,-14,21,10, +-62,-58,-57,-47,-34, +15,-4,34,-78,31, +25,-11,7,50,-10, +42,-63,14,-36,-4, +57,55,57,53,42, +-42,-1,15,40,37, +15,25,-11,6,1, +31,-2,-6,-1,-7, +-64,34,28,30,-1, +3,21,0,-88,-12, +-56,25,-28,40,8, +-28,-14,9,12,2, +-6,-17,22,49,-6, +-26,14,28,-20,4, +-12,50,35,40,13, +-38,-58,-29,17,30, +22,60,26,-54,-39, +-12,58,-28,-63,10, +-21,-8,-12,26,-62, +6,-10,-11,-22,-6, +-7,4,1,18,2, +-70,11,14,4,13, +19,-24,-34,24,67, +17,51,-21,13,23, +54,-30,48,1,-13, +80,26,-16,-2,13, +-4,6,-30,29,-24, +73,-58,30,-27,20, +-2,-21,41,45,30, +-27,-3,-5,-18,-20, +-49,-3,-35,10,42, +-19,-67,-53,-11,9, +13,-15,-33,-51,-30, +15,7,25,-30,4, +28,-22,-34,54,-29, +39,-46,20,16,34, +-4,47,75,1,-44, +-55,-24,7,-1,9, +-42,50,-8,-36,41, +68,0,-4,-10,-23, +-15,-50,64,36,-9, +-27,12,25,-38,-47, +-37,32,-49,51,-36, +2,-4,69,-26,19, +7,45,67,46,13, +-63,46,15,-47,4, +-41,13,-6,5,-21, +37,26,-55,-7,33, +-1,-28,10,-17,-64, +-14,0,-36,-17,93, +-3,-9,-66,44,-21, +3,-12,38,-6,-13, +-12,19,13,43,-43, +-10,-12,6,-5,9, +-49,32,-5,2,4, +5,15,-16,10,-21, +8,-62,-8,64,8, +79,-1,-66,-49,-18, +5,40,-5,-30,-45, +1,-6,21,-32,93, +-18,-30,-21,32,21, +-18,22,8,5,-41, +-54,80,22,-10,-7, +-8,-23,-64,66,56, +-14,-30,-41,-46,-14, +-29,-37,27,-14,42, +-2,-9,-29,34,14, +33,-14,22,4,10, +26,26,28,32,23, +-72,-32,3,0,-14, +35,-42,-78,-32,6, +29,-18,-45,-5,7, +-33,-45,-3,-22,-34, +8,-8,4,-51,-25, +-9,59,-78,21,-5, +-25,-48,66,-15,-17, +-24,-49,-13,25,-23, +-64,-6,40,-24,-19, +-11,57,-33,-8,1, +10,-52,-54,28,39, +49,34,-11,-61,-41, +-43,10,15,-15,51, +30,15,-51,32,-34, +-2,-34,14,18,16, +1,1,-3,-3,1, +1,-18,6,16,48, +12,-5,-42,7,36, +48,7,-20,-10,7, +12,2,54,39,-38, +37,54,4,-11,-8, +-46,-10,5,-10,-34, +46,-12,29,-37,39, +36,-11,24,56,17, +14,20,25,0,-25, +-28,55,-7,-5,27, +3,9,-26,-8,6, +-24,-10,-30,-31,-34, +18,4,22,21,40, +-1,-29,-37,-8,-21, +92,-29,11,-3,11, +73,23,22,7,4, +-44,-9,-11,21,-13, +11,9,-78,-1,47, +114,-12,-37,-19,-5, +-11,-22,19,12,-30, +7,38,45,-21,-8, +-9,55,-45,56,-21, +7,17,46,-57,-87, +-6,27,31,31,7, +-56,-12,46,21,-5, +-12,36,3,3,-21, +43,19,12,-7,9, +-14,0,-9,-33,-91, +7,26,3,-11,64, +83,-31,-46,25,2, +9,5,2,2,-1, +20,-17,10,-5,-27, +-8,20,8,-19,16, +-21,-13,-31,5,5, +42,24,9,34,-20, +28,-61,22,11,-39, +64,-20,-1,-30,-9, +-20,24,-25,-24,-29, +22,-60,6,-5,41, +-9,-87,14,34,15, +-57,52,69,15,-3, +-102,58,16,3,6, +60,-75,-32,26,7, +-57,-27,-32,-24,-21, +-29,-16,62,-46,31, +30,-27,-15,7,15}; diff --git a/Libraries/speex/exc_5_64_table.c b/Libraries/speex/exc_5_64_table.c new file mode 100644 index 000000000..2c66d5189 --- /dev/null +++ b/Libraries/speex/exc_5_64_table.c @@ -0,0 +1,98 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_5_64_table.c + Codebook for excitation in narrowband CELP mode (9600 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_5_64_table[320]={ +1,5,-15,49,-66, +-48,-4,50,-44,7, +37,16,-18,25,-26, +-26,-15,19,19,-27, +-47,28,57,5,-17, +-32,-41,68,21,-2, +64,56,8,-16,-13, +-26,-9,-16,11,6, +-39,25,-19,22,-31, +20,-45,55,-43,10, +-16,47,-40,40,-20, +-51,3,-17,-14,-15, +-24,53,-20,-46,46, +27,-68,32,3,-18, +-5,9,-31,16,-9, +-10,-1,-23,48,95, +47,25,-41,-32,-3, +15,-25,-55,36,41, +-27,20,5,13,14, +-22,5,2,-23,18, +46,-15,17,-18,-34, +-5,-8,27,-55,73, +16,2,-1,-17,40, +-78,33,0,2,19, +4,53,-16,-15,-16, +-28,-3,-13,49,8, +-7,-29,27,-13,32, +20,32,-61,16,14, +41,44,40,24,20, +7,4,48,-60,-77, +17,-6,-48,65,-15, +32,-30,-71,-10,-3, +-6,10,-2,-7,-29, +-56,67,-30,7,-5, +86,-6,-10,0,5, +-31,60,34,-38,-3, +24,10,-2,30,23, +24,-41,12,70,-43, +15,-17,6,13,16, +-13,8,30,-15,-8, +5,23,-34,-98,-4, +-13,13,-48,-31,70, +12,31,25,24,-24, +26,-7,33,-16,8, +5,-11,-14,-8,-65, +13,10,-2,-9,0, +-3,-68,5,35,7, +0,-31,-1,-17,-9, +-9,16,-37,-18,-1, +69,-48,-28,22,-21, +-11,5,49,55,23, +-86,-36,16,2,13, +63,-51,30,-11,13, +24,-18,-6,14,-19, +1,41,9,-5,27, +-36,-44,-34,-37,-21, +-26,31,-39,15,43, +5,-8,29,20,-8, +-20,-52,-28,-1,13, +26,-34,-10,-9,27, +-8,8,27,-66,4, +12,-22,49,10,-77, +32,-18,3,-38,12, +-3,-1,2,2,0}; diff --git a/Libraries/speex/exc_8_128_table.c b/Libraries/speex/exc_8_128_table.c new file mode 100644 index 000000000..17ee64b92 --- /dev/null +++ b/Libraries/speex/exc_8_128_table.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_8_128_table.c + Codebook for excitation in narrowband CELP mode (7000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_8_128_table[1024] = { +-14,9,13,-32,2,-10,31,-10, +-8,-8,6,-4,-1,10,-64,23, +6,20,13,6,8,-22,16,34, +7,42,-49,-28,5,26,4,-15, +41,34,41,32,33,24,23,14, +8,40,34,4,-24,-41,-19,-15, +13,-13,33,-54,24,27,-44,33, +27,-15,-15,24,-19,14,-36,14, +-9,24,-12,-4,37,-5,16,-34, +5,10,33,-15,-54,-16,12,25, +12,1,2,0,3,-1,-4,-4, +11,2,-56,54,27,-20,13,-6, +-46,-41,-33,-11,-5,7,12,14, +-14,-5,8,20,6,3,4,-8, +-5,-42,11,8,-14,25,-2,2, +13,11,-22,39,-9,9,5,-45, +-9,7,-9,12,-7,34,-17,-102, +7,2,-42,18,35,-9,-34,11, +-5,-2,3,22,46,-52,-25,-9, +-94,8,11,-5,-5,-5,4,-7, +-35,-7,54,5,-32,3,24,-9, +-22,8,65,37,-1,-12,-23,-6, +-9,-28,55,-33,14,-3,2,18, +-60,41,-17,8,-16,17,-11,0, +-11,29,-28,37,9,-53,33,-14, +-9,7,-25,-7,-11,26,-32,-8, +24,-21,22,-19,19,-10,29,-14, +0,0,0,0,0,0,0,0, +-5,-52,10,41,6,-30,-4,16, +32,22,-27,-22,32,-3,-28,-3, +3,-35,6,17,23,21,8,2, +4,-45,-17,14,23,-4,-31,-11, +-3,14,1,19,-11,2,61,-8, +9,-12,7,-10,12,-3,-24,99, +-48,23,50,-37,-5,-23,0,8, +-14,35,-64,-5,46,-25,13,-1, +-49,-19,-15,9,34,50,25,11, +-6,-9,-16,-20,-32,-33,-32,-27, +10,-8,12,-15,56,-14,-32,33, +3,-9,1,65,-9,-9,-10,-2, +-6,-23,9,17,3,-28,13,-32, +4,-2,-10,4,-16,76,12,-52, +6,13,33,-6,4,-14,-9,-3, +1,-15,-16,28,1,-15,11,16, +9,4,-21,-37,-40,-6,22,12, +-15,-23,-14,-17,-16,-9,-10,-9, +13,-39,41,5,-9,16,-38,25, +46,-47,4,49,-14,17,-2,6, +18,5,-6,-33,-22,44,50,-2, +1,3,-6,7,7,-3,-21,38, +-18,34,-14,-41,60,-13,6,16, +-24,35,19,-13,-36,24,3,-17, +-14,-10,36,44,-44,-29,-3,3, +-54,-8,12,55,26,4,-2,-5, +2,-11,22,-23,2,22,1,-25, +-39,66,-49,21,-8,-2,10,-14, +-60,25,6,10,27,-25,16,5, +-2,-9,26,-13,-20,58,-2,7, +52,-9,2,5,-4,-15,23,-1, +-38,23,8,27,-6,0,-27,-7, +39,-10,-14,26,11,-45,-12,9, +-5,34,4,-35,10,43,-22,-11, +56,-7,20,1,10,1,-26,9, +94,11,-27,-14,-13,1,-11,0, +14,-5,-6,-10,-4,-15,-8,-41, +21,-5,1,-28,-8,22,-9,33, +-23,-4,-4,-12,39,4,-7,3, +-60,80,8,-17,2,-6,12,-5, +1,9,15,27,31,30,27,23, +61,47,26,10,-5,-8,-12,-13, +5,-18,25,-15,-4,-15,-11,12, +-2,-2,-16,-2,-6,24,12,11, +-4,9,1,-9,14,-45,57,12, +20,-35,26,11,-64,32,-10,-10, +42,-4,-9,-16,32,24,7,10, +52,-11,-57,29,0,8,0,-6, +17,-17,-56,-40,7,20,18,12, +-6,16,5,7,-1,9,1,10, +29,12,16,13,-2,23,7,9, +-3,-4,-5,18,-64,13,55,-25, +9,-9,24,14,-25,15,-11,-40, +-30,37,1,-19,22,-5,-31,13, +-2,0,7,-4,16,-67,12,66, +-36,24,-8,18,-15,-23,19,0, +-45,-7,4,3,-13,13,35,5, +13,33,10,27,23,0,-7,-11, +43,-74,36,-12,2,5,-8,6, +-33,11,-16,-14,-5,-7,-3,17, +-34,27,-16,11,-9,15,33,-31, +8,-16,7,-6,-7,63,-55,-17, +11,-1,20,-46,34,-30,6,9, +19,28,-9,5,-24,-8,-23,-2, +31,-19,-16,-5,-15,-18,0,26, +18,37,-5,-15,-2,17,5,-27, +21,-33,44,12,-27,-9,17,11, +25,-21,-31,-7,13,33,-8,-25, +-7,7,-10,4,-6,-9,48,-82, +-23,-8,6,11,-23,3,-3,49, +-29,25,31,4,14,16,9,-4, +-18,10,-26,3,5,-44,-9,9, +-47,-55,15,9,28,1,4,-3, +46,6,-6,-38,-29,-31,-15,-6, +3,0,14,-6,8,-54,-50,33, +-5,1,-14,33,-48,26,-4,-5, +-3,-5,-3,-5,-28,-22,77,55, +-1,2,10,10,-9,-14,-66,-49, +11,-36,-6,-20,10,-10,16,12, +4,-1,-16,45,-44,-50,31,-2, +25,42,23,-32,-22,0,11,20, +-40,-35,-40,-36,-32,-26,-21,-13, +52,-22,6,-24,-20,17,-5,-8, +36,-25,-11,21,-26,6,34,-8, +7,20,-3,5,-25,-8,18,-5, +-9,-4,1,-9,20,20,39,48, +-24,9,5,-65,22,29,4,3, +-43,-11,32,-6,9,19,-27,-10, +-47,-14,24,10,-7,-36,-7,-1, +-4,-5,-5,16,53,25,-26,-29, +-4,-12,45,-58,-34,33,-5,2, +-1,27,-48,31,-15,22,-5,4, +7,7,-25,-3,11,-22,16,-12, +8,-3,7,-11,45,14,-73,-19, +56,-46,24,-20,28,-12,-2,-1, +-36,-3,-33,19,-6,7,2,-15, +5,-31,-45,8,35,13,20,0, +-9,48,-13,-43,-3,-13,2,-5, +72,-68,-27,2,1,-2,-7,5, +36,33,-40,-12,-4,-5,23,19}; diff --git a/Libraries/speex/fftwrap.c b/Libraries/speex/fftwrap.c new file mode 100644 index 000000000..4f37e1b3f --- /dev/null +++ b/Libraries/speex/fftwrap.c @@ -0,0 +1,397 @@ +/* Copyright (C) 2005-2006 Jean-Marc Valin + File: fftwrap.c + + Wrapper for various FFTs + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "arch.h" +#include "os_support.h" + +#define MAX_FFT_SIZE 2048 + +#ifdef FIXED_POINT +static int maximize_range(spx_word16_t *in, spx_word16_t *out, spx_word16_t bound, int len) +{ + int i, shift; + spx_word16_t max_val = 0; + for (i=0;imax_val) + max_val = in[i]; + if (-in[i]>max_val) + max_val = -in[i]; + } + shift=0; + while (max_val <= (bound>>1) && max_val != 0) + { + max_val <<= 1; + shift++; + } + for (i=0;i + +void *spx_fft_init(int size) +{ + struct drft_lookup *table; + table = speex_alloc(sizeof(struct drft_lookup)); + spx_drft_init((struct drft_lookup *)table, size); + return (void*)table; +} + +void spx_fft_destroy(void *table) +{ + spx_drft_clear(table); + speex_free(table); +} + +void spx_fft(void *table, float *in, float *out) +{ + if (in==out) + { + int i; + float scale = 1./((struct drft_lookup *)table)->n; + speex_warning("FFT should not be done in-place"); + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = scale*in[i]; + } else { + int i; + float scale = 1./((struct drft_lookup *)table)->n; + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = scale*in[i]; + } + spx_drft_forward((struct drft_lookup *)table, out); +} + +void spx_ifft(void *table, float *in, float *out) +{ + if (in==out) + { + speex_warning("FFT should not be done in-place"); + } else { + int i; + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = in[i]; + } + spx_drft_backward((struct drft_lookup *)table, out); +} + +#elif defined(USE_INTEL_MKL) +#include + +struct mkl_config { + DFTI_DESCRIPTOR_HANDLE desc; + int N; +}; + +void *spx_fft_init(int size) +{ + struct mkl_config *table = (struct mkl_config *) speex_alloc(sizeof(struct mkl_config)); + table->N = size; + DftiCreateDescriptor(&table->desc, DFTI_SINGLE, DFTI_REAL, 1, size); + DftiSetValue(table->desc, DFTI_PACKED_FORMAT, DFTI_PACK_FORMAT); + DftiSetValue(table->desc, DFTI_PLACEMENT, DFTI_NOT_INPLACE); + DftiSetValue(table->desc, DFTI_FORWARD_SCALE, 1.0f / size); + DftiCommitDescriptor(table->desc); + return table; +} + +void spx_fft_destroy(void *table) +{ + struct mkl_config *t = (struct mkl_config *) table; + DftiFreeDescriptor(t->desc); + speex_free(table); +} + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + struct mkl_config *t = (struct mkl_config *) table; + DftiComputeForward(t->desc, in, out); +} + +void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + struct mkl_config *t = (struct mkl_config *) table; + DftiComputeBackward(t->desc, in, out); +} + +#elif defined(USE_GPL_FFTW3) + +#include + +struct fftw_config { + float *in; + float *out; + fftwf_plan fft; + fftwf_plan ifft; + int N; +}; + +void *spx_fft_init(int size) +{ + struct fftw_config *table = (struct fftw_config *) speex_alloc(sizeof(struct fftw_config)); + table->in = fftwf_malloc(sizeof(float) * (size+2)); + table->out = fftwf_malloc(sizeof(float) * (size+2)); + + table->fft = fftwf_plan_dft_r2c_1d(size, table->in, (fftwf_complex *) table->out, FFTW_PATIENT); + table->ifft = fftwf_plan_dft_c2r_1d(size, (fftwf_complex *) table->in, table->out, FFTW_PATIENT); + + table->N = size; + return table; +} + +void spx_fft_destroy(void *table) +{ + struct fftw_config *t = (struct fftw_config *) table; + fftwf_destroy_plan(t->fft); + fftwf_destroy_plan(t->ifft); + fftwf_free(t->in); + fftwf_free(t->out); + speex_free(table); +} + + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + int i; + struct fftw_config *t = (struct fftw_config *) table; + const int N = t->N; + float *iptr = t->in; + float *optr = t->out; + const float m = 1.0 / N; + for(i=0;ifft); + + out[0] = optr[0]; + for(i=1;iN; + float *iptr = t->in; + float *optr = t->out; + + iptr[0] = in[0]; + iptr[1] = 0.0f; + for(i=1;iifft); + + for(i=0;iforward = kiss_fftr_alloc(size,0,NULL,NULL); + table->backward = kiss_fftr_alloc(size,1,NULL,NULL); + table->N = size; + return table; +} + +void spx_fft_destroy(void *table) +{ + struct kiss_config *t = (struct kiss_config *)table; + kiss_fftr_free(t->forward); + kiss_fftr_free(t->backward); + speex_free(table); +} + +#ifdef FIXED_POINT + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + int shift; + struct kiss_config *t = (struct kiss_config *)table; + shift = maximize_range(in, in, 32000, t->N); + kiss_fftr2(t->forward, in, out); + renorm_range(in, in, shift, t->N); + renorm_range(out, out, shift, t->N); +} + +#else + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + int i; + float scale; + struct kiss_config *t = (struct kiss_config *)table; + scale = 1./t->N; + kiss_fftr2(t->forward, in, out); + for (i=0;iN;i++) + out[i] *= scale; +} +#endif + +void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + struct kiss_config *t = (struct kiss_config *)table; + kiss_fftri2(t->backward, in, out); +} + + +#else + +#error No other FFT implemented + +#endif + + +#ifdef FIXED_POINT +/*#include "smallft.h"*/ + + +void spx_fft_float(void *table, float *in, float *out) +{ + int i; +#ifdef USE_SMALLFT + int N = ((struct drft_lookup *)table)->n; +#elif defined(USE_KISS_FFT) + int N = ((struct kiss_config *)table)->N; +#else +#endif +#ifdef VAR_ARRAYS + spx_word16_t _in[N]; + spx_word16_t _out[N]; +#else + spx_word16_t _in[MAX_FFT_SIZE]; + spx_word16_t _out[MAX_FFT_SIZE]; +#endif + for (i=0;iN); + scale = 1./((struct kiss_config *)table)->N; + for (i=0;i<((struct kiss_config *)table)->N;i++) + out[i] = scale*in[i]; + spx_drft_forward(&t, out); + spx_drft_clear(&t); + } +#endif +} + +void spx_ifft_float(void *table, float *in, float *out) +{ + int i; +#ifdef USE_SMALLFT + int N = ((struct drft_lookup *)table)->n; +#elif defined(USE_KISS_FFT) + int N = ((struct kiss_config *)table)->N; +#else +#endif +#ifdef VAR_ARRAYS + spx_word16_t _in[N]; + spx_word16_t _out[N]; +#else + spx_word16_t _in[MAX_FFT_SIZE]; + spx_word16_t _out[MAX_FFT_SIZE]; +#endif + for (i=0;iN); + for (i=0;i<((struct kiss_config *)table)->N;i++) + out[i] = in[i]; + spx_drft_backward(&t, out); + spx_drft_clear(&t); + } +#endif +} + +#else + +void spx_fft_float(void *table, float *in, float *out) +{ + spx_fft(table, in, out); +} +void spx_ifft_float(void *table, float *in, float *out) +{ + spx_ifft(table, in, out); +} + +#endif diff --git a/Libraries/speex/fftwrap.h b/Libraries/speex/fftwrap.h new file mode 100644 index 000000000..dfaf48944 --- /dev/null +++ b/Libraries/speex/fftwrap.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2005 Jean-Marc Valin + File: fftwrap.h + + Wrapper for various FFTs + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FFTWRAP_H +#define FFTWRAP_H + +#include "arch.h" + +/** Compute tables for an FFT */ +void *spx_fft_init(int size); + +/** Destroy tables for an FFT */ +void spx_fft_destroy(void *table); + +/** Forward (real to half-complex) transform */ +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out); + +/** Backward (half-complex to real) transform */ +void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out); + +/** Forward (real to half-complex) transform of float data */ +void spx_fft_float(void *table, float *in, float *out); + +/** Backward (half-complex to real) transform of float data */ +void spx_ifft_float(void *table, float *in, float *out); + +#endif diff --git a/Libraries/speex/filterbank.c b/Libraries/speex/filterbank.c new file mode 100644 index 000000000..e2fb71d4b --- /dev/null +++ b/Libraries/speex/filterbank.c @@ -0,0 +1,227 @@ +/* Copyright (C) 2006 Jean-Marc Valin */ +/** + @file filterbank.c + @brief Converting between psd and filterbank + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "filterbank.h" +#include "arch.h" +#include +#include "math_approx.h" +#include "os_support.h" + +#ifdef FIXED_POINT + +#define toBARK(n) (MULT16_16(26829,spx_atan(SHR32(MULT16_16(97,n),2))) + MULT16_16(4588,spx_atan(MULT16_32_Q15(20,MULT16_16(n,n)))) + MULT16_16(3355,n)) + +#else +#define toBARK(n) (13.1f*atan(.00074f*(n))+2.24f*atan((n)*(n)*1.85e-8f)+1e-4f*(n)) +#endif + +#define toMEL(n) (2595.f*log10(1.f+(n)/700.f)) + +FilterBank *filterbank_new(int banks, spx_word32_t sampling, int len, int type) +{ + FilterBank *bank; + spx_word32_t df; + spx_word32_t max_mel, mel_interval; + int i; + int id1; + int id2; + df = DIV32(SHL32(sampling,15),MULT16_16(2,len)); + max_mel = toBARK(EXTRACT16(sampling/2)); + mel_interval = PDIV32(max_mel,banks-1); + + bank = (FilterBank*)speex_alloc(sizeof(FilterBank)); + bank->nb_banks = banks; + bank->len = len; + bank->bank_left = (int*)speex_alloc(len*sizeof(int)); + bank->bank_right = (int*)speex_alloc(len*sizeof(int)); + bank->filter_left = (spx_word16_t*)speex_alloc(len*sizeof(spx_word16_t)); + bank->filter_right = (spx_word16_t*)speex_alloc(len*sizeof(spx_word16_t)); + /* Think I can safely disable normalisation that for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + bank->scaling = (float*)speex_alloc(banks*sizeof(float)); +#endif + for (i=0;i max_mel) + break; +#ifdef FIXED_POINT + id1 = DIV32(mel,mel_interval); +#else + id1 = (int)(floor(mel/mel_interval)); +#endif + if (id1>banks-2) + { + id1 = banks-2; + val = Q15_ONE; + } else { + val = DIV32_16(mel - id1*mel_interval,EXTRACT16(PSHR32(mel_interval,15))); + } + id2 = id1+1; + bank->bank_left[i] = id1; + bank->filter_left[i] = SUB16(Q15_ONE,val); + bank->bank_right[i] = id2; + bank->filter_right[i] = val; + } + + /* Think I can safely disable normalisation for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + for (i=0;inb_banks;i++) + bank->scaling[i] = 0; + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + bank->scaling[id] += bank->filter_left[i]; + id = bank->bank_right[i]; + bank->scaling[id] += bank->filter_right[i]; + } + for (i=0;inb_banks;i++) + bank->scaling[i] = Q15_ONE/(bank->scaling[i]); +#endif + return bank; +} + +void filterbank_destroy(FilterBank *bank) +{ + speex_free(bank->bank_left); + speex_free(bank->bank_right); + speex_free(bank->filter_left); + speex_free(bank->filter_right); +#ifndef FIXED_POINT + speex_free(bank->scaling); +#endif + speex_free(bank); +} + +void filterbank_compute_bank32(FilterBank *bank, spx_word32_t *ps, spx_word32_t *mel) +{ + int i; + for (i=0;inb_banks;i++) + mel[i] = 0; + + for (i=0;ilen;i++) + { + int id; + id = bank->bank_left[i]; + mel[id] += MULT16_32_P15(bank->filter_left[i],ps[i]); + id = bank->bank_right[i]; + mel[id] += MULT16_32_P15(bank->filter_right[i],ps[i]); + } + /* Think I can safely disable normalisation that for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + /*for (i=0;inb_banks;i++) + mel[i] = MULT16_32_P15(Q15(bank->scaling[i]),mel[i]); + */ +#endif +} + +void filterbank_compute_psd16(FilterBank *bank, spx_word16_t *mel, spx_word16_t *ps) +{ + int i; + for (i=0;ilen;i++) + { + spx_word32_t tmp; + int id1, id2; + id1 = bank->bank_left[i]; + id2 = bank->bank_right[i]; + tmp = MULT16_16(mel[id1],bank->filter_left[i]); + tmp += MULT16_16(mel[id2],bank->filter_right[i]); + ps[i] = EXTRACT16(PSHR32(tmp,15)); + } +} + + +#ifndef FIXED_POINT +void filterbank_compute_bank(FilterBank *bank, float *ps, float *mel) +{ + int i; + for (i=0;inb_banks;i++) + mel[i] = 0; + + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + mel[id] += bank->filter_left[i]*ps[i]; + id = bank->bank_right[i]; + mel[id] += bank->filter_right[i]*ps[i]; + } + for (i=0;inb_banks;i++) + mel[i] *= bank->scaling[i]; +} + +void filterbank_compute_psd(FilterBank *bank, float *mel, float *ps) +{ + int i; + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + ps[i] = mel[id]*bank->filter_left[i]; + id = bank->bank_right[i]; + ps[i] += mel[id]*bank->filter_right[i]; + } +} + +void filterbank_psy_smooth(FilterBank *bank, float *ps, float *mask) +{ + /* Low freq slope: 14 dB/Bark*/ + /* High freq slope: 9 dB/Bark*/ + /* Noise vs tone: 5 dB difference */ + /* FIXME: Temporary kludge */ + float bark[100]; + int i; + /* Assumes 1/3 Bark resolution */ + float decay_low = 0.34145f; + float decay_high = 0.50119f; + filterbank_compute_bank(bank, ps, bark); + for (i=1;inb_banks;i++) + { + /*float decay_high = 13-1.6*log10(bark[i-1]); + decay_high = pow(10,(-decay_high/30.f));*/ + bark[i] = bark[i] + decay_high*bark[i-1]; + } + for (i=bank->nb_banks-2;i>=0;i--) + { + bark[i] = bark[i] + decay_low*bark[i+1]; + } + filterbank_compute_psd(bank, bark, mask); +} + +#endif diff --git a/Libraries/speex/filterbank.h b/Libraries/speex/filterbank.h new file mode 100644 index 000000000..3e889a22f --- /dev/null +++ b/Libraries/speex/filterbank.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2006 Jean-Marc Valin */ +/** + @file filterbank.h + @brief Converting between psd and filterbank + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FILTERBANK_H +#define FILTERBANK_H + +#include "arch.h" + +typedef struct { + int *bank_left; + int *bank_right; + spx_word16_t *filter_left; + spx_word16_t *filter_right; +#ifndef FIXED_POINT + float *scaling; +#endif + int nb_banks; + int len; +} FilterBank; + + +FilterBank *filterbank_new(int banks, spx_word32_t sampling, int len, int type); + +void filterbank_destroy(FilterBank *bank); + +void filterbank_compute_bank32(FilterBank *bank, spx_word32_t *ps, spx_word32_t *mel); + +void filterbank_compute_psd16(FilterBank *bank, spx_word16_t *mel, spx_word16_t *psd); + +#ifndef FIXED_POINT +void filterbank_compute_bank(FilterBank *bank, float *psd, float *mel); +void filterbank_compute_psd(FilterBank *bank, float *mel, float *psd); +#endif + + +#endif diff --git a/Libraries/speex/filters.c b/Libraries/speex/filters.c new file mode 100644 index 000000000..36ef4f697 --- /dev/null +++ b/Libraries/speex/filters.c @@ -0,0 +1,821 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: filters.c + Various analysis/synthesis filters + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "filters.h" +#include "stack_alloc.h" +#include "arch.h" +#include "math_approx.h" +#include "ltp.h" +#include + +#ifdef _USE_SSE +#include "filters_sse.h" +#elif defined (ARM4_ASM) || defined(ARM5E_ASM) +#include "filters_arm4.h" +#elif defined (BFIN_ASM) +#include "filters_bfin.h" +#endif + + + +void bw_lpc(spx_word16_t gamma, const spx_coef_t *lpc_in, spx_coef_t *lpc_out, int order) +{ + int i; + spx_word16_t tmp=gamma; + for (i=0;i=min_val && vec[i] <= max_val)) + { + if (vec[i] < min_val) + vec[i] = min_val; + else if (vec[i] > max_val) + vec[i] = max_val; + else /* Has to be NaN */ + vec[i] = 0; + } + } +} + +void highpass(const spx_word16_t *x, spx_word16_t *y, int len, int filtID, spx_mem_t *mem) +{ + int i; +#ifdef FIXED_POINT + const spx_word16_t Pcoef[5][3] = {{16384, -31313, 14991}, {16384, -31569, 15249}, {16384, -31677, 15328}, {16384, -32313, 15947}, {16384, -22446, 6537}}; + const spx_word16_t Zcoef[5][3] = {{15672, -31344, 15672}, {15802, -31601, 15802}, {15847, -31694, 15847}, {16162, -32322, 16162}, {14418, -28836, 14418}}; +#else + const spx_word16_t Pcoef[5][3] = {{1.00000f, -1.91120f, 0.91498f}, {1.00000f, -1.92683f, 0.93071f}, {1.00000f, -1.93338f, 0.93553f}, {1.00000f, -1.97226f, 0.97332f}, {1.00000f, -1.37000f, 0.39900f}}; + const spx_word16_t Zcoef[5][3] = {{0.95654f, -1.91309f, 0.95654f}, {0.96446f, -1.92879f, 0.96446f}, {0.96723f, -1.93445f, 0.96723f}, {0.98645f, -1.97277f, 0.98645f}, {0.88000f, -1.76000f, 0.88000f}}; +#endif + const spx_word16_t *den, *num; + if (filtID>4) + filtID=4; + + den = Pcoef[filtID]; num = Zcoef[filtID]; + /*return;*/ + for (i=0;i SHL32(EXTEND32(SIG_SCALING), 8)) + { + spx_word16_t scale_1; + scale = PSHR32(scale, SIG_SHIFT); + scale_1 = EXTRACT16(PDIV32_16(SHL32(EXTEND32(SIG_SCALING),7),scale)); + for (i=0;i SHR32(EXTEND32(SIG_SCALING), 2)) { + spx_word16_t scale_1; + scale = PSHR32(scale, SIG_SHIFT-5); + scale_1 = DIV32_16(SHL32(EXTEND32(SIG_SCALING),3),scale); + for (i=0;i max_val) + max_val = tmp; + } + + sig_shift=0; + while (max_val>16383) + { + sig_shift++; + max_val >>= 1; + } + + for (i=0;i max_val) + max_val = tmp; + } + if (max_val>16383) + { + spx_word32_t sum=0; + for (i=0;i= max_val) + max_val = tmp; + } + + sig_shift=0; + while (max_val>max_scale) + { + sig_shift++; + max_val >>= 1; + } + + for (i=0;i>1; + for (i=0;i>1; + N2 = N>>1; + ALLOC(xx1, M2+N2, spx_word16_t); + ALLOC(xx2, M2+N2, spx_word16_t); + + for (i = 0; i < N2; i++) + xx1[i] = x1[N2-1-i]; + for (i = 0; i < M2; i++) + xx1[N2+i] = mem1[2*i+1]; + for (i = 0; i < N2; i++) + xx2[i] = x2[N2-1-i]; + for (i = 0; i < M2; i++) + xx2[N2+i] = mem2[2*i+1]; + + for (i = 0; i < N2; i += 2) { + spx_sig_t y0, y1, y2, y3; + spx_word16_t x10, x20; + + y0 = y1 = y2 = y3 = 0; + x10 = xx1[N2-2-i]; + x20 = xx2[N2-2-i]; + + for (j = 0; j < M2; j += 2) { + spx_word16_t x11, x21; + spx_word16_t a0, a1; + + a0 = a[2*j]; + a1 = a[2*j+1]; + x11 = xx1[N2-1+j-i]; + x21 = xx2[N2-1+j-i]; + +#ifdef FIXED_POINT + /* We multiply twice by the same coef to avoid overflows */ + y0 = MAC16_16(MAC16_16(y0, a0, x11), NEG16(a0), x21); + y1 = MAC16_16(MAC16_16(y1, a1, x11), a1, x21); + y2 = MAC16_16(MAC16_16(y2, a0, x10), NEG16(a0), x20); + y3 = MAC16_16(MAC16_16(y3, a1, x10), a1, x20); +#else + y0 = ADD32(y0,MULT16_16(a0, x11-x21)); + y1 = ADD32(y1,MULT16_16(a1, x11+x21)); + y2 = ADD32(y2,MULT16_16(a0, x10-x20)); + y3 = ADD32(y3,MULT16_16(a1, x10+x20)); +#endif + a0 = a[2*j+2]; + a1 = a[2*j+3]; + x10 = xx1[N2+j-i]; + x20 = xx2[N2+j-i]; + +#ifdef FIXED_POINT + /* We multiply twice by the same coef to avoid overflows */ + y0 = MAC16_16(MAC16_16(y0, a0, x10), NEG16(a0), x20); + y1 = MAC16_16(MAC16_16(y1, a1, x10), a1, x20); + y2 = MAC16_16(MAC16_16(y2, a0, x11), NEG16(a0), x21); + y3 = MAC16_16(MAC16_16(y3, a1, x11), a1, x21); +#else + y0 = ADD32(y0,MULT16_16(a0, x10-x20)); + y1 = ADD32(y1,MULT16_16(a1, x10+x20)); + y2 = ADD32(y2,MULT16_16(a0, x11-x21)); + y3 = ADD32(y3,MULT16_16(a1, x11+x21)); +#endif + } +#ifdef FIXED_POINT + y[2*i] = EXTRACT16(SATURATE32(PSHR32(y0,15),32767)); + y[2*i+1] = EXTRACT16(SATURATE32(PSHR32(y1,15),32767)); + y[2*i+2] = EXTRACT16(SATURATE32(PSHR32(y2,15),32767)); + y[2*i+3] = EXTRACT16(SATURATE32(PSHR32(y3,15),32767)); +#else + /* Normalize up explicitly if we're in float */ + y[2*i] = 2.f*y0; + y[2*i+1] = 2.f*y1; + y[2*i+2] = 2.f*y2; + y[2*i+3] = 2.f*y3; +#endif + } + + for (i = 0; i < M2; i++) + mem1[2*i+1] = xx1[i]; + for (i = 0; i < M2; i++) + mem2[2*i+1] = xx2[i]; +} + +#ifdef FIXED_POINT +#if 0 +const spx_word16_t shift_filt[3][7] = {{-33, 1043, -4551, 19959, 19959, -4551, 1043}, + {-98, 1133, -4425, 29179, 8895, -2328, 444}, + {444, -2328, 8895, 29179, -4425, 1133, -98}}; +#else +const spx_word16_t shift_filt[3][7] = {{-390, 1540, -4993, 20123, 20123, -4993, 1540}, + {-1064, 2817, -6694, 31589, 6837, -990, -209}, + {-209, -990, 6837, 31589, -6694, 2817, -1064}}; +#endif +#else +#if 0 +const float shift_filt[3][7] = {{-9.9369e-04, 3.1831e-02, -1.3889e-01, 6.0910e-01, 6.0910e-01, -1.3889e-01, 3.1831e-02}, + {-0.0029937, 0.0345613, -0.1350474, 0.8904793, 0.2714479, -0.0710304, 0.0135403}, + {0.0135403, -0.0710304, 0.2714479, 0.8904793, -0.1350474, 0.0345613, -0.0029937}}; +#else +const float shift_filt[3][7] = {{-0.011915f, 0.046995f, -0.152373f, 0.614108f, 0.614108f, -0.152373f, 0.046995f}, + {-0.0324855f, 0.0859768f, -0.2042986f, 0.9640297f, 0.2086420f, -0.0302054f, -0.0063646f}, + {-0.0063646f, -0.0302054f, 0.2086420f, 0.9640297f, -0.2042986f, 0.0859768f, -0.0324855f}}; +#endif +#endif + +int interp_pitch( +spx_word16_t *exc, /*decoded excitation*/ +spx_word16_t *interp, /*decoded excitation*/ +int pitch, /*pitch period*/ +int len +) +{ + int i,j,k; + spx_word32_t corr[4][7]; + spx_word32_t maxcorr; + int maxi, maxj; + for (i=0;i<7;i++) + { + corr[0][i] = inner_prod(exc, exc-pitch-3+i, len); + } + for (i=0;i<3;i++) + { + for (j=0;j<7;j++) + { + int i1, i2; + spx_word32_t tmp=0; + i1 = 3-j; + if (i1<0) + i1 = 0; + i2 = 10-j; + if (i2>7) + i2 = 7; + for (k=i1;k maxcorr) + { + maxcorr = corr[i][j]; + maxi=i; + maxj=j; + } + } + } + for (i=0;i0) + { + for (k=0;k<7;k++) + { + tmp += MULT16_16(exc[i-(pitch-maxj+3)+k-3],shift_filt[maxi-1][k]); + } + } else { + tmp = SHL32(exc[i-(pitch-maxj+3)],15); + } + interp[i] = PSHR32(tmp,15); + } + return pitch-maxj+3; +} + +void multicomb( +spx_word16_t *exc, /*decoded excitation*/ +spx_word16_t *new_exc, /*enhanced excitation*/ +spx_coef_t *ak, /*LPC filter coefs*/ +int p, /*LPC order*/ +int nsf, /*sub-frame size*/ +int pitch, /*pitch period*/ +int max_pitch, +spx_word16_t comb_gain, /*gain of comb filter*/ +char *stack +) +{ + int i; + VARDECL(spx_word16_t *iexc); + spx_word16_t old_ener, new_ener; + int corr_pitch; + + spx_word16_t iexc0_mag, iexc1_mag, exc_mag; + spx_word32_t corr0, corr1; + spx_word16_t gain0, gain1; + spx_word16_t pgain1, pgain2; + spx_word16_t c1, c2; + spx_word16_t g1, g2; + spx_word16_t ngain; + spx_word16_t gg1, gg2; +#ifdef FIXED_POINT + int scaledown=0; +#endif +#if 0 /* Set to 1 to enable full pitch search */ + int nol_pitch[6]; + spx_word16_t nol_pitch_coef[6]; + spx_word16_t ol_pitch_coef; + open_loop_nbest_pitch(exc, 20, 120, nsf, + nol_pitch, nol_pitch_coef, 6, stack); + corr_pitch=nol_pitch[0]; + ol_pitch_coef = nol_pitch_coef[0]; + /*Try to remove pitch multiples*/ + for (i=1;i<6;i++) + { +#ifdef FIXED_POINT + if ((nol_pitch_coef[i]>MULT16_16_Q15(nol_pitch_coef[0],19661)) && +#else + if ((nol_pitch_coef[i]>.6*nol_pitch_coef[0]) && +#endif + (ABS(2*nol_pitch[i]-corr_pitch)<=2 || ABS(3*nol_pitch[i]-corr_pitch)<=3 || + ABS(4*nol_pitch[i]-corr_pitch)<=4 || ABS(5*nol_pitch[i]-corr_pitch)<=5)) + { + corr_pitch = nol_pitch[i]; + } + } +#else + corr_pitch = pitch; +#endif + + ALLOC(iexc, 2*nsf, spx_word16_t); + + interp_pitch(exc, iexc, corr_pitch, 80); + if (corr_pitch>max_pitch) + interp_pitch(exc, iexc+nsf, 2*corr_pitch, 80); + else + interp_pitch(exc, iexc+nsf, -corr_pitch, 80); + +#ifdef FIXED_POINT + for (i=0;i16383) + { + scaledown = 1; + break; + } + } + if (scaledown) + { + for (i=0;i MULT16_16(iexc0_mag,exc_mag)) + pgain1 = QCONST16(1., 14); + else + pgain1 = PDIV32_16(SHL32(PDIV32(corr0, exc_mag),14),iexc0_mag); + if (corr1 > MULT16_16(iexc1_mag,exc_mag)) + pgain2 = QCONST16(1., 14); + else + pgain2 = PDIV32_16(SHL32(PDIV32(corr1, exc_mag),14),iexc1_mag); + gg1 = PDIV32_16(SHL32(EXTEND32(exc_mag),8), iexc0_mag); + gg2 = PDIV32_16(SHL32(EXTEND32(exc_mag),8), iexc1_mag); + if (comb_gain>0) + { +#ifdef FIXED_POINT + c1 = (MULT16_16_Q15(QCONST16(.4,15),comb_gain)+QCONST16(.07,15)); + c2 = QCONST16(.5,15)+MULT16_16_Q14(QCONST16(1.72,14),(c1-QCONST16(.07,15))); +#else + c1 = .4*comb_gain+.07; + c2 = .5+1.72*(c1-.07); +#endif + } else + { + c1=c2=0; + } +#ifdef FIXED_POINT + g1 = 32767 - MULT16_16_Q13(MULT16_16_Q15(c2, pgain1),pgain1); + g2 = 32767 - MULT16_16_Q13(MULT16_16_Q15(c2, pgain2),pgain2); +#else + g1 = 1-c2*pgain1*pgain1; + g2 = 1-c2*pgain2*pgain2; +#endif + if (g1max_pitch) + { + gain0 = MULT16_16_Q15(QCONST16(.7,15),MULT16_16_Q14(g1,gg1)); + gain1 = MULT16_16_Q15(QCONST16(.3,15),MULT16_16_Q14(g2,gg2)); + } else { + gain0 = MULT16_16_Q15(QCONST16(.6,15),MULT16_16_Q14(g1,gg1)); + gain1 = MULT16_16_Q15(QCONST16(.6,15),MULT16_16_Q14(g2,gg2)); + } + for (i=0;i new_ener) + old_ener = new_ener; + ngain = PDIV32_16(SHL32(EXTEND32(old_ener),14),new_ener); + + for (i=0;imax_scale) + { + sig_shift++; + max_val >>= 1; + } + + __asm__ __volatile__ ( + ".normalize16loop%=: \n" + + "\tldr %4, [%0], #4 \n" + "\tldr %5, [%0], #4 \n" + "\tmov %4, %4, asr %3 \n" + "\tstrh %4, [%1], #2 \n" + "\tldr %4, [%0], #4 \n" + "\tmov %5, %5, asr %3 \n" + "\tstrh %5, [%1], #2 \n" + "\tldr %5, [%0], #4 \n" + "\tmov %4, %4, asr %3 \n" + "\tstrh %4, [%1], #2 \n" + "\tsubs %2, %2, #1 \n" + "\tmov %5, %5, asr %3 \n" + "\tstrh %5, [%1], #2 \n" + + "\tbgt .normalize16loop%=\n" + : "=r" (dead1), "=r" (dead2), "=r" (dead3), "=r" (dead4), + "=r" (dead5), "=r" (dead6) + : "0" (x), "1" (y), "2" (len>>2), "3" (sig_shift) + : "cc", "memory"); + return sig_shift; +} + diff --git a/Libraries/speex/filters_bfin.h b/Libraries/speex/filters_bfin.h new file mode 100644 index 000000000..1e433ee16 --- /dev/null +++ b/Libraries/speex/filters_bfin.h @@ -0,0 +1,515 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file filters_bfin.h + @brief Various analysis/synthesis filters (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_NORMALIZE16 +int normalize16(const spx_sig_t *x, spx_word16_t *y, spx_sig_t max_scale, int len) +{ + spx_sig_t max_val=1; + int sig_shift; + __asm__ + ( + "%0 = 0;\n\t" + "I0 = %1;\n\t" + "L0 = 0;\n\t" + "R1 = [I0++];\n\t" + "LOOP norm_max%= LC0 = %2;\n\t" + "LOOP_BEGIN norm_max%=;\n\t" + "R2 = ABS R1 || R1 = [I0++];\n\t" + "%0 = MAX(%0, R2);\n\t" + "LOOP_END norm_max%=;\n\t" + : "=&d" (max_val) + : "a" (x), "a" (len) + : "R1", "R2" + ); + + sig_shift=0; + while (max_val>max_scale) + { + sig_shift++; + max_val >>= 1; + } + + __asm__ __volatile__ + ( + "I0 = %0;\n\t" + "L0 = 0;\n\t" + "P1 = %1;\n\t" + "R0 = [I0++];\n\t" + "LOOP norm_shift%= LC0 = %3;\n\t" + "LOOP_BEGIN norm_shift%=;\n\t" + "R1 = ASHIFT R0 by %2.L || R0 = [I0++];\n\t" + "W[P1++] = R1;\n\t" + "LOOP_END norm_shift%=;\n\t" + "R1 = ASHIFT R0 by %2.L;\n\t" + "W[P1++] = R1;\n\t" + : : "a" (x), "a" (y), "d" (-sig_shift), "a" (len-1) + : "I0", "L0", "P1", "R0", "R1", "memory" + ); + return sig_shift; +} + + + +#define OVERRIDE_FILTER_MEM16 +void filter_mem16(const spx_word16_t *_x, const spx_coef_t *num, const spx_coef_t *den, spx_word16_t *_y, int N, int ord, spx_mem_t *mem, char *stack) +{ + VARDECL(spx_word32_t *xy2); + VARDECL(spx_word32_t *numden_a); + spx_word32_t *xy; + spx_word16_t *numden; + int i; + + ALLOC(xy2, (N+1), spx_word32_t); + ALLOC(numden_a, (2*ord+2), spx_word32_t); + xy = xy2+1; + numden = (spx_word16_t*) numden_a; + + for (i=0;i>> 13;\n\t" + "W[%0] = R3.L;\n\t" + "R0 <<= 1;\n\t" + "R1 = R1 + R0;\n\t" + "R1 >>>= 13;\n\t" + "W[%1] = R1.L;\n\t" + "LOOP_END samples%=;\n\t" + : "=a" (ytmp2), "=a" (y) + : "a" (awk2), "a" (ak), "d" (ord), "m" (N), "0" (ytmp2), "1" (y) + : "A0", "A1", "R0", "R1", "R2", "R3", "I0", "I1", "I2", "I3", "L0", "L1", "L2", "L3", "A0", "A1" + ); +} + + + +#if 0 /* Equivalent C function for filter_mem2 and compute_impulse_response */ +#define min(a,b) ((a)<(b) ? (a):(b)) + +void compute_impulse_response(const spx_coef_t *ak, const spx_coef_t *awk1, const spx_coef_t *awk2, spx_word16_t *y, int N, int ord, char *stack) +{ + int i,j; + VARDECL(spx_word16_t *ytmp); + ALLOC(ytmp, N, spx_word16_t); + + y[0] = LPC_SCALING; + for (i=0;i + +void filter_mem16_10(const float *x, const float *_num, const float *_den, float *y, int N, int ord, float *_mem) +{ + __m128 num[3], den[3], mem[3]; + + int i; + + /* Copy numerator, denominator and memory to aligned xmm */ + for (i=0;i<2;i++) + { + mem[i] = _mm_loadu_ps(_mem+4*i); + num[i] = _mm_loadu_ps(_num+4*i); + den[i] = _mm_loadu_ps(_den+4*i); + } + mem[2] = _mm_setr_ps(_mem[8], _mem[9], 0, 0); + num[2] = _mm_setr_ps(_num[8], _num[9], 0, 0); + den[2] = _mm_setr_ps(_den[8], _den[9], 0, 0); + + for (i=0;i>1; + __asm__ ( + "P0 = 15;\n\t" + "R0 = %1;\n\t" + "R1 = %2;\n\t" + //"R0 = R0 + R1;\n\t" + "R0 <<= 1;\n\t" + "DIVS (R0, R1);\n\t" + "LOOP divide%= LC0 = P0;\n\t" + "LOOP_BEGIN divide%=;\n\t" + "DIVQ (R0, R1);\n\t" + "LOOP_END divide%=;\n\t" + "R0 = R0.L;\n\t" + "%0 = R0;\n\t" + : "=m" (res) + : "m" (a), "m" (bb) + : "P0", "R0", "R1", "cc"); + return res; +} + +#undef DIV32_16 +static inline spx_word16_t DIV32_16(spx_word32_t a, spx_word16_t b) +{ + spx_word32_t res, bb; + bb = b; + /* Make the roundinf consistent with the C version + (do we need to do that?)*/ + if (a<0) + a += (b-1); + __asm__ ( + "P0 = 15;\n\t" + "R0 = %1;\n\t" + "R1 = %2;\n\t" + "R0 <<= 1;\n\t" + "DIVS (R0, R1);\n\t" + "LOOP divide%= LC0 = P0;\n\t" + "LOOP_BEGIN divide%=;\n\t" + "DIVQ (R0, R1);\n\t" + "LOOP_END divide%=;\n\t" + "R0 = R0.L;\n\t" + "%0 = R0;\n\t" + : "=m" (res) + : "m" (a), "m" (bb) + : "P0", "R0", "R1", "cc"); + return res; +} + +#undef MAX16 +static inline spx_word16_t MAX16(spx_word16_t a, spx_word16_t b) +{ + spx_word32_t res; + __asm__ ( + "%1 = %1.L (X);\n\t" + "%2 = %2.L (X);\n\t" + "%0 = MAX(%1,%2);" + : "=d" (res) + : "%d" (a), "d" (b) + ); + return res; +} + +#undef MULT16_32_Q15 +static inline spx_word32_t MULT16_32_Q15(spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H) ;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b) + : "A1" + ); + return res; +} + +#undef MAC16_32_Q15 +static inline spx_word32_t MAC16_32_Q15(spx_word32_t c, spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H);\n\t" + "%0 = %0 + %4;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b), "d" (c) + : "A1" + ); + return res; +} + +#undef MULT16_32_Q14 +static inline spx_word32_t MULT16_32_Q14(spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "%2 <<= 1;\n\t" + "A1 = %1.L*%2.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %1.L*%2.H);\n\t" + : "=W" (res), "=d" (a), "=d" (b) + : "1" (a), "2" (b) + : "A1" + ); + return res; +} + +#undef MAC16_32_Q14 +static inline spx_word32_t MAC16_32_Q14(spx_word32_t c, spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "%1 <<= 1;\n\t" + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H);\n\t" + "%0 = %0 + %4;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b), "d" (c) + : "A1" + ); + return res; +} + +#endif diff --git a/Libraries/speex/fixed_debug.h b/Libraries/speex/fixed_debug.h new file mode 100644 index 000000000..54f3866e8 --- /dev/null +++ b/Libraries/speex/fixed_debug.h @@ -0,0 +1,487 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_debug.h + @brief Fixed-point operations with debugging +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_DEBUG_H +#define FIXED_DEBUG_H + +#include + +extern long long spx_mips; +#define MIPS_INC spx_mips++, + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + + +#define VERIFY_SHORT(x) ((x)<=32767&&(x)>=-32768) +#define VERIFY_INT(x) ((x)<=2147483647LL&&(x)>=-2147483648LL) + +static inline short NEG16(int x) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "NEG16: input is not short: %d\n", (int)x); + } + res = -x; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "NEG16: output is not short: %d\n", (int)res); + spx_mips++; + return res; +} +static inline int NEG32(long long x) +{ + long long res; + if (!VERIFY_INT(x)) + { + fprintf (stderr, "NEG16: input is not int: %d\n", (int)x); + } + res = -x; + if (!VERIFY_INT(res)) + fprintf (stderr, "NEG16: output is not int: %d\n", (int)res); + spx_mips++; + return res; +} + +#define EXTRACT16(x) _EXTRACT16(x, __FILE__, __LINE__) +static inline short _EXTRACT16(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTRACT16: input is not short: %d in %s: line %d\n", x, file, line); + } + res = x; + spx_mips++; + return res; +} + +#define EXTEND32(x) _EXTEND32(x, __FILE__, __LINE__) +static inline int _EXTEND32(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTEND32: input is not short: %d in %s: line %d\n", x, file, line); + } + res = x; + spx_mips++; + return res; +} + +#define SHR16(a, shift) _SHR16(a, shift, __FILE__, __LINE__) +static inline short _SHR16(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHR16: inputs are not short: %d >> %d in %s: line %d\n", a, shift, file, line); + } + res = a>>shift; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "SHR16: output is not short: %d in %s: line %d\n", res, file, line); + spx_mips++; + return res; +} +#define SHL16(a, shift) _SHL16(a, shift, __FILE__, __LINE__) +static inline short _SHL16(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL16: inputs are not short: %d %d in %s: line %d\n", a, shift, file, line); + } + res = a<>shift; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SHR32: output is not int: %d\n", (int)res); + } + spx_mips++; + return res; +} +static inline int SHL32(long long a, int shift) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL32: inputs are not int: %d %d\n", (int)a, shift); + } + res = a<>1))),shift)) +#define PSHR32(a,shift) (SHR32(ADD32((a),((EXTEND32(1)<<((shift))>>1))),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +//#define SHR(a,shift) ((a) >> (shift)) +//#define SHL(a,shift) ((a) << (shift)) + +#define ADD16(a, b) _ADD16(a, b, __FILE__, __LINE__) +static inline short _ADD16(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "ADD16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = a+b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "ADD16: output is not short: %d+%d=%d in %s: line %d\n", a,b,res, file, line); + } + spx_mips++; + return res; +} + +#define SUB16(a, b) _SUB16(a, b, __FILE__, __LINE__) +static inline short _SUB16(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "SUB16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = a-b; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "SUB16: output is not short: %d in %s: line %d\n", res, file, line); + spx_mips++; + return res; +} + +#define ADD32(a, b) _ADD32(a, b, __FILE__, __LINE__) +static inline int _ADD32(long long a, long long b, char *file, int line) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "ADD32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a+b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "ADD32: output is not int: %d in %s: line %d\n", (int)res, file, line); + } + spx_mips++; + return res; +} + +static inline int SUB32(long long a, long long b) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "SUB32: inputs are not int: %d %d\n", (int)a, (int)b); + } + res = a-b; + if (!VERIFY_INT(res)) + fprintf (stderr, "SUB32: output is not int: %d\n", (int)res); + spx_mips++; + return res; +} + +#define ADD64(a,b) (MIPS_INC(a)+(b)) + +/* result fits in 16 bits */ +static inline short MULT16_16_16(int a, int b) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_16: inputs are not short: %d %d\n", a, b); + } + res = a*b; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_16: output is not short: %d\n", res); + spx_mips++; + return res; +} + +#define MULT16_16(a, b) _MULT16_16(a, b, __FILE__, __LINE__) +static inline int _MULT16_16(int a, int b, char *file, int line) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = ((long long)a)*b; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16: output is not int: %d in %s: line %d\n", (int)res, file, line); + spx_mips++; + return res; +} + +#define MAC16_16(c,a,b) (spx_mips--,ADD32((c),MULT16_16((a),(b)))) +#define MAC16_16_Q11(c,a,b) (EXTRACT16(ADD16((c),EXTRACT16(SHR32(MULT16_16((a),(b)),11))))) +#define MAC16_16_Q13(c,a,b) (EXTRACT16(ADD16((c),EXTRACT16(SHR32(MULT16_16((a),(b)),13))))) +#define MAC16_16_P13(c,a,b) (EXTRACT16(ADD32((c),SHR32(ADD32(4096,MULT16_16((a),(b))),13)))) + + +#define MULT16_32_QX(a, b, Q) _MULT16_32_QX(a, b, Q, __FILE__, __LINE__) +static inline int _MULT16_32_QX(int a, long long b, int Q, char *file, int line) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_Q%d: inputs are not short+int: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); + } + if (ABS32(b)>=(EXTEND32(1)<<(15+Q))) + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); + res = (((long long)a)*(long long)b) >> Q; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_32_Q%d: output is not int: %d*%d=%d in %s: line %d\n", Q, (int)a, (int)b,(int)res, file, line); + spx_mips+=5; + return res; +} + +static inline int MULT16_32_PX(int a, long long b, int Q) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_P%d: inputs are not short+int: %d %d\n", Q, (int)a, (int)b); + } + if (ABS32(b)>=(EXTEND32(1)<<(15+Q))) + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d\n", Q, (int)a, (int)b); + res = ((((long long)a)*(long long)b) + ((EXTEND32(1)<>1))>> Q; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_32_P%d: output is not int: %d*%d=%d\n", Q, (int)a, (int)b,(int)res); + spx_mips+=5; + return res; +} + + +#define MULT16_32_Q11(a,b) MULT16_32_QX(a,b,11) +#define MAC16_32_Q11(c,a,b) ADD32((c),MULT16_32_Q11((a),(b))) +#define MULT16_32_Q12(a,b) MULT16_32_QX(a,b,12) +#define MULT16_32_Q13(a,b) MULT16_32_QX(a,b,13) +#define MULT16_32_Q14(a,b) MULT16_32_QX(a,b,14) +#define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) +#define MULT16_32_P15(a,b) MULT16_32_PX(a,b,15) +#define MAC16_32_Q15(c,a,b) ADD32((c),MULT16_32_Q15((a),(b))) + +static inline int SATURATE(int a, int b) +{ + if (a>b) + a=b; + if (a<-b) + a = -b; + return a; +} + +static inline int MULT16_16_Q11_32(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q11: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 11; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_Q11: output is not short: %d*%d=%d\n", (int)a, (int)b, (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q13(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q13: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 13; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_Q13: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q14(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q14: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 14; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_Q14: output is not short: %d\n", (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q15(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q15: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q15: output is not short: %d\n", (int)res); + } + spx_mips+=3; + return res; +} + +static inline short MULT16_16_P13(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P13: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 4096; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P13: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 13; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P13: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} +static inline short MULT16_16_P14(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P14: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 8192; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P14: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 14; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P14: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} +static inline short MULT16_16_P15(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P15: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 16384; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P15: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 15; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P15: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} + +#define DIV32_16(a, b) _DIV32_16(a, b, __FILE__, __LINE__) + +static inline int _DIV32_16(long long a, long long b, char *file, int line) +{ + long long res; + if (b==0) + { + fprintf(stderr, "DIV32_16: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); + return 0; + } + if (!VERIFY_INT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "DIV32_16: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a/b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "DIV32_16: output is not short: %d / %d = %d in %s: line %d\n", (int)a,(int)b,(int)res, file, line); + if (res>32767) + res = 32767; + if (res<-32768) + res = -32768; + } + spx_mips+=20; + return res; +} + +#define DIV32(a, b) _DIV32(a, b, __FILE__, __LINE__) +static inline int _DIV32(long long a, long long b, char *file, int line) +{ + long long res; + if (b==0) + { + fprintf(stderr, "DIV32: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); + return 0; + } + + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "DIV32: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a/b; + if (!VERIFY_INT(res)) + fprintf (stderr, "DIV32: output is not int: %d in %s: line %d\n", (int)res, file, line); + spx_mips+=36; + return res; +} +#define PDIV32(a,b) DIV32(ADD32((a),(b)>>1),b) +#define PDIV32_16(a,b) DIV32_16(ADD32((a),(b)>>1),b) + +#endif diff --git a/Libraries/speex/fixed_generic.h b/Libraries/speex/fixed_generic.h new file mode 100644 index 000000000..3fb096ed9 --- /dev/null +++ b/Libraries/speex/fixed_generic.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) ((spx_word16_t)(x)) +#define EXTEND32(x) ((spx_word32_t)(x)) +#define SHR16(a,shift) ((a) >> (shift)) +#define SHL16(a,shift) ((a) << (shift)) +#define SHR32(a,shift) ((a) >> (shift)) +#define SHL32(a,shift) ((a) << (shift)) +#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift)) +#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) ((spx_word32_t)(a) << (shift)) +#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + + +#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b))) +#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b)) +#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b)) +#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b)) + + +/* result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b)))) + +/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ +#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) + +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12)) +#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13)) +#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14)) + +#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)) +#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))) + +#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + + +#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) +#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13))) +#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15)) + +#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b)))) +#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b)))) +#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b))) +#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b))) + +#endif diff --git a/Libraries/speex/gain_table.c b/Libraries/speex/gain_table.c new file mode 100644 index 000000000..00b824425 --- /dev/null +++ b/Libraries/speex/gain_table.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: gain_table.c + Codebook for 3-tap pitch prediction gain (128 entries) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char gain_cdbk_nb[512] = { +-32, -32, -32, 0, +-28, -67, -5, 33, +-42, -6, -32, 18, +-57, -10, -54, 35, +-16, 27, -41, 42, +19, -19, -40, 36, +-45, 24, -21, 40, +-8, -14, -18, 28, +1, 14, -58, 53, +-18, -88, -39, 39, +-38, 21, -18, 37, +-19, 20, -43, 38, +10, 17, -48, 54, +-52, -58, -13, 33, +-44, -1, -11, 32, +-12, -11, -34, 22, +14, 0, -46, 46, +-37, -35, -34, 5, +-25, 44, -30, 43, +6, -4, -63, 49, +-31, 43, -41, 43, +-23, 30, -43, 41, +-43, 26, -14, 44, +-33, 1, -13, 27, +-13, 18, -37, 37, +-46, -73, -45, 34, +-36, 24, -25, 34, +-36, -11, -20, 19, +-25, 12, -18, 33, +-36, -69, -59, 34, +-45, 6, 8, 46, +-22, -14, -24, 18, +-1, 13, -44, 44, +-39, -48, -26, 15, +-32, 31, -37, 34, +-33, 15, -46, 31, +-24, 30, -36, 37, +-41, 31, -23, 41, +-50, 22, -4, 50, +-22, 2, -21, 28, +-17, 30, -34, 40, +-7, -60, -28, 29, +-38, 42, -28, 42, +-44, -11, 21, 43, +-16, 8, -44, 34, +-39, -55, -43, 21, +-11, -35, 26, 41, +-9, 0, -34, 29, +-8, 121, -81, 113, +7, -16, -22, 33, +-37, 33, -31, 36, +-27, -7, -36, 17, +-34, 70, -57, 65, +-37, -11, -48, 21, +-40, 17, -1, 44, +-33, 6, -6, 33, +-9, 0, -20, 34, +-21, 69, -33, 57, +-29, 33, -31, 35, +-55, 12, -1, 49, +-33, 27, -22, 35, +-50, -33, -47, 17, +-50, 54, 51, 94, +-1, -5, -44, 35, +-4, 22, -40, 45, +-39, -66, -25, 24, +-33, 1, -26, 20, +-24, -23, -25, 12, +-11, 21, -45, 44, +-25, -45, -19, 17, +-43, 105, -16, 82, +5, -21, 1, 41, +-16, 11, -33, 30, +-13, -99, -4, 57, +-37, 33, -15, 44, +-25, 37, -63, 54, +-36, 24, -31, 31, +-53, -56, -38, 26, +-41, -4, 4, 37, +-33, 13, -30, 24, +49, 52, -94, 114, +-5, -30, -15, 23, +1, 38, -40, 56, +-23, 12, -36, 29, +-17, 40, -47, 51, +-37, -41, -39, 11, +-49, 34, 0, 58, +-18, -7, -4, 34, +-16, 17, -27, 35, +30, 5, -62, 65, +4, 48, -68, 76, +-43, 11, -11, 38, +-18, 19, -15, 41, +-23, -62, -39, 23, +-42, 10, -2, 41, +-21, -13, -13, 25, +-9, 13, -47, 42, +-23, -62, -24, 24, +-44, 60, -21, 58, +-18, -3, -52, 32, +-22, 22, -36, 34, +-75, 57, 16, 90, +-19, 3, 10, 45, +-29, 23, -38, 32, +-5, -62, -51, 38, +-51, 40, -18, 53, +-42, 13, -24, 32, +-34, 14, -20, 30, +-56, -75, -26, 37, +-26, 32, 15, 59, +-26, 17, -29, 29, +-7, 28, -52, 53, +-12, -30, 5, 30, +-5, -48, -5, 35, +2, 2, -43, 40, +21, 16, 16, 75, +-25, -45, -32, 10, +-43, 18, -10, 42, +9, 0, -1, 52, +-1, 7, -30, 36, +19, -48, -4, 48, +-28, 25, -29, 32, +-22, 0, -31, 22, +-32, 17, -10, 36, +-64, -41, -62, 36, +-52, 15, 16, 58, +-30, -22, -32, 6, +-7, 9, -38, 36}; diff --git a/Libraries/speex/gain_table_lbr.c b/Libraries/speex/gain_table_lbr.c new file mode 100644 index 000000000..3c1c3dba9 --- /dev/null +++ b/Libraries/speex/gain_table_lbr.c @@ -0,0 +1,64 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: gain_table_lbr.c + Codebook for 3-tap pitch prediction gain (32 entries) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char gain_cdbk_lbr[128] = { +-32, -32, -32, 0, +-31, -58, -16, 22, +-41, -24, -43, 14, +-56, -22, -55, 29, +-13, 33, -41, 47, +-4, -39, -9, 29, +-41, 15, -12, 38, +-8, -15, -12, 31, +1, 2, -44, 40, +-22, -66, -42, 27, +-38, 28, -23, 38, +-21, 14, -37, 31, +0, 21, -50, 52, +-53, -71, -27, 33, +-37, -1, -19, 25, +-19, -5, -28, 22, +6, 65, -44, 74, +-33, -48, -33, 9, +-40, 57, -14, 58, +-17, 4, -45, 32, +-31, 38, -33, 36, +-23, 28, -40, 39, +-43, 29, -12, 46, +-34, 13, -23, 28, +-16, 15, -27, 34, +-14, -82, -15, 43, +-31, 25, -32, 29, +-21, 5, -5, 38, +-47, -63, -51, 33, +-46, 12, 3, 47, +-28, -17, -29, 11, +-10, 14, -40, 38}; diff --git a/Libraries/speex/hexc_10_32_table.c b/Libraries/speex/hexc_10_32_table.c new file mode 100644 index 000000000..8dd408f2c --- /dev/null +++ b/Libraries/speex/hexc_10_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: hexc_10_32_table.c + Codebook for high-band excitation in SB-CELP mode (4000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char hexc_10_32_table[320] = { +-3, -2, -1, 0, -4, 5, 35, -40, -9, 13, +-44, 5, -27, -1, -7, 6, -11, 7, -8, 7, +19, -14, 15, -4, 9, -10, 10, -8, 10, -9, +-1, 1, 0, 0, 2, 5, -18, 22, -53, 50, +1, -23, 50, -36, 15, 3, -13, 14, -10, 6, +1, 5, -3, 4, -2, 5, -32, 25, 5, -2, +-1, -4, 1, 11, -29, 26, -6, -15, 30, -18, +0, 15, -17, 40, -41, 3, 9, -2, -2, 3, +-3, -1, -5, 2, 21, -6, -16, -21, 23, 2, +60, 15, 16, -16, -9, 14, 9, -1, 7, -9, +0, 1, 1, 0, -1, -6, 17, -28, 54, -45, +-1, 1, -1, -6, -6, 2, 11, 26, -29, -2, +46, -21, 34, 12, -23, 32, -23, 16, -10, 3, +66, 19, -20, 24, 7, 11, -3, 0, -3, -1, +-50, -46, 2, -18, -3, 4, -1, -2, 3, -3, +-19, 41, -36, 9, 11, -24, 21, -16, 9, -3, +-25, -3, 10, 18, -9, -2, -5, -1, -5, 6, +-4, -3, 2, -26, 21, -19, 35, -15, 7, -13, +17, -19, 39, -43, 48, -31, 16, -9, 7, -2, +-5, 3, -4, 9, -19, 27, -55, 63, -35, 10, +26, -44, -2, 9, 4, 1, -6, 8, -9, 5, +-8, -1, -3, -16, 45, -42, 5, 15, -16, 10, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +-16, 24, -55, 47, -38, 27, -19, 7, -3, 1, +16, 27, 20, -19, 18, 5, -7, 1, -5, 2, +-6, 8, -22, 0, -3, -3, 8, -1, 7, -8, +1, -3, 5, 0, 17, -48, 58, -52, 29, -7, +-2, 3, -10, 6, -26, 58, -31, 1, -6, 3, +93, -29, 39, 3, 17, 5, 6, -1, -1, -1, +27, 13, 10, 19, -7, -34, 12, 10, -4, 9, +-76, 9, 8, -28, -2, -11, 2, -1, 3, 1, +-83, 38, -39, 4, -16, -6, -2, -5, 5, -2, +}; diff --git a/Libraries/speex/hexc_table.c b/Libraries/speex/hexc_table.c new file mode 100644 index 000000000..268408a8d --- /dev/null +++ b/Libraries/speex/hexc_table.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: hexc_table.c + Codebook for high-band excitation in SB-CELP mode (8000 bps with sign) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char hexc_table[1024] = { +-24, 21, -20, 5, -5, -7, 14, -10, +2, -27, 16, -20, 0, -32, 26, 19, +8, -11, -41, 31, 28, -27, -32, 34, +42, 34, -17, 22, -10, 13, -29, 18, +-12, -26, -24, 11, 22, 5, -5, -5, +54, -68, -43, 57, -25, 24, 4, 4, +26, -8, -12, -17, 54, 30, -45, 1, +10, -15, 18, -41, 11, 68, -67, 37, +-16, -24, -16, 38, -22, 6, -29, 30, +66, -27, 5, 7, -16, 13, 2, -12, +-7, -3, -20, 36, 4, -28, 9, 3, +32, 48, 26, 39, 3, 0, 7, -21, +-13, 5, -82, -7, 73, -20, 34, -9, +-5, 1, -1, 10, -5, -10, -1, 9, +1, -9, 10, 0, -14, 11, -1, -2, +-1, 11, 20, 96, -81, -22, -12, -9, +-58, 9, 24, -30, 26, -35, 27, -12, +13, -18, 56, -59, 15, -7, 23, -15, +-1, 6, -25, 14, -22, -20, 47, -11, +16, 2, 38, -23, -19, -30, -9, 40, +-11, 5, 4, -6, 8, 26, -21, -11, +127, 4, 1, 6, -9, 2, -7, -2, +-3, 7, -5, 10, -19, 7, -106, 91, +-3, 9, -4, 21, -8, 26, -80, 8, +1, -2, -10, -17, -17, -27, 32, 71, +6, -29, 11, -23, 54, -38, 29, -22, +39, 87, -31, -12, -20, 3, -2, -2, +2, 20, 0, -1, -35, 27, 9, -6, +-12, 3, -12, -6, 13, 1, 14, -22, +-59, -15, -17, -25, 13, -7, 7, 3, +0, 1, -7, 6, -3, 61, -37, -23, +-23, -29, 38, -31, 27, 1, -8, 2, +-27, 23, -26, 36, -34, 5, 24, -24, +-6, 7, 3, -59, 78, -62, 44, -16, +1, 6, 0, 17, 8, 45, 0, -110, +6, 14, -2, 32, -77, -56, 62, -3, +3, -13, 4, -16, 102, -15, -36, -1, +9, -113, 6, 23, 0, 9, 9, 5, +-8, -1, -14, 5, -12, 121, -53, -27, +-8, -9, 22, -13, 3, 2, -3, 1, +-2, -71, 95, 38, -19, 15, -16, -5, +71, 10, 2, -32, -13, -5, 15, -1, +-2, -14, -85, 30, 29, 6, 3, 2, +0, 0, 0, 0, 0, 0, 0, 0, +2, -65, -56, -9, 18, 18, 23, -14, +-2, 0, 12, -29, 26, -12, 1, 2, +-12, -64, 90, -6, 4, 1, 5, -5, +-110, -3, -31, 22, -29, 9, 0, 8, +-40, -5, 21, -5, -5, 13, 10, -18, +40, 1, 35, -20, 30, -28, 11, -6, +19, 7, 14, 18, -64, 9, -6, 16, +51, 68, 8, 16, 12, -8, 0, -9, +20, -22, 25, 7, -4, -13, 41, -35, +93, -18, -54, 11, -1, 1, -9, 4, +-66, 66, -31, 20, -22, 25, -23, 11, +10, 9, 19, 15, 11, -5, -31, -10, +-23, -28, -6, -6, -3, -4, 5, 3, +-28, 22, -11, -42, 25, -25, -16, 41, +34, 47, -6, 2, 42, -19, -22, 5, +-39, 32, 6, -35, 22, 17, -30, 8, +-26, -11, -11, 3, -12, 33, 33, -37, +21, -1, 6, -4, 3, 0, -5, 5, +12, -12, 57, 27, -61, -3, 20, -17, +2, 0, 4, 0, -2, -33, -58, 81, +-23, 39, -10, -5, 2, 6, -7, 5, +4, -3, -2, -13, -23, -72, 107, 15, +-5, 0, -7, -3, -6, 5, -4, 15, +47, 12, -31, 25, -16, 8, 22, -25, +-62, -56, -18, 14, 28, 12, 2, -11, +74, -66, 41, -20, -7, 16, -20, 16, +-8, 0, -16, 4, -19, 92, 12, -59, +-14, -39, 49, -25, -16, 23, -27, 19, +-3, -33, 19, 85, -29, 6, -7, -10, +16, -7, -12, 1, -6, 2, 4, -2, +64, 10, -25, 41, -2, -31, 15, 0, +110, 50, 69, 35, 28, 19, -10, 2, +-43, -49, -56, -15, -16, 10, 3, 12, +-1, -8, 1, 26, -12, -1, 7, -11, +-27, 41, 25, 1, -11, -18, 22, -7, +-1, -47, -8, 23, -3, -17, -7, 18, +-125, 59, -5, 3, 18, 1, 2, 3, +27, -35, 65, -53, 50, -46, 37, -21, +-28, 7, 14, -37, -5, -5, 12, 5, +-8, 78, -19, 21, -6, -16, 8, -7, +5, 2, 7, 2, 10, -6, 12, -60, +44, 11, -36, -32, 31, 0, 2, -2, +2, 1, -3, 7, -10, 17, -21, 10, +6, -2, 19, -2, 59, -38, -86, 38, +8, -41, -30, -45, -33, 7, 15, 28, +29, -7, 24, -40, 7, 7, 5, -2, +9, 24, -23, -18, 6, -29, 30, 2, +28, 49, -11, -46, 10, 43, -13, -9, +-1, -3, -7, -7, -17, -6, 97, -33, +-21, 3, 5, 1, 12, -43, -8, 28, +7, -43, -7, 17, -20, 19, -1, 2, +-13, 9, 54, 34, 9, -28, -11, -9, +-17, 110, -59, 44, -26, 0, 3, -12, +-47, 73, -34, -43, 38, -33, 16, -5, +-46, -4, -6, -2, -25, 19, -29, 28, +-13, 5, 14, 27, -40, -43, 4, 32, +-13, -2, -35, -4, 112, -42, 9, -12, +37, -28, 17, 14, -19, 35, -39, 23, +3, -14, -1, -57, -5, 94, -9, 3, +-39, 5, 30, -10, -32, 42, -13, -14, +-97, -63, 30, -9, 1, -7, 12, 5, +20, 17, -9, -36, -30, 25, 47, -9, +-15, 12, -22, 98, -8, -50, 15, -27, +21, -16, -11, 2, 12, -10, 10, -3, +33, 36, -96, 0, -17, 31, -9, 9, +3, -20, 13, -11, 8, -4, 10, -10, +9, 1, 112, -70, -27, 5, -21, 2, +-57, -3, -29, 10, 19, -21, 21, -10, +-66, -3, 91, -35, 30, -12, 0, -7, +59, -28, 26, 2, 14, -18, 1, 1, +11, 17, 20, -54, -59, 27, 4, 29, +32, 5, 19, 12, -4, 1, 7, -10, +5, -2, 10, 0, 23, -5, 28, -104, +46, 11, 16, 3, 29, 1, -8, -14, +1, 7, -50, 88, -62, 26, 8, -17, +-14, 50, 0, 32, -12, -3, -27, 18, +-8, -5, 8, 3, -20, -11, 37, -12, +9, 33, 46, -101, -1, -4, 1, 6, +-1, 28, -42, -15, 16, 5, -1, -2, +-55, 85, 38, -9, -4, 11, -2, -9, +-6, 3, -20, -10, -77, 89, 24, -3, +-104, -57, -26, -31, -20, -6, -9, 14, +20, -23, 46, -15, -31, 28, 1, -15, +-2, 6, -2, 31, 45, -76, 23, -25, +}; diff --git a/Libraries/speex/high_lsp_tables.c b/Libraries/speex/high_lsp_tables.c new file mode 100644 index 000000000..e82e87550 --- /dev/null +++ b/Libraries/speex/high_lsp_tables.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: high_lsp_tables.c + Codebooks for high-band LSPs in SB-CELP mode + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char high_lsp_cdbk[512]={ +39,12,-14,-20,-29,-61,-67,-76, +-32,-71,-67,68,77,46,34,5, +-13,-48,-46,-72,-81,-84,-60,-58, +-40,-28,82,93,68,45,29,3, +-19,-47,-28,-43,-35,-30,-8,-13, +-39,-91,-91,-123,-96,10,10,-6, +-18,-55,-60,-91,-56,-36,-27,-16, +-48,-75,40,28,-10,-28,35,9, +37,19,1,-20,-31,-41,-18,-25, +-35,-68,-80,45,27,-1,47,13, +0,-29,-35,-57,-50,-79,-73,-38, +-19,5,35,14,-10,-23,16,-8, +5,-24,-40,-62,-23,-27,-22,-16, +-18,-46,-72,-77,43,21,33,1, +-80,-70,-70,-64,-56,-52,-39,-33, +-31,-38,-19,-19,-15,32,33,-2, +7,-15,-15,-24,-23,-33,-41,-56, +-24,-57,5,89,64,41,27,5, +-9,-47,-60,-97,-97,-124,-20,-9, +-44,-73,31,29,-4,64,48,7, +-35,-57,0,-3,-26,-47,-3,-6, +-40,-76,-79,-48,12,81,55,10, +9,-24,-43,-73,-57,-69,16,5, +-28,-53,18,29,20,0,-4,-11, +6,-13,23,7,-17,-35,-37,-37, +-30,-68,-63,6,24,-9,-14,3, +21,-13,-27,-57,-49,-80,-24,-41, +-5,-16,-5,1,45,25,12,-7, +3,-15,-6,-16,-15,-8,6,-13, +-42,-81,-80,-87,14,1,-10,-3, +-43,-69,-46,-24,-28,-29,36,6, +-43,-56,-12,12,54,79,43,9, +54,22,2,8,-12,-43,-46,-52, +-38,-69,-89,-5,75,38,33,5, +-13,-53,-62,-87,-89,-113,-99,-55, +-34,-37,62,55,33,16,21,-2, +-17,-46,-29,-38,-38,-48,-39,-42, +-36,-75,-72,-88,-48,-30,21,2, +-15,-57,-64,-98,-84,-76,25,1, +-46,-80,-12,18,-7,3,34,6, +38,31,23,4,-1,20,14,-15, +-43,-78,-91,-24,14,-3,54,16, +0,-27,-28,-44,-56,-83,-92,-89, +-3,34,56,41,36,22,20,-8, +-7,-35,-42,-62,-49,3,12,-10, +-50,-87,-96,-66,92,70,38,9, +-70,-71,-62,-42,-39,-43,-11,-7, +-50,-79,-58,-50,-31,32,31,-6, +-4,-25,7,-17,-38,-70,-58,-27, +-43,-83,-28,59,36,20,31,2, +-27,-71,-80,-109,-98,-75,-33,-32, +-31,-2,33,15,-6,43,33,-5, +0,-22,-10,-27,-34,-49,-11,-20, +-41,-91,-100,-121,-39,57,41,10, +-19,-50,-38,-59,-60,-70,-18,-20, +-8,-31,-8,-15,1,-14,-26,-25, +33,21,32,17,1,-19,-19,-26, +-58,-81,-35,-22,45,30,11,-11, +3,-26,-48,-87,-67,-83,-58,3, +-1,-26,-20,44,10,25,39,5, +-9,-35,-27,-38,7,10,4,-9, +-42,-85,-102,-127,52,44,28,10, +-47,-61,-40,-39,-17,-1,-10,-33, +-42,-74,-48,21,-4,70,52,10}; + + +const signed char high_lsp_cdbk2[512]={ +-36,-62,6,-9,-10,-14,-56,23, +1,-26,23,-48,-17,12,8,-7, +23,29,-36,-28,-6,-29,-17,-5, +40,23,10,10,-46,-13,36,6, +4,-30,-29,62,32,-32,-1,22, +-14,1,-4,-22,-45,2,54,4, +-30,-57,-59,-12,27,-3,-31,8, +-9,5,10,-14,32,66,19,9, +2,-25,-37,23,-15,18,-38,-31, +5,-9,-21,15,0,22,62,30, +15,-12,-14,-46,77,21,33,3, +34,29,-19,50,2,11,9,-38, +-12,-37,62,1,-15,54,32,6, +2,-24,20,35,-21,2,19,24, +-13,55,4,9,39,-19,30,-1, +-21,73,54,33,8,18,3,15, +6,-19,-47,6,-3,-48,-50,1, +26,20,8,-23,-50,65,-14,-55, +-17,-31,-37,-28,53,-1,-17,-53, +1,57,11,-8,-25,-30,-37,64, +5,-52,-45,15,23,31,15,14, +-25,24,33,-2,-44,-56,-18,6, +-21,-43,4,-12,17,-37,20,-10, +34,15,2,15,55,21,-11,-31, +-6,46,25,16,-9,-25,-8,-62, +28,17,20,-32,-29,26,30,25, +-19,2,-16,-17,26,-51,2,50, +42,19,-66,23,29,-2,3,19, +-19,-37,32,15,6,30,-34,13, +11,-5,40,31,10,-42,4,-9, +26,-9,-70,17,-2,-23,20,-22, +-55,51,-24,-31,22,-22,15,-13, +3,-10,-28,-16,56,4,-63,11, +-18,-15,-18,-38,-35,16,-7,34, +-1,-21,-49,-47,9,-37,7,8, +69,55,20,6,-33,-45,-10,-9, +6,-9,12,71,15,-3,-42,-7, +-24,32,-35,-2,-42,-17,-5,0, +-2,-33,-54,13,-12,-34,47,23, +19,55,7,-8,74,31,14,16, +-23,-26,19,12,-18,-49,-28,-31, +-20,2,-14,-20,-47,78,40,13, +-23,-11,21,-6,18,1,47,5, +38,35,32,46,22,8,13,16, +-14,18,51,19,40,39,11,-26, +-1,-17,47,2,-53,-15,31,-22, +38,21,-15,-16,5,-33,53,15, +-38,86,11,-3,-24,49,13,-4, +-11,-18,28,20,-12,-27,-26,35, +-25,-35,-3,-20,-61,30,10,-55, +-12,-22,-52,-54,-14,19,-32,-12, +45,15,-8,-48,-9,11,-32,8, +-16,-34,-13,51,18,38,-2,-32, +-17,22,-2,-18,-28,-70,59,27, +-28,-19,-10,-20,-9,-9,-8,-21, +21,-8,35,-2,45,-3,-9,12, +0,30,7,-39,43,27,-38,-91, +30,26,19,-55,-4,63,14,-17, +13,9,13,2,7,4,6,61, +72,-1,-17,29,-1,-22,-17,8, +-28,-37,63,44,41,3,2,14, +9,-6,75,-8,-7,-12,-15,-12, +13,9,-4,30,-22,-65,15,0, +-45,4,-4,1,5,22,11,23}; diff --git a/Libraries/speex/include/bitwise.c b/Libraries/speex/include/bitwise.c new file mode 100644 index 000000000..cd78db7b3 --- /dev/null +++ b/Libraries/speex/include/bitwise.c @@ -0,0 +1,857 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE Ogg CONTAINER SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2010 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: packing variable sized words into an octet stream + last mod: $Id: bitwise.c 18051 2011-08-04 17:56:39Z giles $ + + ********************************************************************/ + +/* We're 'LSb' endian; if we write a word but read individual bits, + then we'll read the lsb first */ + +#include +#include +#include +#include "ogg/ogg.h" + +#define BUFFER_INCREMENT 256 + +static const unsigned long mask[]= +{0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f, + 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff, + 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff, + 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff, + 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff, + 0x3fffffff,0x7fffffff,0xffffffff }; + +static const unsigned int mask8B[]= +{0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff}; + +void oggpack_writeinit(oggpack_buffer *b){ + memset(b,0,sizeof(*b)); + b->ptr=b->buffer=_ogg_malloc(BUFFER_INCREMENT); + b->buffer[0]='\0'; + b->storage=BUFFER_INCREMENT; +} + +void oggpackB_writeinit(oggpack_buffer *b){ + oggpack_writeinit(b); +} + +int oggpack_writecheck(oggpack_buffer *b){ + if(!b->ptr || !b->storage)return -1; + return 0; +} + +int oggpackB_writecheck(oggpack_buffer *b){ + return oggpack_writecheck(b); +} + +void oggpack_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + if(b->ptr){ + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask[bits]; + } +} + +void oggpackB_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + if(b->ptr){ + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask8B[bits]; + } +} + +/* Takes only up to 32 bits. */ +void oggpack_write(oggpack_buffer *b,unsigned long value,int bits){ + if(bits<0 || bits>32) goto err; + if(b->endbyte>=b->storage-4){ + void *ret; + if(!b->ptr)return; + if(b->storage>LONG_MAX-BUFFER_INCREMENT) goto err; + ret=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + if(!ret) goto err; + b->buffer=ret; + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value&=mask[bits]; + bits+=b->endbit; + + b->ptr[0]|=value<endbit; + + if(bits>=8){ + b->ptr[1]=(unsigned char)(value>>(8-b->endbit)); + if(bits>=16){ + b->ptr[2]=(unsigned char)(value>>(16-b->endbit)); + if(bits>=24){ + b->ptr[3]=(unsigned char)(value>>(24-b->endbit)); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=(unsigned char)(value>>(32-b->endbit)); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; + return; + err: + oggpack_writeclear(b); +} + +/* Takes only up to 32 bits. */ +void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits){ + if(bits<0 || bits>32) goto err; + if(b->endbyte>=b->storage-4){ + void *ret; + if(!b->ptr)return; + if(b->storage>LONG_MAX-BUFFER_INCREMENT) goto err; + ret=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + if(!ret) goto err; + b->buffer=ret; + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value=(value&mask[bits])<<(32-bits); + bits+=b->endbit; + + b->ptr[0]|=value>>(24+b->endbit); + + if(bits>=8){ + b->ptr[1]=(unsigned char)(value>>(16+b->endbit)); + if(bits>=16){ + b->ptr[2]=(unsigned char)(value>>(8+b->endbit)); + if(bits>=24){ + b->ptr[3]=(unsigned char)(value>>(b->endbit)); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=(unsigned char)(value<<(8-b->endbit)); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; + return; + err: + oggpack_writeclear(b); +} + +void oggpack_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpack_write(b,0,bits); +} + +void oggpackB_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpackB_write(b,0,bits); +} + +static void oggpack_writecopy_helper(oggpack_buffer *b, + void *source, + long bits, + void (*w)(oggpack_buffer *, + unsigned long, + int), + int msb){ + unsigned char *ptr=(unsigned char *)source; + + long bytes=bits/8; + bits-=bytes*8; + + if(b->endbit){ + int i; + /* unaligned copy. Do it the hard way. */ + for(i=0;iendbyte+bytes+1>=b->storage){ + void *ret; + if(!b->ptr) goto err; + if(b->endbyte+bytes+BUFFER_INCREMENT>b->storage) goto err; + b->storage=b->endbyte+bytes+BUFFER_INCREMENT; + ret=_ogg_realloc(b->buffer,b->storage); + if(!ret) goto err; + b->buffer=ret; + b->ptr=b->buffer+b->endbyte; + } + + memmove(b->ptr,source,bytes); + b->ptr+=bytes; + b->endbyte+=bytes; + *b->ptr=0; + + } + if(bits){ + if(msb) + w(b,(unsigned long)(ptr[bytes]>>(8-bits)),bits); + else + w(b,(unsigned long)(ptr[bytes]),bits); + } + return; + err: + oggpack_writeclear(b); +} + +void oggpack_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpack_write,0); +} + +void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpackB_write,1); +} + +void oggpack_reset(oggpack_buffer *b){ + if(!b->ptr)return; + b->ptr=b->buffer; + b->buffer[0]=0; + b->endbit=b->endbyte=0; +} + +void oggpackB_reset(oggpack_buffer *b){ + oggpack_reset(b); +} + +void oggpack_writeclear(oggpack_buffer *b){ + if(b->buffer)_ogg_free(b->buffer); + memset(b,0,sizeof(*b)); +} + +void oggpackB_writeclear(oggpack_buffer *b){ + oggpack_writeclear(b); +} + +void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + memset(b,0,sizeof(*b)); + b->buffer=b->ptr=buf; + b->storage=bytes; +} + +void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + oggpack_readinit(b,buf,bytes); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpack_look(oggpack_buffer *b,int bits){ + unsigned long ret; + unsigned long m; + + if(bits<0 || bits>32) return -1; + m=mask[bits]; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) return -1; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + return(m&ret); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpackB_look(oggpack_buffer *b,int bits){ + unsigned long ret; + int m=32-bits; + + if(m<0 || m>32) return -1; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) return -1; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + return ((ret&0xffffffff)>>(m>>1))>>((m+1)>>1); +} + +long oggpack_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>b->endbit)&1); +} + +long oggpackB_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>(7-b->endbit))&1); +} + +void oggpack_adv(oggpack_buffer *b,int bits){ + bits+=b->endbit; + + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; +} + +void oggpackB_adv(oggpack_buffer *b,int bits){ + oggpack_adv(b,bits); +} + +void oggpack_adv1(oggpack_buffer *b){ + if(++(b->endbit)>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } +} + +void oggpackB_adv1(oggpack_buffer *b){ + oggpack_adv1(b); +} + +/* bits <= 32 */ +long oggpack_read(oggpack_buffer *b,int bits){ + long ret; + unsigned long m; + + if(bits<0 || bits>32) goto err; + m=mask[bits]; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit){ + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + } + ret&=m; + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return ret; + + overflow: + err: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +/* bits <= 32 */ +long oggpackB_read(oggpack_buffer *b,int bits){ + long ret; + long m=32-bits; + + if(m<0 || m>32) goto err; + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + ret=((ret&0xffffffffUL)>>(m>>1))>>((m+1)>>1); + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return ret; + + overflow: + err: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpack_read1(oggpack_buffer *b){ + long ret; + + if(b->endbyte >= b->storage) goto overflow; + ret=(b->ptr[0]>>b->endbit)&1; + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return ret; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpackB_read1(oggpack_buffer *b){ + long ret; + + if(b->endbyte >= b->storage) goto overflow; + ret=(b->ptr[0]>>(7-b->endbit))&1; + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return ret; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpack_bytes(oggpack_buffer *b){ + return(b->endbyte+(b->endbit+7)/8); +} + +long oggpack_bits(oggpack_buffer *b){ + return(b->endbyte*8+b->endbit); +} + +long oggpackB_bytes(oggpack_buffer *b){ + return oggpack_bytes(b); +} + +long oggpackB_bits(oggpack_buffer *b){ + return oggpack_bits(b); +} + +unsigned char *oggpack_get_buffer(oggpack_buffer *b){ + return(b->buffer); +} + +unsigned char *oggpackB_get_buffer(oggpack_buffer *b){ + return oggpack_get_buffer(b); +} + +/* Self test of the bitwise routines; everything else is based on + them, so they damned well better be solid. */ + +#ifdef _V_SELFTEST +#include + +static int ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +oggpack_buffer o; +oggpack_buffer r; + +void report(char *in){ + fprintf(stderr,"%s",in); + exit(1); +} + +void cliptest(unsigned long *b,int vals,int bits,int *comp,int compsize){ + long bytes,i; + unsigned char *buffer; + + oggpack_reset(&o); + for(i=0;i header file. */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GETOPT_H 1 + +/* Define to 1 if you have the `getopt_long' function. */ +#define HAVE_GETOPT_LONG 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `m' library (-lm). */ +#define HAVE_LIBM 1 + +/* Define to 1 if you have the `winmm' library (-lwinmm). */ +/* #undef HAVE_LIBWINMM */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_AUDIOIO_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOUNDCARD_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 8 + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT 2 + +/* Version extra */ +#define SPEEX_EXTRA_VERSION "" + +/* Version major */ +#define SPEEX_MAJOR_VERSION 1 + +/* Version micro */ +#define SPEEX_MICRO_VERSION 16 + +/* Version minor */ +#define SPEEX_MINOR_VERSION 1 + +/* Complete version string */ +#define SPEEX_VERSION "1.2rc1" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Enable support for TI C55X DSP */ +/* #undef TI_C55X */ + +/* Make use of alloca */ +/* #undef USE_ALLOCA */ + +/* Use FFTW3 for FFT */ +/* #undef USE_GPL_FFTW3 */ + +/* Use Intel Math Kernel Library for FFT */ +/* #undef USE_INTEL_MKL */ + +/* Use KISS Fast Fourier Transform */ +#define USE_KISS_FFT + +/* Use FFT from OggVorbis */ +/* #undef USE_SMALLFT */ + +/* Use C99 variable-size arrays */ +#define VAR_ARRAYS + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +/* #undef WORDS_BIGENDIAN */ + +/* Enable SSE support */ +/* #undef _USE_SSE */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to equivalent of C99 restrict keyword, or to nothing if this is not + supported. Do not define if restrict is supported directly. */ +#define restrict __restrict diff --git a/Libraries/speex/include/framing.c b/Libraries/speex/include/framing.c new file mode 100644 index 000000000..08ac85278 --- /dev/null +++ b/Libraries/speex/include/framing.c @@ -0,0 +1,2093 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE Ogg CONTAINER SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2010 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: code raw packets into framed OggSquish stream and + decode Ogg streams back into raw packets + last mod: $Id: framing.c 18052 2011-08-04 17:57:02Z giles $ + + note: The CRC code is directly derived from public domain code by + Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html + for details. + + ********************************************************************/ + +#include +#include +#include "ogg/ogg.h" + +/* A complete description of Ogg framing exists in docs/framing.html */ + +int ogg_page_version(const ogg_page *og){ + return((int)(og->header[4])); +} + +int ogg_page_continued(const ogg_page *og){ + return((int)(og->header[5]&0x01)); +} + +int ogg_page_bos(const ogg_page *og){ + return((int)(og->header[5]&0x02)); +} + +int ogg_page_eos(const ogg_page *og){ + return((int)(og->header[5]&0x04)); +} + +ogg_int64_t ogg_page_granulepos(const ogg_page *og){ + unsigned char *page=og->header; + ogg_int64_t granulepos=page[13]&(0xff); + granulepos= (granulepos<<8)|(page[12]&0xff); + granulepos= (granulepos<<8)|(page[11]&0xff); + granulepos= (granulepos<<8)|(page[10]&0xff); + granulepos= (granulepos<<8)|(page[9]&0xff); + granulepos= (granulepos<<8)|(page[8]&0xff); + granulepos= (granulepos<<8)|(page[7]&0xff); + granulepos= (granulepos<<8)|(page[6]&0xff); + return(granulepos); +} + +int ogg_page_serialno(const ogg_page *og){ + return(og->header[14] | + (og->header[15]<<8) | + (og->header[16]<<16) | + (og->header[17]<<24)); +} + +long ogg_page_pageno(const ogg_page *og){ + return(og->header[18] | + (og->header[19]<<8) | + (og->header[20]<<16) | + (og->header[21]<<24)); +} + + + +/* returns the number of packets that are completed on this page (if + the leading packet is begun on a previous page, but ends on this + page, it's counted */ + +/* NOTE: + If a page consists of a packet begun on a previous page, and a new + packet begun (but not completed) on this page, the return will be: + ogg_page_packets(page) ==1, + ogg_page_continued(page) !=0 + + If a page happens to be a single packet that was begun on a + previous page, and spans to the next page (in the case of a three or + more page packet), the return will be: + ogg_page_packets(page) ==0, + ogg_page_continued(page) !=0 +*/ + +int ogg_page_packets(const ogg_page *og){ + int i,n=og->header[26],count=0; + for(i=0;iheader[27+i]<255)count++; + return(count); +} + + +#if 0 +/* helper to initialize lookup for direct-table CRC (illustrative; we + use the static init below) */ + +static ogg_uint32_t _ogg_crc_entry(unsigned long index){ + int i; + unsigned long r; + + r = index << 24; + for (i=0; i<8; i++) + if (r & 0x80000000UL) + r = (r << 1) ^ 0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + else + r<<=1; + return (r & 0xffffffffUL); +} +#endif + +static const ogg_uint32_t crc_lookup[256]={ + 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, + 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, + 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, + 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, + 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, + 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, + 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, + 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, + 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, + 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, + 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, + 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, + 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, + 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, + 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, + 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, + 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, + 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, + 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, + 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, + 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, + 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, + 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, + 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, + 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, + 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, + 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, + 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, + 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, + 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, + 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, + 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, + 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, + 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, + 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, + 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, + 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, + 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, + 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, + 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, + 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, + 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, + 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, + 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, + 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, + 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, + 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, + 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, + 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, + 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, + 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, + 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, + 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, + 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, + 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, + 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, + 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, + 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, + 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, + 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, + 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, + 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, + 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, + 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4}; + +/* init the encode/decode logical stream state */ + +int ogg_stream_init(ogg_stream_state *os,int serialno){ + if(os){ + memset(os,0,sizeof(*os)); + os->body_storage=16*1024; + os->lacing_storage=1024; + + os->body_data=_ogg_malloc(os->body_storage*sizeof(*os->body_data)); + os->lacing_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->lacing_vals)); + os->granule_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->granule_vals)); + + if(!os->body_data || !os->lacing_vals || !os->granule_vals){ + ogg_stream_clear(os); + return -1; + } + + os->serialno=serialno; + + return(0); + } + return(-1); +} + +/* async/delayed error detection for the ogg_stream_state */ +int ogg_stream_check(ogg_stream_state *os){ + if(!os || !os->body_data) return -1; + return 0; +} + +/* _clear does not free os, only the non-flat storage within */ +int ogg_stream_clear(ogg_stream_state *os){ + if(os){ + if(os->body_data)_ogg_free(os->body_data); + if(os->lacing_vals)_ogg_free(os->lacing_vals); + if(os->granule_vals)_ogg_free(os->granule_vals); + + memset(os,0,sizeof(*os)); + } + return(0); +} + +int ogg_stream_destroy(ogg_stream_state *os){ + if(os){ + ogg_stream_clear(os); + _ogg_free(os); + } + return(0); +} + +/* Helpers for ogg_stream_encode; this keeps the structure and + what's happening fairly clear */ + +static int _os_body_expand(ogg_stream_state *os,int needed){ + if(os->body_storage<=os->body_fill+needed){ + void *ret; + ret=_ogg_realloc(os->body_data,(os->body_storage+needed+1024)* + sizeof(*os->body_data)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->body_storage+=(needed+1024); + os->body_data=ret; + } + return 0; +} + +static int _os_lacing_expand(ogg_stream_state *os,int needed){ + if(os->lacing_storage<=os->lacing_fill+needed){ + void *ret; + ret=_ogg_realloc(os->lacing_vals,(os->lacing_storage+needed+32)* + sizeof(*os->lacing_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->lacing_vals=ret; + ret=_ogg_realloc(os->granule_vals,(os->lacing_storage+needed+32)* + sizeof(*os->granule_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->granule_vals=ret; + os->lacing_storage+=(needed+32); + } + return 0; +} + +/* checksum the page */ +/* Direct table CRC; note that this will be faster in the future if we + perform the checksum simultaneously with other copies */ + +void ogg_page_checksum_set(ogg_page *og){ + if(og){ + ogg_uint32_t crc_reg=0; + int i; + + /* safety; needed for API behavior, but not framing code */ + og->header[22]=0; + og->header[23]=0; + og->header[24]=0; + og->header[25]=0; + + for(i=0;iheader_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->header[i]]; + for(i=0;ibody_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->body[i]]; + + og->header[22]=(unsigned char)(crc_reg&0xff); + og->header[23]=(unsigned char)((crc_reg>>8)&0xff); + og->header[24]=(unsigned char)((crc_reg>>16)&0xff); + og->header[25]=(unsigned char)((crc_reg>>24)&0xff); + } +} + +/* submit data to the internal buffer of the framing engine */ +int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, int count, + long e_o_s, ogg_int64_t granulepos){ + + int bytes = 0, lacing_vals, i; + + if(ogg_stream_check(os)) return -1; + if(!iov) return 0; + + for (i = 0; i < count; ++i) bytes += (int)iov[i].iov_len; + lacing_vals=bytes/255+1; + + if(os->body_returned){ + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + + os->body_fill-=os->body_returned; + if(os->body_fill) + memmove(os->body_data,os->body_data+os->body_returned, + os->body_fill); + os->body_returned=0; + } + + /* make sure we have the buffer storage */ + if(_os_body_expand(os,bytes) || _os_lacing_expand(os,lacing_vals)) + return -1; + + /* Copy in the submitted packet. Yes, the copy is a waste; this is + the liability of overly clean abstraction for the time being. It + will actually be fairly easy to eliminate the extra copy in the + future */ + + for (i = 0; i < count; ++i) { + memcpy(os->body_data+os->body_fill, iov[i].iov_base, iov[i].iov_len); + os->body_fill += (int)iov[i].iov_len; + } + + /* Store lacing vals for this packet */ + for(i=0;ilacing_vals[os->lacing_fill+i]=255; + os->granule_vals[os->lacing_fill+i]=os->granulepos; + } + os->lacing_vals[os->lacing_fill+i]=bytes%255; + os->granulepos=os->granule_vals[os->lacing_fill+i]=granulepos; + + /* flag the first segment as the beginning of the packet */ + os->lacing_vals[os->lacing_fill]|= 0x100; + + os->lacing_fill+=lacing_vals; + + /* for the sake of completeness */ + os->packetno++; + + if(e_o_s)os->e_o_s=1; + + return(0); +} + +int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){ + ogg_iovec_t iov; + iov.iov_base = op->packet; + iov.iov_len = op->bytes; + return ogg_stream_iovecin(os, &iov, 1, op->e_o_s, op->granulepos); +} + +/* Conditionally flush a page; force==0 will only flush nominal-size + pages, force==1 forces us to flush a page regardless of page size + so long as there's any data available at all. */ +static int ogg_stream_flush_i(ogg_stream_state *os,ogg_page *og, int force, int nfill){ + int i; + int vals=0; + int maxvals=(os->lacing_fill>255?255:os->lacing_fill); + int bytes=0; + long acc=0; + ogg_int64_t granule_pos=-1; + + if(ogg_stream_check(os)) return(0); + if(maxvals==0) return(0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(os->b_o_s==0){ /* 'initial header page' case */ + granule_pos=0; + for(vals=0;valslacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + }else{ + + /* The extra packets_done, packet_just_done logic here attempts to do two things: + 1) Don't unneccessarily span pages. + 2) Unless necessary, don't flush pages if there are less than four packets on + them; this expands page size to reduce unneccessary overhead if incoming packets + are large. + These are not necessary behaviors, just 'always better than naive flushing' + without requiring an application to explicitly request a specific optimized + behavior. We'll want an explicit behavior setup pathway eventually as well. */ + + int packets_done=0; + int packet_just_done=0; + for(vals=0;valsnfill && packet_just_done>=4){ + force=1; + break; + } + acc+=os->lacing_vals[vals]&0x0ff; + if((os->lacing_vals[vals]&0xff)<255){ + granule_pos=os->granule_vals[vals]; + packet_just_done=++packets_done; + }else + packet_just_done=0; + } + if(vals==255)force=1; + } + + if(!force) return(0); + + /* construct the header in temp storage */ + memcpy(os->header,"OggS",4); + + /* stream structure version */ + os->header[4]=0x00; + + /* continued packet flag? */ + os->header[5]=0x00; + if((os->lacing_vals[0]&0x100)==0)os->header[5]|=0x01; + /* first page flag? */ + if(os->b_o_s==0)os->header[5]|=0x02; + /* last page flag? */ + if(os->e_o_s && os->lacing_fill==vals)os->header[5]|=0x04; + os->b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6;i<14;i++){ + os->header[i]=(unsigned char)(granule_pos&0xff); + granule_pos>>=8; + } + + /* 32 bits of stream serial number */ + { + long serialno=os->serialno; + for(i=14;i<18;i++){ + os->header[i]=(unsigned char)(serialno&0xff); + serialno>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(os->pageno==-1)os->pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + long pageno=os->pageno++; + for(i=18;i<22;i++){ + os->header[i]=(unsigned char)(pageno&0xff); + pageno>>=8; + } + } + + /* zero for computation; filled in later */ + os->header[22]=0; + os->header[23]=0; + os->header[24]=0; + os->header[25]=0; + + /* segment table */ + os->header[26]=(unsigned char)(vals&0xff); + for(i=0;iheader[i+27]=(unsigned char)(os->lacing_vals[i]&0xff); + + /* set pointers in the ogg_page struct */ + og->header=os->header; + og->header_len=os->header_fill=vals+27; + og->body=os->body_data+os->body_returned; + og->body_len=bytes; + + /* advance the lacing data and set the body_returned pointer */ + + os->lacing_fill-=vals; + memmove(os->lacing_vals,os->lacing_vals+vals,os->lacing_fill*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+vals,os->lacing_fill*sizeof(*os->granule_vals)); + os->body_returned+=bytes; + + /* calculate the checksum */ + + ogg_page_checksum_set(og); + + /* done */ + return(1); +} + +/* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not guarantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + since ogg_stream_flush will flush the last page in a stream even if + it's undersized, you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you specifically need to flush + a page regardless of size in the middle of a stream. */ + +int ogg_stream_flush(ogg_stream_state *os,ogg_page *og){ + return ogg_stream_flush_i(os,og,1,4096); +} + +/* Like the above, but an argument is provided to adjust the nominal + page size for applications which are smart enough to provide their + own delay based flushing */ + +int ogg_stream_flush_fill(ogg_stream_state *os,ogg_page *og, int nfill){ + return ogg_stream_flush_i(os,og,1,nfill); +} + +/* This constructs pages from buffered packet segments. The pointers +returned are to static buffers; do not free. The returned buffers are +good only until the next call (using the same ogg_stream_state) */ + +int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,4096)); +} + +/* Like the above, but an argument is provided to adjust the nominal +page size for applications which are smart enough to provide their +own delay based flushing */ + +int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,nfill)); +} + +int ogg_stream_eos(ogg_stream_state *os){ + if(ogg_stream_check(os)) return 1; + return os->e_o_s; +} + +/* DECODING PRIMITIVES: packet streaming layer **********************/ + +/* This has two layers to place more of the multi-serialno and paging + control in the application's hands. First, we expose a data buffer + using ogg_sync_buffer(). The app either copies into the + buffer, or passes it directly to read(), etc. We then call + ogg_sync_wrote() to tell how many bytes we just added. + + Pages are returned (pointers into the buffer in ogg_sync_state) + by ogg_sync_pageout(). The page is then submitted to + ogg_stream_pagein() along with the appropriate + ogg_stream_state* (ie, matching serialno). We then get raw + packets out calling ogg_stream_packetout() with a + ogg_stream_state. */ + +/* initialize the struct to a known state */ +int ogg_sync_init(ogg_sync_state *oy){ + if(oy){ + oy->storage = -1; /* used as a readiness flag */ + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +/* clear non-flat storage within */ +int ogg_sync_clear(ogg_sync_state *oy){ + if(oy){ + if(oy->data)_ogg_free(oy->data); + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +int ogg_sync_destroy(ogg_sync_state *oy){ + if(oy){ + ogg_sync_clear(oy); + _ogg_free(oy); + } + return(0); +} + +int ogg_sync_check(ogg_sync_state *oy){ + if(oy->storage<0) return -1; + return 0; +} + +char *ogg_sync_buffer(ogg_sync_state *oy, long size){ + if(ogg_sync_check(oy)) return NULL; + + /* first, clear out any space that has been previously returned */ + if(oy->returned){ + oy->fill-=oy->returned; + if(oy->fill>0) + memmove(oy->data,oy->data+oy->returned,oy->fill); + oy->returned=0; + } + + if(size>oy->storage-oy->fill){ + /* We need to extend the internal buffer */ + long newsize=size+oy->fill+4096; /* an extra page to be nice */ + void *ret; + + if(oy->data) + ret=_ogg_realloc(oy->data,newsize); + else + ret=_ogg_malloc(newsize); + if(!ret){ + ogg_sync_clear(oy); + return NULL; + } + oy->data=ret; + oy->storage=newsize; + } + + /* expose a segment at least as large as requested at the fill mark */ + return((char *)oy->data+oy->fill); +} + +int ogg_sync_wrote(ogg_sync_state *oy, long bytes){ + if(ogg_sync_check(oy))return -1; + if(oy->fill+bytes>oy->storage)return -1; + oy->fill+=bytes; + return(0); +} + +/* sync the stream. This is meant to be useful for finding page + boundaries. + + return values for this: + -n) skipped n bytes + 0) page not ready; more data (no bytes skipped) + n) page synced at current location; page length n bytes + +*/ + +long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og){ + unsigned char *page=oy->data+oy->returned; + unsigned char *next; + long bytes=oy->fill-oy->returned; + + if(ogg_sync_check(oy))return 0; + + if(oy->headerbytes==0){ + int headerbytes,i; + if(bytes<27)return(0); /* not enough for a header */ + + /* verify capture pattern */ + if(memcmp(page,"OggS",4))goto sync_fail; + + headerbytes=page[26]+27; + if(bytesbodybytes+=page[27+i]; + oy->headerbytes=headerbytes; + } + + if(oy->bodybytes+oy->headerbytes>bytes)return(0); + + /* The whole test page is buffered. Verify the checksum */ + { + /* Grab the checksum bytes, set the header field to zero */ + char chksum[4]; + ogg_page log; + + memcpy(chksum,page+22,4); + memset(page+22,0,4); + + /* set up a temp page struct and recompute the checksum */ + log.header=page; + log.header_len=oy->headerbytes; + log.body=page+oy->headerbytes; + log.body_len=oy->bodybytes; + ogg_page_checksum_set(&log); + + /* Compare */ + if(memcmp(chksum,page+22,4)){ + /* D'oh. Mismatch! Corrupt page (or miscapture and not a page + at all) */ + /* replace the computed checksum with the one actually read in */ + memcpy(page+22,chksum,4); + + /* Bad checksum. Lose sync */ + goto sync_fail; + } + } + + /* yes, have a whole page all ready to go */ + { + unsigned char *page=oy->data+oy->returned; + long bytes; + + if(og){ + og->header=page; + og->header_len=oy->headerbytes; + og->body=page+oy->headerbytes; + og->body_len=oy->bodybytes; + } + + oy->unsynced=0; + oy->returned+=(bytes=oy->headerbytes+oy->bodybytes); + oy->headerbytes=0; + oy->bodybytes=0; + return(bytes); + } + + sync_fail: + + oy->headerbytes=0; + oy->bodybytes=0; + + /* search for possible capture */ + next=memchr(page+1,'O',bytes-1); + if(!next) + next=oy->data+oy->fill; + + oy->returned=(int)(next-oy->data); + return((long)-(next-page)); +} + +/* sync the stream and get a page. Keep trying until we find a page. + Suppress 'sync errors' after reporting the first. + + return values: + -1) recapture (hole in data) + 0) need more data + 1) page returned + + Returns pointers into buffered data; invalidated by next call to + _stream, _clear, _init, or _buffer */ + +int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og){ + + if(ogg_sync_check(oy))return 0; + + /* all we need to do is verify a page at the head of the stream + buffer. If it doesn't verify, we look for the next potential + frame */ + + for(;;){ + long ret=ogg_sync_pageseek(oy,og); + if(ret>0){ + /* have a page */ + return(1); + } + if(ret==0){ + /* need more data */ + return(0); + } + + /* head did not start a synced page... skipped some bytes */ + if(!oy->unsynced){ + oy->unsynced=1; + return(-1); + } + + /* loop. keep looking */ + + } +} + +/* add the incoming page to the stream state; we decompose the page + into packet segments here as well. */ + +int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og){ + unsigned char *header=og->header; + unsigned char *body=og->body; + long bodysize=og->body_len; + int segptr=0; + + int version=ogg_page_version(og); + int continued=ogg_page_continued(og); + int bos=ogg_page_bos(og); + int eos=ogg_page_eos(og); + ogg_int64_t granulepos=ogg_page_granulepos(og); + int serialno=ogg_page_serialno(og); + long pageno=ogg_page_pageno(og); + int segments=header[26]; + + if(ogg_stream_check(os)) return -1; + + /* clean up 'returned data' */ + { + long lr=os->lacing_returned; + long br=os->body_returned; + + /* body data */ + if(br){ + os->body_fill-=br; + if(os->body_fill) + memmove(os->body_data,os->body_data+br,os->body_fill); + os->body_returned=0; + } + + if(lr){ + /* segment table */ + if(os->lacing_fill-lr){ + memmove(os->lacing_vals,os->lacing_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->granule_vals)); + } + os->lacing_fill-=lr; + os->lacing_packet-=lr; + os->lacing_returned=0; + } + } + + /* check the serial number */ + if(serialno!=os->serialno)return(-1); + if(version>0)return(-1); + + if(_os_lacing_expand(os,segments+1)) return -1; + + /* are we in sequence? */ + if(pageno!=os->pageno){ + int i; + + /* unroll previous partial packet (if any) */ + for(i=os->lacing_packet;ilacing_fill;i++) + os->body_fill-=os->lacing_vals[i]&0xff; + os->lacing_fill=os->lacing_packet; + + /* make a note of dropped data in segment table */ + if(os->pageno!=-1){ + os->lacing_vals[os->lacing_fill++]=0x400; + os->lacing_packet++; + } + } + + /* are we a 'continued packet' page? If so, we may need to skip + some segments */ + if(continued){ + if(os->lacing_fill<1 || + os->lacing_vals[os->lacing_fill-1]==0x400){ + bos=0; + for(;segptrbody_data+os->body_fill,body,bodysize); + os->body_fill+=bodysize; + } + + { + int saved=-1; + while(segptrlacing_vals[os->lacing_fill]=val; + os->granule_vals[os->lacing_fill]=-1; + + if(bos){ + os->lacing_vals[os->lacing_fill]|=0x100; + bos=0; + } + + if(val<255)saved=os->lacing_fill; + + os->lacing_fill++; + segptr++; + + if(val<255)os->lacing_packet=os->lacing_fill; + } + + /* set the granulepos on the last granuleval of the last full packet */ + if(saved!=-1){ + os->granule_vals[saved]=granulepos; + } + + } + + if(eos){ + os->e_o_s=1; + if(os->lacing_fill>0) + os->lacing_vals[os->lacing_fill-1]|=0x200; + } + + os->pageno=pageno+1; + + return(0); +} + +/* clear things to an initial state. Good to call, eg, before seeking */ +int ogg_sync_reset(ogg_sync_state *oy){ + if(ogg_sync_check(oy))return -1; + + oy->fill=0; + oy->returned=0; + oy->unsynced=0; + oy->headerbytes=0; + oy->bodybytes=0; + return(0); +} + +int ogg_stream_reset(ogg_stream_state *os){ + if(ogg_stream_check(os)) return -1; + + os->body_fill=0; + os->body_returned=0; + + os->lacing_fill=0; + os->lacing_packet=0; + os->lacing_returned=0; + + os->header_fill=0; + + os->e_o_s=0; + os->b_o_s=0; + os->pageno=-1; + os->packetno=0; + os->granulepos=0; + + return(0); +} + +int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno){ + if(ogg_stream_check(os)) return -1; + ogg_stream_reset(os); + os->serialno=serialno; + return(0); +} + +static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){ + + /* The last part of decode. We have the stream broken into packet + segments. Now we need to group them into packets (or return the + out of sync markers) */ + + int ptr=os->lacing_returned; + + if(os->lacing_packet<=ptr)return(0); + + if(os->lacing_vals[ptr]&0x400){ + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + os->lacing_returned++; + os->packetno++; + return(-1); + } + + if(!op && !adv)return(1); /* just using peek as an inexpensive way + to ask if there's a whole packet + waiting */ + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=os->lacing_vals[ptr]&0xff; + long bytes=size; + int eos=os->lacing_vals[ptr]&0x200; /* last packet of the stream? */ + int bos=os->lacing_vals[ptr]&0x100; /* first packet of the stream? */ + + while(size==255){ + int val=os->lacing_vals[++ptr]; + size=val&0xff; + if(val&0x200)eos=0x200; + bytes+=size; + } + + if(op){ + op->e_o_s=eos; + op->b_o_s=bos; + op->packet=os->body_data+os->body_returned; + op->packetno=os->packetno; + op->granulepos=os->granule_vals[ptr]; + op->bytes=bytes; + } + + if(adv){ + os->body_returned+=bytes; + os->lacing_returned=ptr+1; + os->packetno++; + } + } + return(1); +} + +int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; + return _packetout(os,op,1); +} + +int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; + return _packetout(os,op,0); +} + +void ogg_packet_clear(ogg_packet *op) { + _ogg_free(op->packet); + memset(op, 0, sizeof(*op)); +} + +#ifdef _V_SELFTEST +#include + +ogg_stream_state os_en, os_de; +ogg_sync_state oy; + +void checkpacket(ogg_packet *op,long len, int no, long pos){ + long j; + static int sequence=0; + static int lastno=0; + + if(op->bytes!=len){ + fprintf(stderr,"incorrect packet length (%ld != %ld)!\n",op->bytes,len); + exit(1); + } + if(op->granulepos!=pos){ + fprintf(stderr,"incorrect packet granpos (%ld != %ld)!\n",(long)op->granulepos,pos); + exit(1); + } + + /* packet number just follows sequence/gap; adjust the input number + for that */ + if(no==0){ + sequence=0; + }else{ + sequence++; + if(no>lastno+1) + sequence++; + } + lastno=no; + if(op->packetno!=sequence){ + fprintf(stderr,"incorrect packet sequence %ld != %d\n", + (long)(op->packetno),sequence); + exit(1); + } + + /* Test data */ + for(j=0;jbytes;j++) + if(op->packet[j]!=((j+no)&0xff)){ + fprintf(stderr,"body data mismatch (1) at pos %ld: %x!=%lx!\n\n", + j,op->packet[j],(j+no)&0xff); + exit(1); + } +} + +void check_page(unsigned char *data,const int *header,ogg_page *og){ + long j; + /* Test data */ + for(j=0;jbody_len;j++) + if(og->body[j]!=data[j]){ + fprintf(stderr,"body data mismatch (2) at pos %ld: %x!=%x!\n\n", + j,data[j],og->body[j]); + exit(1); + } + + /* Test header */ + for(j=0;jheader_len;j++){ + if(og->header[j]!=header[j]){ + fprintf(stderr,"header content mismatch at pos %ld:\n",j); + for(j=0;jheader[j]); + fprintf(stderr,"\n"); + exit(1); + } + } + if(og->header_len!=header[26]+27){ + fprintf(stderr,"header length incorrect! (%ld!=%d)\n", + og->header_len,header[26]+27); + exit(1); + } +} + +void print_header(ogg_page *og){ + int j; + fprintf(stderr,"\nHEADER:\n"); + fprintf(stderr," capture: %c %c %c %c version: %d flags: %x\n", + og->header[0],og->header[1],og->header[2],og->header[3], + (int)og->header[4],(int)og->header[5]); + + fprintf(stderr," granulepos: %d serialno: %d pageno: %ld\n", + (og->header[9]<<24)|(og->header[8]<<16)| + (og->header[7]<<8)|og->header[6], + (og->header[17]<<24)|(og->header[16]<<16)| + (og->header[15]<<8)|og->header[14], + ((long)(og->header[21])<<24)|(og->header[20]<<16)| + (og->header[19]<<8)|og->header[18]); + + fprintf(stderr," checksum: %02x:%02x:%02x:%02x\n segments: %d (", + (int)og->header[22],(int)og->header[23], + (int)og->header[24],(int)og->header[25], + (int)og->header[26]); + + for(j=27;jheader_len;j++) + fprintf(stderr,"%d ",(int)og->header[j]); + fprintf(stderr,")\n\n"); +} + +void copy_page(ogg_page *og){ + unsigned char *temp=_ogg_malloc(og->header_len); + memcpy(temp,og->header,og->header_len); + og->header=temp; + + temp=_ogg_malloc(og->body_len); + memcpy(temp,og->body,og->body_len); + og->body=temp; +} + +void free_page(ogg_page *og){ + _ogg_free (og->header); + _ogg_free (og->body); +} + +void error(void){ + fprintf(stderr,"error!\n"); + exit(1); +} + +/* 17 only */ +const int head1_0[] = {0x4f,0x67,0x67,0x53,0,0x06, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x15,0xed,0xec,0x91, + 1, + 17}; + +/* 17, 254, 255, 256, 500, 510, 600 byte, pad */ +const int head1_1[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x59,0x10,0x6c,0x2c, + 1, + 17}; +const int head2_1[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x89,0x33,0x85,0xce, + 13, + 254,255,0,255,1,255,245,255,255,0, + 255,255,90}; + +/* nil packets; beginning,middle,end */ +const int head1_2[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; +const int head2_2[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x5c,0x3f,0x66,0xcb, + 17, + 17,254,255,0,0,255,1,0,255,245,255,255,0, + 255,255,90,0}; + +/* large initial packet */ +const int head1_3[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x01,0x27,0x31,0xaa, + 18, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10}; + +const int head2_3[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x7f,0x4e,0x8a,0xd2, + 4, + 255,4,255,0}; + + +/* continuing packet test */ +const int head1_4[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xf8,0x3c,0x19,0x79, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255}; + +const int head3_4[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x38,0xe6,0xb6,0x28, + 6, + 255,220,255,4,255,0}; + + +/* spill expansion test */ +const int head1_4b[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4b[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xce,0x8f,0x17,0x1a, + 23, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10,255,4,255,0,0}; + + +const int head3_4b[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x9b,0xb2,0x50,0xa1, + 1, + 0}; + +/* page with the 255 segment limit */ +const int head1_5[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_5[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xed,0x2a,0x2e,0xa7, + 255, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10}; + +const int head3_5[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x6c,0x3b,0x82,0x3d, + 1, + 50}; + + +/* packet that overspans over an entire page */ +const int head1_6[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_6[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255}; + +const int head3_6[] = {0x4f,0x67,0x67,0x53,0,0x01, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xf4,0x87,0xba,0xf3, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255}; + +const int head4_6[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,3,0,0,0, + 0xf7,0x2f,0x6c,0x60, + 5, + 254,255,4,255,0}; + +/* packet that overspans over an entire page */ +const int head1_7[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_7[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255}; + +const int head3_7[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xd4,0xe0,0x60,0xe5, + 1, + 0}; + +void test_pack(const int *pl, const int **headers, int byteskip, + int pageskip, int packetskip){ + unsigned char *data=_ogg_malloc(1024*1024); /* for scripted test cases only */ + long inptr=0; + long outptr=0; + long deptr=0; + long depacket=0; + long granule_pos=7,pageno=0; + int i,j,packets,pageout=pageskip; + int eosflag=0; + int bosflag=0; + + int byteskipcount=0; + + ogg_stream_reset(&os_en); + ogg_stream_reset(&os_de); + ogg_sync_reset(&oy); + + for(packets=0;packetsbyteskip){ + memcpy(next,og.header,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + byteskipcount+=og.body_len; + if(byteskipcount>byteskip){ + memcpy(next,og.body,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + ogg_sync_wrote(&oy,next-buf); + + while(1){ + int ret=ogg_sync_pageout(&oy,&og_de); + if(ret==0)break; + if(ret<0)continue; + /* got a page. Happy happy. Verify that it's good. */ + + fprintf(stderr,"(%d), ",pageout); + + check_page(data+deptr,headers[pageout],&og_de); + deptr+=og_de.body_len; + pageout++; + + /* submit it to deconstitution */ + ogg_stream_pagein(&os_de,&og_de); + + /* packets out? */ + while(ogg_stream_packetpeek(&os_de,&op_de2)>0){ + ogg_stream_packetpeek(&os_de,NULL); + ogg_stream_packetout(&os_de,&op_de); /* just catching them all */ + + /* verify peek and out match */ + if(memcmp(&op_de,&op_de2,sizeof(op_de))){ + fprintf(stderr,"packetout != packetpeek! pos=%ld\n", + depacket); + exit(1); + } + + /* verify the packet! */ + /* check data */ + if(memcmp(data+depacket,op_de.packet,op_de.bytes)){ + fprintf(stderr,"packet data mismatch in decode! pos=%ld\n", + depacket); + exit(1); + } + /* check bos flag */ + if(bosflag==0 && op_de.b_o_s==0){ + fprintf(stderr,"b_o_s flag not set on packet!\n"); + exit(1); + } + if(bosflag && op_de.b_o_s){ + fprintf(stderr,"b_o_s flag incorrectly set on packet!\n"); + exit(1); + } + bosflag=1; + depacket+=op_de.bytes; + + /* check eos flag */ + if(eosflag){ + fprintf(stderr,"Multiple decoded packets with eos flag!\n"); + exit(1); + } + + if(op_de.e_o_s)eosflag=1; + + /* check granulepos flag */ + if(op_de.granulepos!=-1){ + fprintf(stderr," granule:%ld ",(long)op_de.granulepos); + } + } + } + } + } + } + } + _ogg_free(data); + if(headers[pageno]!=NULL){ + fprintf(stderr,"did not write last page!\n"); + exit(1); + } + if(headers[pageout]!=NULL){ + fprintf(stderr,"did not decode last page!\n"); + exit(1); + } + if(inptr!=outptr){ + fprintf(stderr,"encoded page data incomplete!\n"); + exit(1); + } + if(inptr!=deptr){ + fprintf(stderr,"decoded page data incomplete!\n"); + exit(1); + } + if(inptr!=depacket){ + fprintf(stderr,"decoded packet data incomplete!\n"); + exit(1); + } + if(!eosflag){ + fprintf(stderr,"Never got a packet with EOS set!\n"); + exit(1); + } + fprintf(stderr,"ok.\n"); +} + +int main(void){ + + ogg_stream_init(&os_en,0x04030201); + ogg_stream_init(&os_de,0x04030201); + ogg_sync_init(&oy); + + /* Exercise each code path in the framing code. Also verify that + the checksums are working. */ + + { + /* 17 only */ + const int packets[]={17, -1}; + const int *headret[]={head1_0,NULL}; + + fprintf(stderr,"testing single page encoding... "); + test_pack(packets,headret,0,0,0); + } + + { + /* 17, 254, 255, 256, 500, 510, 600 byte, pad */ + const int packets[]={17, 254, 255, 256, 500, 510, 600, -1}; + const int *headret[]={head1_1,head2_1,NULL}; + + fprintf(stderr,"testing basic page encoding... "); + test_pack(packets,headret,0,0,0); + } + + { + /* nil packets; beginning,middle,end */ + const int packets[]={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1}; + const int *headret[]={head1_2,head2_2,NULL}; + + fprintf(stderr,"testing basic nil packets... "); + test_pack(packets,headret,0,0,0); + } + + { + /* large initial packet */ + const int packets[]={4345,259,255,-1}; + const int *headret[]={head1_3,head2_3,NULL}; + + fprintf(stderr,"testing initial-packet lacing > 4k... "); + test_pack(packets,headret,0,0,0); + } + + { + /* continuing packet test; with page spill expansion, we have to + overflow the lacing table. */ + const int packets[]={0,65500,259,255,-1}; + const int *headret[]={head1_4,head2_4,head3_4,NULL}; + + fprintf(stderr,"testing single packet page span... "); + test_pack(packets,headret,0,0,0); + } + + { + /* spill expand packet test */ + const int packets[]={0,4345,259,255,0,0,-1}; + const int *headret[]={head1_4b,head2_4b,head3_4b,NULL}; + + fprintf(stderr,"testing page spill expansion... "); + test_pack(packets,headret,0,0,0); + } + + /* page with the 255 segment limit */ + { + + const int packets[]={0,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,50,-1}; + const int *headret[]={head1_5,head2_5,head3_5,NULL}; + + fprintf(stderr,"testing max packet segments... "); + test_pack(packets,headret,0,0,0); + } + + { + /* packet that overspans over an entire page */ + const int packets[]={0,100,130049,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing very large packets... "); + test_pack(packets,headret,0,0,0); + } + + { + /* test for the libogg 1.1.1 resync in large continuation bug + found by Josh Coalson) */ + const int packets[]={0,100,130049,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing continuation resync in very large packets... "); + test_pack(packets,headret,100,2,3); + } + + { + /* term only page. why not? */ + const int packets[]={0,100,64770,-1}; + const int *headret[]={head1_7,head2_7,head3_7,NULL}; + + fprintf(stderr,"testing zero data page (1 nil packet)... "); + test_pack(packets,headret,0,0,0); + } + + + + { + /* build a bunch of pages for testing */ + unsigned char *data=_ogg_malloc(1024*1024); + int pl[]={0, 1,1,98,4079, 1,1,2954,2057, 76,34,912,0,234,1000,1000, 1000,300,-1}; + int inptr=0,i,j; + ogg_page og[5]; + + ogg_stream_reset(&os_en); + + for(i=0;pl[i]!=-1;i++){ + ogg_packet op; + int len=pl[i]; + + op.packet=data+inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.granulepos=(i+1)*1000; + + for(j=0;j0)error(); + + /* Test fractional page inputs: incomplete fixed header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+3, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+23, + 5); + ogg_sync_wrote(&oy,5); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete body */ + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+28, + og[1].header_len-28); + ogg_sync_wrote(&oy,og[1].header_len-28); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body,1000); + ogg_sync_wrote(&oy,1000); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body+1000, + og[1].body_len-1000); + ogg_sync_wrote(&oy,og[1].body_len-1000); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test fractional page inputs: page + incomplete capture */ + { + ogg_page og_de; + fprintf(stderr,"Testing sync on 1+partial inputs... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+20, + og[1].header_len-20); + ogg_sync_wrote(&oy,og[1].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing search for capture... "); + ogg_sync_reset(&oy); + + /* 'garbage' */ + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header+20, + og[2].header_len-20); + ogg_sync_wrote(&oy,og[2].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len); + ogg_sync_wrote(&oy,og[2].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: page + garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing recapture... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len-5); + ogg_sync_wrote(&oy,og[2].body_len-5); + + memcpy(ogg_sync_buffer(&oy,og[3].header_len),og[3].header, + og[3].header_len); + ogg_sync_wrote(&oy,og[3].header_len); + + memcpy(ogg_sync_buffer(&oy,og[3].body_len),og[3].body, + og[3].body_len); + ogg_sync_wrote(&oy,og[3].body_len); + + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Free page data that was previously copied */ + { + for(i=0;i<5;i++){ + free_page(&og[i]); + } + } + } + + return(0); +} + +#endif diff --git a/Libraries/speex/include/ogg/config_types.h b/Libraries/speex/include/ogg/config_types.h new file mode 100644 index 000000000..00962788e --- /dev/null +++ b/Libraries/speex/include/ogg/config_types.h @@ -0,0 +1,25 @@ +#ifndef __CONFIG_TYPES_H__ +#define __CONFIG_TYPES_H__ + +/* these are filled in by configure */ +#define INCLUDE_INTTYPES_H 1 +#define INCLUDE_STDINT_H 1 +#define INCLUDE_SYS_TYPES_H 1 + +#if INCLUDE_INTTYPES_H +# include +#endif +#if INCLUDE_STDINT_H +# include +#endif +#if INCLUDE_SYS_TYPES_H +# include +#endif + +typedef short ogg_int16_t; +typedef unsigned short ogg_uint16_t; +typedef int ogg_int32_t; +typedef unsigned int ogg_uint32_t; +typedef long ogg_int64_t; + +#endif diff --git a/Libraries/speex/include/ogg/ogg.h b/Libraries/speex/include/ogg/ogg.h new file mode 100644 index 000000000..cea4ebed7 --- /dev/null +++ b/Libraries/speex/include/ogg/ogg.h @@ -0,0 +1,210 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2007 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel libogg include + last mod: $Id: ogg.h 18044 2011-08-01 17:55:20Z gmaxwell $ + + ********************************************************************/ +#ifndef _OGG_H +#define _OGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + void *iov_base; + size_t iov_len; +} ogg_iovec_t; + +typedef struct { + long endbyte; + int endbit; + + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + unsigned char *header; + long header_len; + unsigned char *body; + long body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + long body_storage; /* storage elements allocated */ + long body_fill; /* elements stored; fill mark */ + long body_returned; /* elements of fill returned */ + + + int *lacing_vals; /* The values that will go to the segment table */ + ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + int header_fill; + + int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + long serialno; + long pageno; + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ + ogg_int64_t granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + unsigned char *packet; + long bytes; + long b_o_s; + long e_o_s; + + ogg_int64_t granulepos; + + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + unsigned char *data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +/* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ + +extern void oggpack_writeinit(oggpack_buffer *b); +extern int oggpack_writecheck(oggpack_buffer *b); +extern void oggpack_writetrunc(oggpack_buffer *b,long bits); +extern void oggpack_writealign(oggpack_buffer *b); +extern void oggpack_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpack_reset(oggpack_buffer *b); +extern void oggpack_writeclear(oggpack_buffer *b); +extern void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpack_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpack_look(oggpack_buffer *b,int bits); +extern long oggpack_look1(oggpack_buffer *b); +extern void oggpack_adv(oggpack_buffer *b,int bits); +extern void oggpack_adv1(oggpack_buffer *b); +extern long oggpack_read(oggpack_buffer *b,int bits); +extern long oggpack_read1(oggpack_buffer *b); +extern long oggpack_bytes(oggpack_buffer *b); +extern long oggpack_bits(oggpack_buffer *b); +extern unsigned char *oggpack_get_buffer(oggpack_buffer *b); + +extern void oggpackB_writeinit(oggpack_buffer *b); +extern int oggpackB_writecheck(oggpack_buffer *b); +extern void oggpackB_writetrunc(oggpack_buffer *b,long bits); +extern void oggpackB_writealign(oggpack_buffer *b); +extern void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpackB_reset(oggpack_buffer *b); +extern void oggpackB_writeclear(oggpack_buffer *b); +extern void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpackB_look(oggpack_buffer *b,int bits); +extern long oggpackB_look1(oggpack_buffer *b); +extern void oggpackB_adv(oggpack_buffer *b,int bits); +extern void oggpackB_adv1(oggpack_buffer *b); +extern long oggpackB_read(oggpack_buffer *b,int bits); +extern long oggpackB_read1(oggpack_buffer *b); +extern long oggpackB_bytes(oggpack_buffer *b); +extern long oggpackB_bits(oggpack_buffer *b); +extern unsigned char *oggpackB_get_buffer(oggpack_buffer *b); + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +extern int ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); +extern int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, + int count, long e_o_s, ogg_int64_t granulepos); +extern int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill); +extern int ogg_stream_flush(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_flush_fill(ogg_stream_state *os, ogg_page *og, int nfill); + +/* Ogg BITSTREAM PRIMITIVES: decoding **************************/ + +extern int ogg_sync_init(ogg_sync_state *oy); +extern int ogg_sync_clear(ogg_sync_state *oy); +extern int ogg_sync_reset(ogg_sync_state *oy); +extern int ogg_sync_destroy(ogg_sync_state *oy); +extern int ogg_sync_check(ogg_sync_state *oy); + +extern char *ogg_sync_buffer(ogg_sync_state *oy, long size); +extern int ogg_sync_wrote(ogg_sync_state *oy, long bytes); +extern long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); +extern int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); +extern int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); +extern int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +extern int ogg_stream_init(ogg_stream_state *os,int serialno); +extern int ogg_stream_clear(ogg_stream_state *os); +extern int ogg_stream_reset(ogg_stream_state *os); +extern int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno); +extern int ogg_stream_destroy(ogg_stream_state *os); +extern int ogg_stream_check(ogg_stream_state *os); +extern int ogg_stream_eos(ogg_stream_state *os); + +extern void ogg_page_checksum_set(ogg_page *og); + +extern int ogg_page_version(const ogg_page *og); +extern int ogg_page_continued(const ogg_page *og); +extern int ogg_page_bos(const ogg_page *og); +extern int ogg_page_eos(const ogg_page *og); +extern ogg_int64_t ogg_page_granulepos(const ogg_page *og); +extern int ogg_page_serialno(const ogg_page *og); +extern long ogg_page_pageno(const ogg_page *og); +extern int ogg_page_packets(const ogg_page *og); + +extern void ogg_packet_clear(ogg_packet *op); + + +#ifdef __cplusplus +} +#endif + +#endif /* _OGG_H */ diff --git a/Libraries/speex/include/ogg/os_types.h b/Libraries/speex/include/ogg/os_types.h new file mode 100644 index 000000000..d6691b703 --- /dev/null +++ b/Libraries/speex/include/ogg/os_types.h @@ -0,0 +1,147 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: #ifdef jail to whip a few platforms into the UNIX ideal. + last mod: $Id: os_types.h 17712 2010-12-03 17:10:02Z xiphmont $ + + ********************************************************************/ +#ifndef _OS_TYPES_H +#define _OS_TYPES_H + +/* make it easy on the folks that want to compile the libs with a + different malloc than stdlib */ +#define _ogg_malloc malloc +#define _ogg_calloc calloc +#define _ogg_realloc realloc +#define _ogg_free free + +#if defined(_WIN32) + +# if defined(__CYGWIN__) +# include + typedef int16_t ogg_int16_t; + typedef uint16_t ogg_uint16_t; + typedef int32_t ogg_int32_t; + typedef uint32_t ogg_uint32_t; + typedef int64_t ogg_int64_t; + typedef uint64_t ogg_uint64_t; +# elif defined(__MINGW32__) +# include + typedef short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + typedef unsigned long long ogg_uint64_t; +# elif defined(__MWERKS__) + typedef long long ogg_int64_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef short ogg_int16_t; + typedef unsigned short ogg_uint16_t; +# else + /* MSVC/Borland */ + typedef __int64 ogg_int64_t; + typedef __int32 ogg_int32_t; + typedef unsigned __int32 ogg_uint32_t; + typedef __int16 ogg_int16_t; + typedef unsigned __int16 ogg_uint16_t; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 ogg_int16_t; + typedef UInt16 ogg_uint16_t; + typedef SInt32 ogg_int32_t; + typedef UInt32 ogg_uint32_t; + typedef SInt64 ogg_int64_t; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t ogg_int16_t; + typedef uint16_t ogg_uint16_t; + typedef int32_t ogg_int32_t; + typedef uint32_t ogg_uint32_t; + typedef int64_t ogg_int64_t; + +#elif defined(__HAIKU__) + + /* Haiku */ +# include + typedef short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16_t ogg_int16_t; + typedef uint16_t ogg_uint16_t; + typedef int32_t ogg_int32_t; + typedef uint32_t ogg_uint32_t; + typedef int64_t ogg_int64_t; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short ogg_int16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + +#elif defined(R5900) + + /* PS2 EE */ + typedef long ogg_int64_t; + typedef int ogg_int32_t; + typedef unsigned ogg_uint32_t; + typedef short ogg_int16_t; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef signed int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long int ogg_int64_t; + +#elif defined(__TMS320C6X__) + + /* TI C64x compiler */ + typedef signed short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef signed int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long int ogg_int64_t; + +#else + +# include + +#endif + +#endif /* _OS_TYPES_H */ diff --git a/Libraries/speex/include/speex/Makefile b/Libraries/speex/include/speex/Makefile new file mode 100644 index 000000000..63e1bdaeb --- /dev/null +++ b/Libraries/speex/include/speex/Makefile @@ -0,0 +1,440 @@ +# Makefile.in generated by automake 1.8.5 from Makefile.am. +# include/speex/Makefile. Generated from Makefile.in by configure. + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + + +# Disable automatic dependency tracking if using other tools than gcc and gmake +#AUTOMAKE_OPTIONS = no-dependencies + +srcdir = . +top_srcdir = ../.. + +pkgdatadir = $(datadir)/speex +pkglibdir = $(libdir)/speex +pkgincludedir = $(includedir)/speex +top_builddir = ../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = /usr/bin/install -c +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +host_triplet = i386-apple-darwin12.2.0 +subdir = include/speex +DIST_COMMON = $(pkginclude_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in $(srcdir)/speex_config_types.h.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(mkdir_p) +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = speex_config_types.h +SOURCES = +DIST_SOURCES = +am__installdirs = "$(DESTDIR)$(pkgincludedir)" "$(DESTDIR)$(pkgincludedir)" +nodist_pkgincludeHEADERS_INSTALL = $(INSTALL_HEADER) +pkgincludeHEADERS_INSTALL = $(INSTALL_HEADER) +HEADERS = $(nodist_pkginclude_HEADERS) $(pkginclude_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run aclocal-1.8 +AMDEP_FALSE = # +AMDEP_TRUE = +AMTAR = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run tar +AR = ar +AS = as +AUTOCONF = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run autoconf +AUTOHEADER = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run autoheader +AUTOMAKE = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run automake-1.8 +AWK = awk +BUILD_KISS_FFT_FALSE = +BUILD_KISS_FFT_TRUE = # +BUILD_SMALLFT_FALSE = # +BUILD_SMALLFT_TRUE = +CC = gcc +CCDEPMODE = depmode=gcc3 +CFLAGS = -g -O2 -fvisibility=hidden +CPP = gcc -E +CPPFLAGS = +CXX = g++ +CXXCPP = g++ -E +CXXDEPMODE = depmode=gcc3 +CXXFLAGS = -g -O2 +CYGPATH_W = echo +DEFS = -DHAVE_CONFIG_H +DEPDIR = .deps +DLLTOOL = dlltool +DSYMUTIL = dsymutil +ECHO = /bin/echo +ECHO_C = \c +ECHO_N = +ECHO_T = +EGREP = /usr/bin/grep -E +EXEEXT = +F77 = +FFLAGS = +FFT_CFLAGS = +FFT_LIBS = +FFT_PKGCONFIG = +GREP = /usr/bin/grep +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = ${SHELL} $(install_sh) -c -s +LDFLAGS = +LIBOBJS = +LIBS = -lm +LIBTOOL = $(SHELL) $(top_builddir)/libtool +LN_S = ln -s +LTLIBOBJS = +MAINT = # +MAINTAINER_MODE_FALSE = +MAINTAINER_MODE_TRUE = # +MAKEINFO = ${SHELL} /Users/petar/Downloads/speex-1.2rc1/missing --run makeinfo +NMEDIT = nmedit +OBJDUMP = objdump +OBJEXT = o +OGG_CFLAGS = +OGG_LIBS = -logg +PACKAGE = speex +PACKAGE_BUGREPORT = +PACKAGE_NAME = +PACKAGE_STRING = +PACKAGE_TARNAME = +PACKAGE_VERSION = +PATH_SEPARATOR = : +PKG_CONFIG = +RANLIB = ranlib +SED = /usr/bin/sed +SET_MAKE = +SHELL = /bin/sh +SIZE16 = short +SIZE32 = int +SPEEX_LT_AGE = 5 +SPEEX_LT_CURRENT = 6 +SPEEX_LT_REVISION = 0 +SPEEX_VERSION = 1.2rc1 +STRIP = strip +VERSION = 1.2rc1 +ac_ct_CC = gcc +ac_ct_CXX = g++ +ac_ct_F77 = +am__fastdepCC_FALSE = # +am__fastdepCC_TRUE = +am__fastdepCXX_FALSE = # +am__fastdepCXX_TRUE = +am__include = include +am__leading_dot = . +am__quote = +bindir = ${exec_prefix}/bin +build = i386-apple-darwin12.2.0 +build_alias = +build_cpu = i386 +build_os = darwin12.2.0 +build_vendor = apple +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE} +dvidir = ${docdir} +exec_prefix = ${prefix} +host = i386-apple-darwin12.2.0 +host_alias = +host_cpu = i386 +host_os = darwin12.2.0 +host_vendor = apple +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${datarootdir}/info +install_sh = /Users/petar/Downloads/speex-1.2rc1/install-sh +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/libexec +localedir = ${datarootdir}/locale +localstatedir = ${prefix}/var +mandir = ${datarootdir}/man +mkdir_p = $(install_sh) -d +oldincludedir = /usr/include +pdfdir = ${docdir} +prefix = /usr/local +program_transform_name = s,x,x, +psdir = ${docdir} +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +src = src +sysconfdir = ${prefix}/etc +target_alias = +nodist_pkginclude_HEADERS = speex_config_types.h +pkginclude_HEADERS = speex.h speex_bits.h speex_buffer.h speex_callbacks.h \ + speex_echo.h speex_header.h speex_jitter.h speex_preprocess.h speex_resampler.h \ + speex_stereo.h speex_types.h + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: # $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/speex/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu include/speex/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: # $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): # $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +speex_config_types.h: $(top_builddir)/config.status $(srcdir)/speex_config_types.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: +install-nodist_pkgincludeHEADERS: $(nodist_pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(mkdir_p) "$(DESTDIR)$(pkgincludedir)" + @list='$(nodist_pkginclude_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " $(nodist_pkgincludeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + $(nodist_pkgincludeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +uninstall-nodist_pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_pkginclude_HEADERS)'; for p in $$list; do \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " rm -f '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + rm -f "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(mkdir_p) "$(DESTDIR)$(pkgincludedir)" + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " $(pkgincludeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + $(pkgincludeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " rm -f '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + rm -f "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkgincludedir)" "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-nodist_pkgincludeHEADERS \ + install-pkgincludeHEADERS + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am uninstall-nodist_pkgincludeHEADERS \ + uninstall-pkgincludeHEADERS + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool ctags distclean distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-man install-nodist_pkgincludeHEADERS \ + install-pkgincludeHEADERS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-info-am \ + uninstall-nodist_pkgincludeHEADERS uninstall-pkgincludeHEADERS + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/Libraries/speex/include/speex/Makefile.am b/Libraries/speex/include/speex/Makefile.am new file mode 100644 index 000000000..2ae34f9f8 --- /dev/null +++ b/Libraries/speex/include/speex/Makefile.am @@ -0,0 +1,9 @@ +# Disable automatic dependency tracking if using other tools than gcc and gmake +#AUTOMAKE_OPTIONS = no-dependencies + +nodist_pkginclude_HEADERS = speex_config_types.h + +pkginclude_HEADERS = speex.h speex_bits.h speex_buffer.h speex_callbacks.h \ + speex_echo.h speex_header.h speex_jitter.h speex_preprocess.h speex_resampler.h \ + speex_stereo.h speex_types.h + diff --git a/Libraries/speex/include/speex/Makefile.in b/Libraries/speex/include/speex/Makefile.in new file mode 100644 index 000000000..9c0a33d08 --- /dev/null +++ b/Libraries/speex/include/speex/Makefile.in @@ -0,0 +1,440 @@ +# Makefile.in generated by automake 1.8.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Disable automatic dependency tracking if using other tools than gcc and gmake +#AUTOMAKE_OPTIONS = no-dependencies + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = ../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +host_triplet = @host@ +subdir = include/speex +DIST_COMMON = $(pkginclude_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in $(srcdir)/speex_config_types.h.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(mkdir_p) +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = speex_config_types.h +SOURCES = +DIST_SOURCES = +am__installdirs = "$(DESTDIR)$(pkgincludedir)" "$(DESTDIR)$(pkgincludedir)" +nodist_pkgincludeHEADERS_INSTALL = $(INSTALL_HEADER) +pkgincludeHEADERS_INSTALL = $(INSTALL_HEADER) +HEADERS = $(nodist_pkginclude_HEADERS) $(pkginclude_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_KISS_FFT_FALSE = @BUILD_KISS_FFT_FALSE@ +BUILD_KISS_FFT_TRUE = @BUILD_KISS_FFT_TRUE@ +BUILD_SMALLFT_FALSE = @BUILD_SMALLFT_FALSE@ +BUILD_SMALLFT_TRUE = @BUILD_SMALLFT_TRUE@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +FFT_CFLAGS = @FFT_CFLAGS@ +FFT_LIBS = @FFT_LIBS@ +FFT_PKGCONFIG = @FFT_PKGCONFIG@ +GREP = @GREP@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OGG_CFLAGS = @OGG_CFLAGS@ +OGG_LIBS = @OGG_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIZE16 = @SIZE16@ +SIZE32 = @SIZE32@ +SPEEX_LT_AGE = @SPEEX_LT_AGE@ +SPEEX_LT_CURRENT = @SPEEX_LT_CURRENT@ +SPEEX_LT_REVISION = @SPEEX_LT_REVISION@ +SPEEX_VERSION = @SPEEX_VERSION@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +src = @src@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +nodist_pkginclude_HEADERS = speex_config_types.h +pkginclude_HEADERS = speex.h speex_bits.h speex_buffer.h speex_callbacks.h \ + speex_echo.h speex_header.h speex_jitter.h speex_preprocess.h speex_resampler.h \ + speex_stereo.h speex_types.h + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/speex/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu include/speex/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +speex_config_types.h: $(top_builddir)/config.status $(srcdir)/speex_config_types.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: +install-nodist_pkgincludeHEADERS: $(nodist_pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(mkdir_p) "$(DESTDIR)$(pkgincludedir)" + @list='$(nodist_pkginclude_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " $(nodist_pkgincludeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + $(nodist_pkgincludeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +uninstall-nodist_pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_pkginclude_HEADERS)'; for p in $$list; do \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " rm -f '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + rm -f "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(mkdir_p) "$(DESTDIR)$(pkgincludedir)" + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " $(pkgincludeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + $(pkgincludeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " rm -f '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + rm -f "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkgincludedir)" "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-nodist_pkgincludeHEADERS \ + install-pkgincludeHEADERS + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am uninstall-nodist_pkgincludeHEADERS \ + uninstall-pkgincludeHEADERS + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool ctags distclean distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-man install-nodist_pkgincludeHEADERS \ + install-pkgincludeHEADERS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-info-am \ + uninstall-nodist_pkgincludeHEADERS uninstall-pkgincludeHEADERS + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/Libraries/speex/include/speex/speex.h b/Libraries/speex/include/speex/speex.h new file mode 100644 index 000000000..82ba01623 --- /dev/null +++ b/Libraries/speex/include/speex/speex.h @@ -0,0 +1,424 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin*/ +/** + @file speex.h + @brief Describes the different modes of the codec +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_H +#define SPEEX_H +/** @defgroup Codec Speex encoder and decoder + * This is the Speex codec itself. + * @{ + */ + +#include "speex/speex_bits.h" +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Values allowed for *ctl() requests */ + +/** Set enhancement on/off (decoder only) */ +#define SPEEX_SET_ENH 0 +/** Get enhancement state (decoder only) */ +#define SPEEX_GET_ENH 1 + +/*Would be SPEEX_SET_FRAME_SIZE, but it's (currently) invalid*/ +/** Obtain frame size used by encoder/decoder */ +#define SPEEX_GET_FRAME_SIZE 3 + +/** Set quality value */ +#define SPEEX_SET_QUALITY 4 +/** Get current quality setting */ +/* #define SPEEX_GET_QUALITY 5 -- Doesn't make much sense, does it? */ + +/** Set sub-mode to use */ +#define SPEEX_SET_MODE 6 +/** Get current sub-mode in use */ +#define SPEEX_GET_MODE 7 + +/** Set low-band sub-mode to use (wideband only)*/ +#define SPEEX_SET_LOW_MODE 8 +/** Get current low-band mode in use (wideband only)*/ +#define SPEEX_GET_LOW_MODE 9 + +/** Set high-band sub-mode to use (wideband only)*/ +#define SPEEX_SET_HIGH_MODE 10 +/** Get current high-band mode in use (wideband only)*/ +#define SPEEX_GET_HIGH_MODE 11 + +/** Set VBR on (1) or off (0) */ +#define SPEEX_SET_VBR 12 +/** Get VBR status (1 for on, 0 for off) */ +#define SPEEX_GET_VBR 13 + +/** Set quality value for VBR encoding (0-10) */ +#define SPEEX_SET_VBR_QUALITY 14 +/** Get current quality value for VBR encoding (0-10) */ +#define SPEEX_GET_VBR_QUALITY 15 + +/** Set complexity of the encoder (0-10) */ +#define SPEEX_SET_COMPLEXITY 16 +/** Get current complexity of the encoder (0-10) */ +#define SPEEX_GET_COMPLEXITY 17 + +/** Set bit-rate used by the encoder (or lower) */ +#define SPEEX_SET_BITRATE 18 +/** Get current bit-rate used by the encoder or decoder */ +#define SPEEX_GET_BITRATE 19 + +/** Define a handler function for in-band Speex request*/ +#define SPEEX_SET_HANDLER 20 + +/** Define a handler function for in-band user-defined request*/ +#define SPEEX_SET_USER_HANDLER 22 + +/** Set sampling rate used in bit-rate computation */ +#define SPEEX_SET_SAMPLING_RATE 24 +/** Get sampling rate used in bit-rate computation */ +#define SPEEX_GET_SAMPLING_RATE 25 + +/** Reset the encoder/decoder memories to zero*/ +#define SPEEX_RESET_STATE 26 + +/** Get VBR info (mostly used internally) */ +#define SPEEX_GET_RELATIVE_QUALITY 29 + +/** Set VAD status (1 for on, 0 for off) */ +#define SPEEX_SET_VAD 30 + +/** Get VAD status (1 for on, 0 for off) */ +#define SPEEX_GET_VAD 31 + +/** Set Average Bit-Rate (ABR) to n bits per seconds */ +#define SPEEX_SET_ABR 32 +/** Get Average Bit-Rate (ABR) setting (in bps) */ +#define SPEEX_GET_ABR 33 + +/** Set DTX status (1 for on, 0 for off) */ +#define SPEEX_SET_DTX 34 +/** Get DTX status (1 for on, 0 for off) */ +#define SPEEX_GET_DTX 35 + +/** Set submode encoding in each frame (1 for yes, 0 for no, setting to no breaks the standard) */ +#define SPEEX_SET_SUBMODE_ENCODING 36 +/** Get submode encoding in each frame */ +#define SPEEX_GET_SUBMODE_ENCODING 37 + +/*#define SPEEX_SET_LOOKAHEAD 38*/ +/** Returns the lookahead used by Speex */ +#define SPEEX_GET_LOOKAHEAD 39 + +/** Sets tuning for packet-loss concealment (expected loss rate) */ +#define SPEEX_SET_PLC_TUNING 40 +/** Gets tuning for PLC */ +#define SPEEX_GET_PLC_TUNING 41 + +/** Sets the max bit-rate allowed in VBR mode */ +#define SPEEX_SET_VBR_MAX_BITRATE 42 +/** Gets the max bit-rate allowed in VBR mode */ +#define SPEEX_GET_VBR_MAX_BITRATE 43 + +/** Turn on/off input/output high-pass filtering */ +#define SPEEX_SET_HIGHPASS 44 +/** Get status of input/output high-pass filtering */ +#define SPEEX_GET_HIGHPASS 45 + +/** Get "activity level" of the last decoded frame, i.e. + how much damage we cause if we remove the frame */ +#define SPEEX_GET_ACTIVITY 47 + + +/* Preserving compatibility:*/ +/** Equivalent to SPEEX_SET_ENH */ +#define SPEEX_SET_PF 0 +/** Equivalent to SPEEX_GET_ENH */ +#define SPEEX_GET_PF 1 + + + + +/* Values allowed for mode queries */ +/** Query the frame size of a mode */ +#define SPEEX_MODE_FRAME_SIZE 0 + +/** Query the size of an encoded frame for a particular sub-mode */ +#define SPEEX_SUBMODE_BITS_PER_FRAME 1 + + + +/** Get major Speex version */ +#define SPEEX_LIB_GET_MAJOR_VERSION 1 +/** Get minor Speex version */ +#define SPEEX_LIB_GET_MINOR_VERSION 3 +/** Get micro Speex version */ +#define SPEEX_LIB_GET_MICRO_VERSION 5 +/** Get extra Speex version */ +#define SPEEX_LIB_GET_EXTRA_VERSION 7 +/** Get Speex version string */ +#define SPEEX_LIB_GET_VERSION_STRING 9 + +/*#define SPEEX_LIB_SET_ALLOC_FUNC 10 +#define SPEEX_LIB_GET_ALLOC_FUNC 11 +#define SPEEX_LIB_SET_FREE_FUNC 12 +#define SPEEX_LIB_GET_FREE_FUNC 13 + +#define SPEEX_LIB_SET_WARNING_FUNC 14 +#define SPEEX_LIB_GET_WARNING_FUNC 15 +#define SPEEX_LIB_SET_ERROR_FUNC 16 +#define SPEEX_LIB_GET_ERROR_FUNC 17 +*/ + +/** Number of defined modes in Speex */ +#define SPEEX_NB_MODES 3 + +/** modeID for the defined narrowband mode */ +#define SPEEX_MODEID_NB 0 + +/** modeID for the defined wideband mode */ +#define SPEEX_MODEID_WB 1 + +/** modeID for the defined ultra-wideband mode */ +#define SPEEX_MODEID_UWB 2 + +struct SpeexMode; + + +/* Prototypes for mode function pointers */ + +/** Encoder state initialization function */ +typedef void *(*encoder_init_func)(const struct SpeexMode *mode); + +/** Encoder state destruction function */ +typedef void (*encoder_destroy_func)(void *st); + +/** Main encoding function */ +typedef int (*encode_func)(void *state, void *in, SpeexBits *bits); + +/** Function for controlling the encoder options */ +typedef int (*encoder_ctl_func)(void *state, int request, void *ptr); + +/** Decoder state initialization function */ +typedef void *(*decoder_init_func)(const struct SpeexMode *mode); + +/** Decoder state destruction function */ +typedef void (*decoder_destroy_func)(void *st); + +/** Main decoding function */ +typedef int (*decode_func)(void *state, SpeexBits *bits, void *out); + +/** Function for controlling the decoder options */ +typedef int (*decoder_ctl_func)(void *state, int request, void *ptr); + + +/** Query function for a mode */ +typedef int (*mode_query_func)(const void *mode, int request, void *ptr); + +/** Struct defining a Speex mode */ +typedef struct SpeexMode { + /** Pointer to the low-level mode data */ + const void *mode; + + /** Pointer to the mode query function */ + mode_query_func query; + + /** The name of the mode (you should not rely on this to identify the mode)*/ + const char *modeName; + + /**ID of the mode*/ + int modeID; + + /**Version number of the bitstream (incremented every time we break + bitstream compatibility*/ + int bitstream_version; + + /** Pointer to encoder initialization function */ + encoder_init_func enc_init; + + /** Pointer to encoder destruction function */ + encoder_destroy_func enc_destroy; + + /** Pointer to frame encoding function */ + encode_func enc; + + /** Pointer to decoder initialization function */ + decoder_init_func dec_init; + + /** Pointer to decoder destruction function */ + decoder_destroy_func dec_destroy; + + /** Pointer to frame decoding function */ + decode_func dec; + + /** ioctl-like requests for encoder */ + encoder_ctl_func enc_ctl; + + /** ioctl-like requests for decoder */ + decoder_ctl_func dec_ctl; + +} SpeexMode; + +/** + * Returns a handle to a newly created Speex encoder state structure. For now, + * the "mode" argument can be &nb_mode or &wb_mode . In the future, more modes + * may be added. Note that for now if you have more than one channels to + * encode, you need one state per channel. + * + * @param mode The mode to use (either speex_nb_mode or speex_wb.mode) + * @return A newly created encoder state or NULL if state allocation fails + */ +void *speex_encoder_init(const SpeexMode *mode); + +/** Frees all resources associated to an existing Speex encoder state. + * @param state Encoder state to be destroyed */ +void speex_encoder_destroy(void *state); + +/** Uses an existing encoder state to encode one frame of speech pointed to by + "in". The encoded bit-stream is saved in "bits". + @param state Encoder state + @param in Frame that will be encoded with a +-2^15 range. This data MAY be + overwritten by the encoder and should be considered uninitialised + after the call. + @param bits Bit-stream where the data will be written + @return 0 if frame needs not be transmitted (DTX only), 1 otherwise + */ +int speex_encode(void *state, float *in, SpeexBits *bits); + +/** Uses an existing encoder state to encode one frame of speech pointed to by + "in". The encoded bit-stream is saved in "bits". + @param state Encoder state + @param in Frame that will be encoded with a +-2^15 range + @param bits Bit-stream where the data will be written + @return 0 if frame needs not be transmitted (DTX only), 1 otherwise + */ +int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits); + +/** Used like the ioctl function to control the encoder parameters + * + * @param state Encoder state + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_encoder_ctl(void *state, int request, void *ptr); + + +/** Returns a handle to a newly created decoder state structure. For now, + * the mode argument can be &nb_mode or &wb_mode . In the future, more modes + * may be added. Note that for now if you have more than one channels to + * decode, you need one state per channel. + * + * @param mode Speex mode (one of speex_nb_mode or speex_wb_mode) + * @return A newly created decoder state or NULL if state allocation fails + */ +void *speex_decoder_init(const SpeexMode *mode); + +/** Frees all resources associated to an existing decoder state. + * + * @param state State to be destroyed + */ +void speex_decoder_destroy(void *state); + +/** Uses an existing decoder state to decode one frame of speech from + * bit-stream bits. The output speech is saved written to out. + * + * @param state Decoder state + * @param bits Bit-stream from which to decode the frame (NULL if the packet was lost) + * @param out Where to write the decoded frame + * @return return status (0 for no error, -1 for end of stream, -2 corrupt stream) + */ +int speex_decode(void *state, SpeexBits *bits, float *out); + +/** Uses an existing decoder state to decode one frame of speech from + * bit-stream bits. The output speech is saved written to out. + * + * @param state Decoder state + * @param bits Bit-stream from which to decode the frame (NULL if the packet was lost) + * @param out Where to write the decoded frame + * @return return status (0 for no error, -1 for end of stream, -2 corrupt stream) + */ +int speex_decode_int(void *state, SpeexBits *bits, spx_int16_t *out); + +/** Used like the ioctl function to control the encoder parameters + * + * @param state Decoder state + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_decoder_ctl(void *state, int request, void *ptr); + + +/** Query function for mode information + * + * @param mode Speex mode + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_mode_query(const SpeexMode *mode, int request, void *ptr); + +/** Functions for controlling the behavior of libspeex + * @param request ioctl-type request (one of the SPEEX_LIB_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_lib_ctl(int request, void *ptr); + +/** Default narrowband mode */ +extern const SpeexMode speex_nb_mode; + +/** Default wideband mode */ +extern const SpeexMode speex_wb_mode; + +/** Default "ultra-wideband" mode */ +extern const SpeexMode speex_uwb_mode; + +/** List of all modes available */ +extern const SpeexMode * const speex_mode_list[SPEEX_NB_MODES]; + +/** Obtain one of the modes available */ +const SpeexMode * speex_lib_get_mode (int mode); + +#ifndef WIN32 +/* We actually override the function in the narrowband case so that we can avoid linking in the wideband stuff */ +#define speex_lib_get_mode(mode) ((mode)==SPEEX_MODEID_NB ? &speex_nb_mode : speex_lib_get_mode (mode)) +#endif + +#ifdef __cplusplus +} +#endif + +/** @}*/ +#endif diff --git a/Libraries/speex/include/speex/speex_bits.h b/Libraries/speex/include/speex/speex_bits.h new file mode 100644 index 000000000..a26fb4ce0 --- /dev/null +++ b/Libraries/speex/include/speex/speex_bits.h @@ -0,0 +1,174 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_bits.h + @brief Handles bit packing/unpacking +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef BITS_H +#define BITS_H +/** @defgroup SpeexBits SpeexBits: Bit-stream manipulations + * This is the structure that holds the bit-stream when encoding or decoding + * with Speex. It allows some manipulations as well. + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Bit-packing data structure representing (part of) a bit-stream. */ +typedef struct SpeexBits { + char *chars; /**< "raw" data */ + int nbBits; /**< Total number of bits stored in the stream*/ + int charPtr; /**< Position of the byte "cursor" */ + int bitPtr; /**< Position of the bit "cursor" within the current char */ + int owner; /**< Does the struct "own" the "raw" buffer (member "chars") */ + int overflow;/**< Set to one if we try to read past the valid data */ + int buf_size;/**< Allocated size for buffer */ + int reserved1; /**< Reserved for future use */ + void *reserved2; /**< Reserved for future use */ +} SpeexBits; + +/** Initializes and allocates resources for a SpeexBits struct */ +void speex_bits_init(SpeexBits *bits); + +/** Initializes SpeexBits struct using a pre-allocated buffer*/ +void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size); + +/** Sets the bits in a SpeexBits struct to use data from an existing buffer (for decoding without copying data) */ +void speex_bits_set_bit_buffer(SpeexBits *bits, void *buff, int buf_size); + +/** Frees all resources associated to a SpeexBits struct. Right now this does nothing since no resources are allocated, but this could change in the future.*/ +void speex_bits_destroy(SpeexBits *bits); + +/** Resets bits to initial value (just after initialization, erasing content)*/ +void speex_bits_reset(SpeexBits *bits); + +/** Rewind the bit-stream to the beginning (ready for read) without erasing the content */ +void speex_bits_rewind(SpeexBits *bits); + +/** Initializes the bit-stream from the data in an area of memory */ +void speex_bits_read_from(SpeexBits *bits, char *bytes, int len); + +/** Append bytes to the bit-stream + * + * @param bits Bit-stream to operate on + * @param bytes pointer to the bytes what will be appended + * @param len Number of bytes of append + */ +void speex_bits_read_whole_bytes(SpeexBits *bits, char *bytes, int len); + +/** Write the content of a bit-stream to an area of memory + * + * @param bits Bit-stream to operate on + * @param bytes Memory location where to write the bits + * @param max_len Maximum number of bytes to write (i.e. size of the "bytes" buffer) + * @return Number of bytes written to the "bytes" buffer +*/ +int speex_bits_write(SpeexBits *bits, char *bytes, int max_len); + +/** Like speex_bits_write, but writes only the complete bytes in the stream. Also removes the written bytes from the stream */ +int speex_bits_write_whole_bytes(SpeexBits *bits, char *bytes, int max_len); + +/** Append bits to the bit-stream + * @param bits Bit-stream to operate on + * @param data Value to append as integer + * @param nbBits number of bits to consider in "data" + */ +void speex_bits_pack(SpeexBits *bits, int data, int nbBits); + +/** Interpret the next bits in the bit-stream as a signed integer + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to interpret + * @return A signed integer represented by the bits read + */ +int speex_bits_unpack_signed(SpeexBits *bits, int nbBits); + +/** Interpret the next bits in the bit-stream as an unsigned integer + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to interpret + * @return An unsigned integer represented by the bits read + */ +unsigned int speex_bits_unpack_unsigned(SpeexBits *bits, int nbBits); + +/** Returns the number of bytes in the bit-stream, including the last one even if it is not "full" + * + * @param bits Bit-stream to operate on + * @return Number of bytes in the stream + */ +int speex_bits_nbytes(SpeexBits *bits); + +/** Same as speex_bits_unpack_unsigned, but without modifying the cursor position + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to look for + * @return Value of the bits peeked, interpreted as unsigned + */ +unsigned int speex_bits_peek_unsigned(SpeexBits *bits, int nbBits); + +/** Get the value of the next bit in the stream, without modifying the + * "cursor" position + * + * @param bits Bit-stream to operate on + * @return Value of the bit peeked (one bit only) + */ +int speex_bits_peek(SpeexBits *bits); + +/** Advances the position of the "bit cursor" in the stream + * + * @param bits Bit-stream to operate on + * @param n Number of bits to advance + */ +void speex_bits_advance(SpeexBits *bits, int n); + +/** Returns the number of bits remaining to be read in a stream + * + * @param bits Bit-stream to operate on + * @return Number of bits that can still be read from the stream + */ +int speex_bits_remaining(SpeexBits *bits); + +/** Insert a terminator so that the data can be sent as a packet while auto-detecting + * the number of frames in each packet + * + * @param bits Bit-stream to operate on + */ +void speex_bits_insert_terminator(SpeexBits *bits); + +#ifdef __cplusplus +} +#endif + +/* @} */ +#endif diff --git a/Libraries/speex/include/speex/speex_buffer.h b/Libraries/speex/include/speex/speex_buffer.h new file mode 100644 index 000000000..df56f5f18 --- /dev/null +++ b/Libraries/speex/include/speex/speex_buffer.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: speex_buffer.h + This is a very simple ring buffer implementation. It is not thread-safe + so you need to do your own locking. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_BUFFER_H +#define SPEEX_BUFFER_H + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct SpeexBuffer_; +typedef struct SpeexBuffer_ SpeexBuffer; + +SpeexBuffer *speex_buffer_init(int size); + +void speex_buffer_destroy(SpeexBuffer *st); + +int speex_buffer_write(SpeexBuffer *st, void *data, int len); + +int speex_buffer_writezeros(SpeexBuffer *st, int len); + +int speex_buffer_read(SpeexBuffer *st, void *data, int len); + +int speex_buffer_get_available(SpeexBuffer *st); + +int speex_buffer_resize(SpeexBuffer *st, int len); + +#ifdef __cplusplus +} +#endif + +#endif + + + + diff --git a/Libraries/speex/include/speex/speex_callbacks.h b/Libraries/speex/include/speex/speex_callbacks.h new file mode 100644 index 000000000..6f450b3a3 --- /dev/null +++ b/Libraries/speex/include/speex/speex_callbacks.h @@ -0,0 +1,134 @@ +/* Copyright (C) 2002 Jean-Marc Valin*/ +/** + @file speex_callbacks.h + @brief Describes callback handling and in-band signalling +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_CALLBACKS_H +#define SPEEX_CALLBACKS_H +/** @defgroup SpeexCallbacks Various definitions for Speex callbacks supported by the decoder. + * @{ + */ + +#include "speex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Total number of callbacks */ +#define SPEEX_MAX_CALLBACKS 16 + +/* Describes all the in-band requests */ + +/*These are 1-bit requests*/ +/** Request for perceptual enhancement (1 for on, 0 for off) */ +#define SPEEX_INBAND_ENH_REQUEST 0 +/** Reserved */ +#define SPEEX_INBAND_RESERVED1 1 + +/*These are 4-bit requests*/ +/** Request for a mode change */ +#define SPEEX_INBAND_MODE_REQUEST 2 +/** Request for a low mode change */ +#define SPEEX_INBAND_LOW_MODE_REQUEST 3 +/** Request for a high mode change */ +#define SPEEX_INBAND_HIGH_MODE_REQUEST 4 +/** Request for VBR (1 on, 0 off) */ +#define SPEEX_INBAND_VBR_QUALITY_REQUEST 5 +/** Request to be sent acknowledge */ +#define SPEEX_INBAND_ACKNOWLEDGE_REQUEST 6 +/** Request for VBR (1 for on, 0 for off) */ +#define SPEEX_INBAND_VBR_REQUEST 7 + +/*These are 8-bit requests*/ +/** Send a character in-band */ +#define SPEEX_INBAND_CHAR 8 +/** Intensity stereo information */ +#define SPEEX_INBAND_STEREO 9 + +/*These are 16-bit requests*/ +/** Transmit max bit-rate allowed */ +#define SPEEX_INBAND_MAX_BITRATE 10 + +/*These are 32-bit requests*/ +/** Acknowledge packet reception */ +#define SPEEX_INBAND_ACKNOWLEDGE 12 + +/** Callback function type */ +typedef int (*speex_callback_func)(SpeexBits *bits, void *state, void *data); + +/** Callback information */ +typedef struct SpeexCallback { + int callback_id; /**< ID associated to the callback */ + speex_callback_func func; /**< Callback handler function */ + void *data; /**< Data that will be sent to the handler */ + void *reserved1; /**< Reserved for future use */ + int reserved2; /**< Reserved for future use */ +} SpeexCallback; + +/** Handle in-band request */ +int speex_inband_handler(SpeexBits *bits, SpeexCallback *callback_list, void *state); + +/** Standard handler for mode request (change mode, no questions asked) */ +int speex_std_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for high mode request (change high mode, no questions asked) */ +int speex_std_high_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for in-band characters (write to stderr) */ +int speex_std_char_handler(SpeexBits *bits, void *state, void *data); + +/** Default handler for user-defined requests: in this case, just ignore */ +int speex_default_user_handler(SpeexBits *bits, void *state, void *data); + + + +/** Standard handler for low mode request (change low mode, no questions asked) */ +int speex_std_low_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for VBR request (Set VBR, no questions asked) */ +int speex_std_vbr_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for enhancer request (Turn enhancer on/off, no questions asked) */ +int speex_std_enh_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for VBR quality request (Set VBR quality, no questions asked) */ +int speex_std_vbr_quality_request_handler(SpeexBits *bits, void *state, void *data); + + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/Libraries/speex/include/speex/speex_config_types.h b/Libraries/speex/include/speex/speex_config_types.h new file mode 100644 index 000000000..bd548546b --- /dev/null +++ b/Libraries/speex/include/speex/speex_config_types.h @@ -0,0 +1,11 @@ +#ifndef __SPEEX_TYPES_H__ +#define __SPEEX_TYPES_H__ + +/* these are filled in by configure */ +typedef short spx_int16_t; +typedef unsigned short spx_uint16_t; +typedef int spx_int32_t; +typedef unsigned int spx_uint32_t; + +#endif + diff --git a/Libraries/speex/include/speex/speex_config_types.h.in b/Libraries/speex/include/speex/speex_config_types.h.in new file mode 100644 index 000000000..3fab2ae44 --- /dev/null +++ b/Libraries/speex/include/speex/speex_config_types.h.in @@ -0,0 +1,11 @@ +#ifndef __SPEEX_TYPES_H__ +#define __SPEEX_TYPES_H__ + +/* these are filled in by configure */ +typedef @SIZE16@ spx_int16_t; +typedef unsigned @SIZE16@ spx_uint16_t; +typedef @SIZE32@ spx_int32_t; +typedef unsigned @SIZE32@ spx_uint32_t; + +#endif + diff --git a/Libraries/speex/include/speex/speex_echo.h b/Libraries/speex/include/speex/speex_echo.h new file mode 100644 index 000000000..53bcd28a1 --- /dev/null +++ b/Libraries/speex/include/speex/speex_echo.h @@ -0,0 +1,170 @@ +/* Copyright (C) Jean-Marc Valin */ +/** + @file speex_echo.h + @brief Echo cancellation +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_ECHO_H +#define SPEEX_ECHO_H +/** @defgroup SpeexEchoState SpeexEchoState: Acoustic echo canceller + * This is the acoustic echo canceller module. + * @{ + */ +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Obtain frame size used by the AEC */ +#define SPEEX_ECHO_GET_FRAME_SIZE 3 + +/** Set sampling rate */ +#define SPEEX_ECHO_SET_SAMPLING_RATE 24 +/** Get sampling rate */ +#define SPEEX_ECHO_GET_SAMPLING_RATE 25 + +/* Can't set window sizes */ +/** Get size of impulse response (int32) */ +#define SPEEX_ECHO_GET_IMPULSE_RESPONSE_SIZE 27 + +/* Can't set window content */ +/** Get impulse response (int32[]) */ +#define SPEEX_ECHO_GET_IMPULSE_RESPONSE 29 + +/** Internal echo canceller state. Should never be accessed directly. */ +struct SpeexEchoState_; + +/** @class SpeexEchoState + * This holds the state of the echo canceller. You need one per channel. +*/ + +/** Internal echo canceller state. Should never be accessed directly. */ +typedef struct SpeexEchoState_ SpeexEchoState; + +/** Creates a new echo canceller state + * @param frame_size Number of samples to process at one time (should correspond to 10-20 ms) + * @param filter_length Number of samples of echo to cancel (should generally correspond to 100-500 ms) + * @return Newly-created echo canceller state + */ +SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length); + +/** Creates a new multi-channel echo canceller state + * @param frame_size Number of samples to process at one time (should correspond to 10-20 ms) + * @param filter_length Number of samples of echo to cancel (should generally correspond to 100-500 ms) + * @param nb_mic Number of microphone channels + * @param nb_speakers Number of speaker channels + * @return Newly-created echo canceller state + */ +SpeexEchoState *speex_echo_state_init_mc(int frame_size, int filter_length, int nb_mic, int nb_speakers); + +/** Destroys an echo canceller state + * @param st Echo canceller state +*/ +void speex_echo_state_destroy(SpeexEchoState *st); + +/** Performs echo cancellation a frame, based on the audio sent to the speaker (no delay is added + * to playback in this form) + * + * @param st Echo canceller state + * @param rec Signal from the microphone (near end + far end echo) + * @param play Signal played to the speaker (received from far end) + * @param out Returns near-end signal with echo removed + */ +void speex_echo_cancellation(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out); + +/** Performs echo cancellation a frame (deprecated) */ +void speex_echo_cancel(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out, spx_int32_t *Yout); + +/** Perform echo cancellation using internal playback buffer, which is delayed by two frames + * to account for the delay introduced by most soundcards (but it could be off!) + * @param st Echo canceller state + * @param rec Signal from the microphone (near end + far end echo) + * @param out Returns near-end signal with echo removed +*/ +void speex_echo_capture(SpeexEchoState *st, const spx_int16_t *rec, spx_int16_t *out); + +/** Let the echo canceller know that a frame was just queued to the soundcard + * @param st Echo canceller state + * @param play Signal played to the speaker (received from far end) +*/ +void speex_echo_playback(SpeexEchoState *st, const spx_int16_t *play); + +/** Reset the echo canceller to its original state + * @param st Echo canceller state + */ +void speex_echo_state_reset(SpeexEchoState *st); + +/** Used like the ioctl function to control the echo canceller parameters + * + * @param st Echo canceller state + * @param request ioctl-type request (one of the SPEEX_ECHO_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown + */ +int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr); + + + +struct SpeexDecorrState_; + +typedef struct SpeexDecorrState_ SpeexDecorrState; + + +/** Create a state for the channel decorrelation algorithm + This is useful for multi-channel echo cancellation only + * @param rate Sampling rate + * @param channels Number of channels (it's a bit pointless if you don't have at least 2) + * @param frame_size Size of the frame to process at ones (counting samples *per* channel) +*/ +SpeexDecorrState *speex_decorrelate_new(int rate, int channels, int frame_size); + +/** Remove correlation between the channels by modifying the phase and possibly + adding noise in a way that is not (or little) perceptible. + * @param st Decorrelator state + * @param in Input audio in interleaved format + * @param out Result of the decorrelation (out *may* alias in) + * @param strength How much alteration of the audio to apply from 0 to 100. +*/ +void speex_decorrelate(SpeexDecorrState *st, const spx_int16_t *in, spx_int16_t *out, int strength); + +/** Destroy a Decorrelation state + * @param st State to destroy +*/ +void speex_decorrelate_destroy(SpeexDecorrState *st); + + +#ifdef __cplusplus +} +#endif + + +/** @}*/ +#endif diff --git a/Libraries/speex/include/speex/speex_header.h b/Libraries/speex/include/speex/speex_header.h new file mode 100644 index 000000000..f85b2496a --- /dev/null +++ b/Libraries/speex/include/speex/speex_header.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_header.h + @brief Describes the Speex header +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef SPEEX_HEADER_H +#define SPEEX_HEADER_H +/** @defgroup SpeexHeader SpeexHeader: Makes it easy to write/parse an Ogg/Speex header + * This is the Speex header for the Ogg encapsulation. You don't need that if you just use RTP. + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct SpeexMode; + +/** Length of the Speex header identifier */ +#define SPEEX_HEADER_STRING_LENGTH 8 + +/** Maximum number of characters for encoding the Speex version number in the header */ +#define SPEEX_HEADER_VERSION_LENGTH 20 + +/** Speex header info for file-based formats */ +typedef struct SpeexHeader { + char speex_string[SPEEX_HEADER_STRING_LENGTH]; /**< Identifies a Speex bit-stream, always set to "Speex " */ + char speex_version[SPEEX_HEADER_VERSION_LENGTH]; /**< Speex version */ + spx_int32_t speex_version_id; /**< Version for Speex (for checking compatibility) */ + spx_int32_t header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + spx_int32_t rate; /**< Sampling rate used */ + spx_int32_t mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + spx_int32_t mode_bitstream_version; /**< Version ID of the bit-stream */ + spx_int32_t nb_channels; /**< Number of channels encoded */ + spx_int32_t bitrate; /**< Bit-rate used */ + spx_int32_t frame_size; /**< Size of frames */ + spx_int32_t vbr; /**< 1 for a VBR encoding, 0 otherwise */ + spx_int32_t frames_per_packet; /**< Number of frames stored per Ogg packet */ + spx_int32_t extra_headers; /**< Number of additional headers after the comments */ + spx_int32_t reserved1; /**< Reserved for future use, must be zero */ + spx_int32_t reserved2; /**< Reserved for future use, must be zero */ +} SpeexHeader; + +/** Initializes a SpeexHeader using basic information */ +void speex_init_header(SpeexHeader *header, int rate, int nb_channels, const struct SpeexMode *m); + +/** Creates the header packet from the header itself (mostly involves endianness conversion) */ +char *speex_header_to_packet(SpeexHeader *header, int *size); + +/** Creates a SpeexHeader from a packet */ +SpeexHeader *speex_packet_to_header(char *packet, int size); + +/** Frees the memory allocated by either speex_header_to_packet() or speex_packet_to_header() */ +void speex_header_free(void *ptr); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/Libraries/speex/include/speex/speex_jitter.h b/Libraries/speex/include/speex/speex_jitter.h new file mode 100644 index 000000000..d68674b13 --- /dev/null +++ b/Libraries/speex/include/speex/speex_jitter.h @@ -0,0 +1,197 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_jitter.h + @brief Adaptive jitter buffer for Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_JITTER_H +#define SPEEX_JITTER_H +/** @defgroup JitterBuffer JitterBuffer: Adaptive jitter buffer + * This is the jitter buffer that reorders UDP/RTP packets and adjusts the buffer size + * to maintain good quality and low latency. + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Generic adaptive jitter buffer state */ +struct JitterBuffer_; + +/** Generic adaptive jitter buffer state */ +typedef struct JitterBuffer_ JitterBuffer; + +/** Definition of an incoming packet */ +typedef struct _JitterBufferPacket JitterBufferPacket; + +/** Definition of an incoming packet */ +struct _JitterBufferPacket { + char *data; /**< Data bytes contained in the packet */ + spx_uint32_t len; /**< Length of the packet in bytes */ + spx_uint32_t timestamp; /**< Timestamp for the packet */ + spx_uint32_t span; /**< Time covered by the packet (same units as timestamp) */ + spx_uint16_t sequence; /**< RTP Sequence number if available (0 otherwise) */ + spx_uint32_t user_data; /**< Put whatever data you like here (it's ignored by the jitter buffer) */ +}; + +/** Packet has been retrieved */ +#define JITTER_BUFFER_OK 0 +/** Packet is lost or is late */ +#define JITTER_BUFFER_MISSING 1 +/** A "fake" packet is meant to be inserted here to increase buffering */ +#define JITTER_BUFFER_INSERTION 2 +/** There was an error in the jitter buffer */ +#define JITTER_BUFFER_INTERNAL_ERROR -1 +/** Invalid argument */ +#define JITTER_BUFFER_BAD_ARGUMENT -2 + + +/** Set minimum amount of extra buffering required (margin) */ +#define JITTER_BUFFER_SET_MARGIN 0 +/** Get minimum amount of extra buffering required (margin) */ +#define JITTER_BUFFER_GET_MARGIN 1 +/* JITTER_BUFFER_SET_AVAILABLE_COUNT wouldn't make sense */ + +/** Get the amount of available packets currently buffered */ +#define JITTER_BUFFER_GET_AVAILABLE_COUNT 3 +/** Included because of an early misspelling (will remove in next release) */ +#define JITTER_BUFFER_GET_AVALIABLE_COUNT 3 + +/** Assign a function to destroy unused packet. When setting that, the jitter + buffer no longer copies packet data. */ +#define JITTER_BUFFER_SET_DESTROY_CALLBACK 4 +/** */ +#define JITTER_BUFFER_GET_DESTROY_CALLBACK 5 + +/** Tell the jitter buffer to only adjust the delay in multiples of the step parameter provided */ +#define JITTER_BUFFER_SET_DELAY_STEP 6 +/** */ +#define JITTER_BUFFER_GET_DELAY_STEP 7 + +/** Tell the jitter buffer to only do concealment in multiples of the size parameter provided */ +#define JITTER_BUFFER_SET_CONCEALMENT_SIZE 8 +#define JITTER_BUFFER_GET_CONCEALMENT_SIZE 9 + +/** Absolute max amount of loss that can be tolerated regardless of the delay. Typical loss + should be half of that or less. */ +#define JITTER_BUFFER_SET_MAX_LATE_RATE 10 +#define JITTER_BUFFER_GET_MAX_LATE_RATE 11 + +/** Equivalent cost of one percent late packet in timestamp units */ +#define JITTER_BUFFER_SET_LATE_COST 12 +#define JITTER_BUFFER_GET_LATE_COST 13 + + +/** Initialises jitter buffer + * + * @param step_size Starting value for the size of concleanment packets and delay + adjustment steps. Can be changed at any time using JITTER_BUFFER_SET_DELAY_STEP + and JITTER_BUFFER_GET_CONCEALMENT_SIZE. + * @return Newly created jitter buffer state + */ +JitterBuffer *jitter_buffer_init(int step_size); + +/** Restores jitter buffer to its original state + * + * @param jitter Jitter buffer state + */ +void jitter_buffer_reset(JitterBuffer *jitter); + +/** Destroys jitter buffer + * + * @param jitter Jitter buffer state + */ +void jitter_buffer_destroy(JitterBuffer *jitter); + +/** Put one packet into the jitter buffer + * + * @param jitter Jitter buffer state + * @param packet Incoming packet +*/ +void jitter_buffer_put(JitterBuffer *jitter, const JitterBufferPacket *packet); + +/** Get one packet from the jitter buffer + * + * @param jitter Jitter buffer state + * @param packet Returned packet + * @param desired_span Number of samples (or units) we wish to get from the buffer (no guarantee) + * @param current_timestamp Timestamp for the returned packet +*/ +int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset); + +/** Used right after jitter_buffer_get() to obtain another packet that would have the same timestamp. + * This is mainly useful for media where a single "frame" can be split into several packets. + * + * @param jitter Jitter buffer state + * @param packet Returned packet + */ +int jitter_buffer_get_another(JitterBuffer *jitter, JitterBufferPacket *packet); + +/** Get pointer timestamp of jitter buffer + * + * @param jitter Jitter buffer state +*/ +int jitter_buffer_get_pointer_timestamp(JitterBuffer *jitter); + +/** Advance by one tick + * + * @param jitter Jitter buffer state +*/ +void jitter_buffer_tick(JitterBuffer *jitter); + +/** Telling the jitter buffer about the remaining data in the application buffer + * @param jitter Jitter buffer state + * @param rem Amount of data buffered by the application (timestamp units) + */ +void jitter_buffer_remaining_span(JitterBuffer *jitter, spx_uint32_t rem); + +/** Used like the ioctl function to control the jitter buffer parameters + * + * @param jitter Jitter buffer state + * @param request ioctl-type request (one of the JITTER_BUFFER_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown +*/ +int jitter_buffer_ctl(JitterBuffer *jitter, int request, void *ptr); + +int jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Libraries/speex/include/speex/speex_preprocess.h b/Libraries/speex/include/speex/speex_preprocess.h new file mode 100644 index 000000000..f8eef2cd9 --- /dev/null +++ b/Libraries/speex/include/speex/speex_preprocess.h @@ -0,0 +1,219 @@ +/* Copyright (C) 2003 Epic Games + Written by Jean-Marc Valin */ +/** + * @file speex_preprocess.h + * @brief Speex preprocessor. The preprocess can do noise suppression, + * residual echo suppression (after using the echo canceller), automatic + * gain control (AGC) and voice activity detection (VAD). +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_PREPROCESS_H +#define SPEEX_PREPROCESS_H +/** @defgroup SpeexPreprocessState SpeexPreprocessState: The Speex preprocessor + * This is the Speex preprocessor. The preprocess can do noise suppression, + * residual echo suppression (after using the echo canceller), automatic + * gain control (AGC) and voice activity detection (VAD). + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** State of the preprocessor (one per channel). Should never be accessed directly. */ +struct SpeexPreprocessState_; + +/** State of the preprocessor (one per channel). Should never be accessed directly. */ +typedef struct SpeexPreprocessState_ SpeexPreprocessState; + + +/** Creates a new preprocessing state. You MUST create one state per channel processed. + * @param frame_size Number of samples to process at one time (should correspond to 10-20 ms). Must be + * the same value as that used for the echo canceller for residual echo cancellation to work. + * @param sampling_rate Sampling rate used for the input. + * @return Newly created preprocessor state +*/ +SpeexPreprocessState *speex_preprocess_state_init(int frame_size, int sampling_rate); + +/** Destroys a preprocessor state + * @param st Preprocessor state to destroy +*/ +void speex_preprocess_state_destroy(SpeexPreprocessState *st); + +/** Preprocess a frame + * @param st Preprocessor state + * @param x Audio sample vector (in and out). Must be same size as specified in speex_preprocess_state_init(). + * @return Bool value for voice activity (1 for speech, 0 for noise/silence), ONLY if VAD turned on. +*/ +int speex_preprocess_run(SpeexPreprocessState *st, spx_int16_t *x); + +/** Preprocess a frame (deprecated, use speex_preprocess_run() instead)*/ +int speex_preprocess(SpeexPreprocessState *st, spx_int16_t *x, spx_int32_t *echo); + +/** Update preprocessor state, but do not compute the output + * @param st Preprocessor state + * @param x Audio sample vector (in only). Must be same size as specified in speex_preprocess_state_init(). +*/ +void speex_preprocess_estimate_update(SpeexPreprocessState *st, spx_int16_t *x); + +/** Used like the ioctl function to control the preprocessor parameters + * @param st Preprocessor state + * @param request ioctl-type request (one of the SPEEX_PREPROCESS_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown +*/ +int speex_preprocess_ctl(SpeexPreprocessState *st, int request, void *ptr); + + + +/** Set preprocessor denoiser state */ +#define SPEEX_PREPROCESS_SET_DENOISE 0 +/** Get preprocessor denoiser state */ +#define SPEEX_PREPROCESS_GET_DENOISE 1 + +/** Set preprocessor Automatic Gain Control state */ +#define SPEEX_PREPROCESS_SET_AGC 2 +/** Get preprocessor Automatic Gain Control state */ +#define SPEEX_PREPROCESS_GET_AGC 3 + +/** Set preprocessor Voice Activity Detection state */ +#define SPEEX_PREPROCESS_SET_VAD 4 +/** Get preprocessor Voice Activity Detection state */ +#define SPEEX_PREPROCESS_GET_VAD 5 + +/** Set preprocessor Automatic Gain Control level (float) */ +#define SPEEX_PREPROCESS_SET_AGC_LEVEL 6 +/** Get preprocessor Automatic Gain Control level (float) */ +#define SPEEX_PREPROCESS_GET_AGC_LEVEL 7 + +/** Set preprocessor dereverb state */ +#define SPEEX_PREPROCESS_SET_DEREVERB 8 +/** Get preprocessor dereverb state */ +#define SPEEX_PREPROCESS_GET_DEREVERB 9 + +/** Set preprocessor dereverb level */ +#define SPEEX_PREPROCESS_SET_DEREVERB_LEVEL 10 +/** Get preprocessor dereverb level */ +#define SPEEX_PREPROCESS_GET_DEREVERB_LEVEL 11 + +/** Set preprocessor dereverb decay */ +#define SPEEX_PREPROCESS_SET_DEREVERB_DECAY 12 +/** Get preprocessor dereverb decay */ +#define SPEEX_PREPROCESS_GET_DEREVERB_DECAY 13 + +/** Set probability required for the VAD to go from silence to voice */ +#define SPEEX_PREPROCESS_SET_PROB_START 14 +/** Get probability required for the VAD to go from silence to voice */ +#define SPEEX_PREPROCESS_GET_PROB_START 15 + +/** Set probability required for the VAD to stay in the voice state (integer percent) */ +#define SPEEX_PREPROCESS_SET_PROB_CONTINUE 16 +/** Get probability required for the VAD to stay in the voice state (integer percent) */ +#define SPEEX_PREPROCESS_GET_PROB_CONTINUE 17 + +/** Set maximum attenuation of the noise in dB (negative number) */ +#define SPEEX_PREPROCESS_SET_NOISE_SUPPRESS 18 +/** Get maximum attenuation of the noise in dB (negative number) */ +#define SPEEX_PREPROCESS_GET_NOISE_SUPPRESS 19 + +/** Set maximum attenuation of the residual echo in dB (negative number) */ +#define SPEEX_PREPROCESS_SET_ECHO_SUPPRESS 20 +/** Get maximum attenuation of the residual echo in dB (negative number) */ +#define SPEEX_PREPROCESS_GET_ECHO_SUPPRESS 21 + +/** Set maximum attenuation of the residual echo in dB when near end is active (negative number) */ +#define SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE 22 +/** Get maximum attenuation of the residual echo in dB when near end is active (negative number) */ +#define SPEEX_PREPROCESS_GET_ECHO_SUPPRESS_ACTIVE 23 + +/** Set the corresponding echo canceller state so that residual echo suppression can be performed (NULL for no residual echo suppression) */ +#define SPEEX_PREPROCESS_SET_ECHO_STATE 24 +/** Get the corresponding echo canceller state */ +#define SPEEX_PREPROCESS_GET_ECHO_STATE 25 + +/** Set maximal gain increase in dB/second (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_INCREMENT 26 + +/** Get maximal gain increase in dB/second (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_INCREMENT 27 + +/** Set maximal gain decrease in dB/second (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_DECREMENT 28 + +/** Get maximal gain decrease in dB/second (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_DECREMENT 29 + +/** Set maximal gain in dB (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_MAX_GAIN 30 + +/** Get maximal gain in dB (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_MAX_GAIN 31 + +/* Can't set loudness */ +/** Get loudness */ +#define SPEEX_PREPROCESS_GET_AGC_LOUDNESS 33 + +/* Can't set gain */ +/** Get current gain (int32 percent) */ +#define SPEEX_PREPROCESS_GET_AGC_GAIN 35 + +/* Can't set spectrum size */ +/** Get spectrum size for power spectrum (int32) */ +#define SPEEX_PREPROCESS_GET_PSD_SIZE 37 + +/* Can't set power spectrum */ +/** Get power spectrum (int32[] of squared values) */ +#define SPEEX_PREPROCESS_GET_PSD 39 + +/* Can't set noise size */ +/** Get spectrum size for noise estimate (int32) */ +#define SPEEX_PREPROCESS_GET_NOISE_PSD_SIZE 41 + +/* Can't set noise estimate */ +/** Get noise estimate (int32[] of squared values) */ +#define SPEEX_PREPROCESS_GET_NOISE_PSD 43 + +/* Can't set speech probability */ +/** Get speech probability in last frame (int32). */ +#define SPEEX_PREPROCESS_GET_PROB 45 + +/** Set preprocessor Automatic Gain Control level (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_TARGET 46 +/** Get preprocessor Automatic Gain Control level (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_TARGET 47 + +#ifdef __cplusplus +} +#endif + +/** @}*/ +#endif diff --git a/Libraries/speex/include/speex/speex_resampler.h b/Libraries/speex/include/speex/speex_resampler.h new file mode 100644 index 000000000..54eef8d7b --- /dev/null +++ b/Libraries/speex/include/speex/speex_resampler.h @@ -0,0 +1,340 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: speex_resampler.h + Resampling code + + The design goals of this code are: + - Very fast algorithm + - Low memory requirement + - Good *perceptual* quality (and not best SNR) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef SPEEX_RESAMPLER_H +#define SPEEX_RESAMPLER_H + +#ifdef OUTSIDE_SPEEX + +/********* WARNING: MENTAL SANITY ENDS HERE *************/ + +/* If the resampler is defined outside of Speex, we change the symbol names so that + there won't be any clash if linking with Speex later on. */ + +/* #define RANDOM_PREFIX your software name here */ +#ifndef RANDOM_PREFIX +#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes" +#endif + +#define CAT_PREFIX2(a,b) a ## b +#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b) + +#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init) +#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac) +#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy) +#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float) +#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int) +#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float) +#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int) +#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate) +#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate) +#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac) +#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio) +#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality) +#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality) +#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride) +#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride) +#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride) +#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride) +#define speex_resampler_get_input_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_latency) +#define speex_resampler_get_output_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_latency) +#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros) +#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem) +#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror) + +#define spx_int16_t short +#define spx_int32_t int +#define spx_uint16_t unsigned short +#define spx_uint32_t unsigned int + +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_types.h" + +#endif /* OUTSIDE_SPEEX */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPEEX_RESAMPLER_QUALITY_MAX 10 +#define SPEEX_RESAMPLER_QUALITY_MIN 0 +#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4 +#define SPEEX_RESAMPLER_QUALITY_VOIP 3 +#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5 + +enum { + RESAMPLER_ERR_SUCCESS = 0, + RESAMPLER_ERR_ALLOC_FAILED = 1, + RESAMPLER_ERR_BAD_STATE = 2, + RESAMPLER_ERR_INVALID_ARG = 3, + RESAMPLER_ERR_PTR_OVERLAP = 4, + + RESAMPLER_ERR_MAX_ERROR +}; + +struct SpeexResamplerState_; +typedef struct SpeexResamplerState_ SpeexResamplerState; + +/** Create a new resampler with integer input and output rates. + * @param nb_channels Number of channels to be processed + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Create a new resampler with fractional input/output rates. The sampling + * rate ratio is an arbitrary rational number with both the numerator and + * denominator being 32-bit integers. + * @param nb_channels Number of channels to be processed + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Destroy a resampler state. + * @param st Resampler state + */ +void speex_resampler_destroy(SpeexResamplerState *st); + +/** Resample a float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the + * number of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_float(SpeexResamplerState *st, + spx_uint32_t channel_index, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_int(SpeexResamplerState *st, + spx_uint32_t channel_index, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Resample an interleaved float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_float(SpeexResamplerState *st, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an interleaved int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_int(SpeexResamplerState *st, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Set (change) the input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + */ +int speex_resampler_set_rate(SpeexResamplerState *st, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz) copied. + * @param out_rate Output sampling rate (integer number of Hz) copied. + */ +void speex_resampler_get_rate(SpeexResamplerState *st, + spx_uint32_t *in_rate, + spx_uint32_t *out_rate); + +/** Set (change) the input/output sampling rates and resampling ratio + * (fractional values in Hz supported). + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + */ +int speex_resampler_set_rate_frac(SpeexResamplerState *st, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current resampling ratio. This will be reduced to the least + * common denominator. + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio copied + * @param ratio_den Denominator of the sampling rate ratio copied + */ +void speex_resampler_get_ratio(SpeexResamplerState *st, + spx_uint32_t *ratio_num, + spx_uint32_t *ratio_den); + +/** Set (change) the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +int speex_resampler_set_quality(SpeexResamplerState *st, + int quality); + +/** Get the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +void speex_resampler_get_quality(SpeexResamplerState *st, + int *quality); + +/** Set (change) the input stride. + * @param st Resampler state + * @param stride Input stride + */ +void speex_resampler_set_input_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the input stride. + * @param st Resampler state + * @param stride Input stride copied + */ +void speex_resampler_get_input_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Set (change) the output stride. + * @param st Resampler state + * @param stride Output stride + */ +void speex_resampler_set_output_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the output stride. + * @param st Resampler state copied + * @param stride Output stride + */ +void speex_resampler_get_output_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Get the latency in input samples introduced by the resampler. + * @param st Resampler state + */ +int speex_resampler_get_input_latency(SpeexResamplerState *st); + +/** Get the latency in output samples introduced by the resampler. + * @param st Resampler state + */ +int speex_resampler_get_output_latency(SpeexResamplerState *st); + +/** Make sure that the first samples to go out of the resamplers don't have + * leading zeros. This is only useful before starting to use a newly created + * resampler. It is recommended to use that when resampling an audio file, as + * it will generate a file with the same length. For real-time processing, + * it is probably easier not to use this call (so that the output duration + * is the same for the first frame). + * @param st Resampler state + */ +int speex_resampler_skip_zeros(SpeexResamplerState *st); + +/** Reset a resampler so a new (unrelated) stream can be processed. + * @param st Resampler state + */ +int speex_resampler_reset_mem(SpeexResamplerState *st); + +/** Returns the English meaning for an error code + * @param err Error code + * @return English string + */ +const char *speex_resampler_strerror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Libraries/speex/include/speex/speex_stereo.h b/Libraries/speex/include/speex/speex_stereo.h new file mode 100644 index 000000000..a259713b8 --- /dev/null +++ b/Libraries/speex/include/speex/speex_stereo.h @@ -0,0 +1,91 @@ +/* Copyright (C) 2002 Jean-Marc Valin*/ +/** + @file speex_stereo.h + @brief Describes the handling for intensity stereo +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef STEREO_H +#define STEREO_H +/** @defgroup SpeexStereoState SpeexStereoState: Handling Speex stereo files + * This describes the Speex intensity stereo encoding/decoding + * @{ + */ + +#include "speex/speex_types.h" +#include "speex/speex_bits.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** If you access any of these fields directly, I'll personally come and bite you */ +typedef struct SpeexStereoState { + float balance; /**< Left/right balance info */ + float e_ratio; /**< Ratio of energies: E(left+right)/[E(left)+E(right)] */ + float smooth_left; /**< Smoothed left channel gain */ + float smooth_right; /**< Smoothed right channel gain */ + float reserved1; /**< Reserved for future use */ + float reserved2; /**< Reserved for future use */ +} SpeexStereoState; + +/** Deprecated. Use speex_stereo_state_init() instead. */ +#define SPEEX_STEREO_STATE_INIT {1,.5,1,1,0,0} + +/** Initialise/create a stereo stereo state */ +SpeexStereoState *speex_stereo_state_init(); + +/** Reset/re-initialise an already allocated stereo state */ +void speex_stereo_state_reset(SpeexStereoState *stereo); + +/** Destroy a stereo stereo state */ +void speex_stereo_state_destroy(SpeexStereoState *stereo); + +/** Transforms a stereo frame into a mono frame and stores intensity stereo info in 'bits' */ +void speex_encode_stereo(float *data, int frame_size, SpeexBits *bits); + +/** Transforms a stereo frame into a mono frame and stores intensity stereo info in 'bits' */ +void speex_encode_stereo_int(spx_int16_t *data, int frame_size, SpeexBits *bits); + +/** Transforms a mono frame into a stereo frame using intensity stereo info */ +void speex_decode_stereo(float *data, int frame_size, SpeexStereoState *stereo); + +/** Transforms a mono frame into a stereo frame using intensity stereo info */ +void speex_decode_stereo_int(spx_int16_t *data, int frame_size, SpeexStereoState *stereo); + +/** Callback handler for intensity stereo info */ +int speex_std_stereo_request_handler(SpeexBits *bits, void *state, void *data); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/Libraries/speex/include/speex/speex_types.h b/Libraries/speex/include/speex/speex_types.h new file mode 100644 index 000000000..852fed801 --- /dev/null +++ b/Libraries/speex/include/speex/speex_types.h @@ -0,0 +1,126 @@ +/* speex_types.h taken from libogg */ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: #ifdef jail to whip a few platforms into the UNIX ideal. + last mod: $Id: os_types.h 7524 2004-08-11 04:20:36Z conrad $ + + ********************************************************************/ +/** + @file speex_types.h + @brief Speex types +*/ +#ifndef _SPEEX_TYPES_H +#define _SPEEX_TYPES_H + +#if defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t spx_int32_t; + typedef _G_uint32_t spx_uint32_t; + typedef _G_int16_t spx_int16_t; + typedef _G_uint16_t spx_uint16_t; +# elif defined(__MINGW32__) + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; +# elif defined(__MWERKS__) + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; +# else + /* MSVC/Borland */ + typedef __int32 spx_int32_t; + typedef unsigned __int32 spx_uint32_t; + typedef __int16 spx_int16_t; + typedef unsigned __int16 spx_uint16_t; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 spx_int16_t; + typedef UInt16 spx_uint16_t; + typedef SInt32 spx_int32_t; + typedef UInt32 spx_uint32_t; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t spx_int16_t; + typedef u_int16_t spx_uint16_t; + typedef int32_t spx_int32_t; + typedef u_int32_t spx_uint32_t; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16_t spx_int16_t; + typedef u_int16_t spx_uint16_t; + typedef int32_t spx_int32_t; + typedef u_int32_t spx_uint32_t; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short spx_int16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int spx_int32_t; + typedef unsigned spx_uint32_t; + typedef short spx_int16_t; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef signed int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef long spx_int32_t; + typedef unsigned long spx_uint32_t; + +#elif defined(CONFIG_TI_C6X) + + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#else + +# include + +#endif + +#endif /* _SPEEX_TYPES_H */ diff --git a/Libraries/speex/jitter.c b/Libraries/speex/jitter.c new file mode 100644 index 000000000..17bd044fc --- /dev/null +++ b/Libraries/speex/jitter.c @@ -0,0 +1,843 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_jitter.h + + Adaptive jitter buffer for Speex + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* +TODO: +- Add short-term estimate +- Defensive programming + + warn when last returned < last desired (begative buffering) + + warn if update_delay not called between get() and tick() or is called twice in a row +- Linked list structure for holding the packets instead of the current fixed-size array + + return memory to a pool + + allow pre-allocation of the pool + + optional max number of elements +- Statistics + + drift + + loss + + late + + jitter + + buffering delay +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "arch.h" +#include +#include +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +#define SPEEX_JITTER_MAX_BUFFER_SIZE 200 /**< Maximum number of packets in jitter buffer */ + +#define TSUB(a,b) ((spx_int32_t)((a)-(b))) + +#define GT32(a,b) (((spx_int32_t)((a)-(b)))>0) +#define GE32(a,b) (((spx_int32_t)((a)-(b)))>=0) +#define LT32(a,b) (((spx_int32_t)((a)-(b)))<0) +#define LE32(a,b) (((spx_int32_t)((a)-(b)))<=0) + +#define ROUND_DOWN(x, step) ((x)<0 ? ((x)-(step)+1)/(step)*(step) : (x)/(step)*(step)) + +#define MAX_TIMINGS 40 +#define MAX_BUFFERS 3 +#define TOP_DELAY 40 + +/** Buffer that keeps the time of arrival of the latest packets */ +struct TimingBuffer { + int filled; /**< Number of entries occupied in "timing" and "counts"*/ + int curr_count; /**< Number of packet timings we got (including those we discarded) */ + spx_int32_t timing[MAX_TIMINGS]; /**< Sorted list of all timings ("latest" packets first) */ + spx_int16_t counts[MAX_TIMINGS]; /**< Order the packets were put in (will be used for short-term estimate) */ +}; + +static void tb_init(struct TimingBuffer *tb) +{ + tb->filled = 0; + tb->curr_count = 0; +} + +/* Add the timing of a new packet to the TimingBuffer */ +static void tb_add(struct TimingBuffer *tb, spx_int16_t timing) +{ + int pos; + /* Discard packet that won't make it into the list because they're too early */ + if (tb->filled >= MAX_TIMINGS && timing >= tb->timing[tb->filled-1]) + { + tb->curr_count++; + return; + } + + /* Find where the timing info goes in the sorted list */ + pos = 0; + /* FIXME: Do bisection instead of linear search */ + while (posfilled && timing >= tb->timing[pos]) + { + pos++; + } + + speex_assert(pos <= tb->filled && pos < MAX_TIMINGS); + + /* Shift everything so we can perform the insertion */ + if (pos < tb->filled) + { + int move_size = tb->filled-pos; + if (tb->filled == MAX_TIMINGS) + move_size -= 1; + SPEEX_MOVE(&tb->timing[pos+1], &tb->timing[pos], move_size); + SPEEX_MOVE(&tb->counts[pos+1], &tb->counts[pos], move_size); + } + /* Insert */ + tb->timing[pos] = timing; + tb->counts[pos] = tb->curr_count; + + tb->curr_count++; + if (tb->filledfilled++; +} + + + +/** Jitter buffer structure */ +struct JitterBuffer_ { + spx_uint32_t pointer_timestamp; /**< Timestamp of what we will *get* next */ + spx_uint32_t last_returned_timestamp; /**< Useful for getting the next packet with the same timestamp (for fragmented media) */ + spx_uint32_t next_stop; /**< Estimated time the next get() will be called */ + + spx_int32_t buffered; /**< Amount of data we think is still buffered by the application (timestamp units)*/ + + JitterBufferPacket packets[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packets stored in the buffer */ + spx_uint32_t arrival[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packet arrival time (0 means it was late, even though it's a valid timestamp) */ + + void (*destroy) (void *); /**< Callback for destroying a packet */ + + spx_int32_t delay_step; /**< Size of the steps when adjusting buffering (timestamp units) */ + spx_int32_t concealment_size; /**< Size of the packet loss concealment "units" */ + int reset_state; /**< True if state was just reset */ + int buffer_margin; /**< How many frames we want to keep in the buffer (lower bound) */ + int late_cutoff; /**< How late must a packet be for it not to be considered at all */ + int interp_requested; /**< An interpolation is requested by speex_jitter_update_delay() */ + int auto_adjust; /**< Whether to automatically adjust the delay at any time */ + + struct TimingBuffer _tb[MAX_BUFFERS]; /**< Don't use those directly */ + struct TimingBuffer *timeBuffers[MAX_BUFFERS]; /**< Storing arrival time of latest frames so we can compute some stats */ + int window_size; /**< Total window over which the late frames are counted */ + int subwindow_size; /**< Sub-window size for faster computation */ + int max_late_rate; /**< Absolute maximum amount of late packets tolerable (in percent) */ + int latency_tradeoff; /**< Latency equivalent of losing one percent of packets */ + int auto_tradeoff; /**< Latency equivalent of losing one percent of packets (automatic default) */ + + int lost_count; /**< Number of consecutive lost packets */ +}; + +/** Based on available data, this computes the optimal delay for the jitter buffer. + The optimised function is in timestamp units and is: + cost = delay + late_factor*[number of frames that would be late if we used that delay] + @param tb Array of buffers + @param late_factor Equivalent cost of a late frame (in timestamp units) + */ +static spx_int16_t compute_opt_delay(JitterBuffer *jitter) +{ + int i; + spx_int16_t opt=0; + spx_int32_t best_cost=0x7fffffff; + int late = 0; + int pos[MAX_BUFFERS]; + int tot_count; + float late_factor; + int penalty_taken = 0; + int best = 0; + int worst = 0; + spx_int32_t deltaT; + struct TimingBuffer *tb; + + tb = jitter->_tb; + + /* Number of packet timings we have received (including those we didn't keep) */ + tot_count = 0; + for (i=0;ilatency_tradeoff != 0) + late_factor = jitter->latency_tradeoff * 100.0f / tot_count; + else + late_factor = jitter->auto_tradeoff * jitter->window_size/tot_count; + + /*fprintf(stderr, "late_factor = %f\n", late_factor);*/ + for (i=0;idelay_step); + pos[next]++; + + /* Actual cost function that tells us how bad using this delay would be */ + cost = -latest + late_factor*late; + /*fprintf(stderr, "cost %d = %d + %f * %d\n", cost, -latest, late_factor, late);*/ + if (cost < best_cost) + { + best_cost = cost; + opt = latest; + } + } else { + break; + } + + /* For the next timing we will consider, there will be one more late packet to count */ + late++; + /* Two-frame penalty if we're going to increase the amount of late frames (hysteresis) */ + if (latest >= 0 && !penalty_taken) + { + penalty_taken = 1; + late+=4; + } + } + + deltaT = best-worst; + /* This is a default "automatic latency tradeoff" when none is provided */ + jitter->auto_tradeoff = 1 + deltaT/TOP_DELAY; + /*fprintf(stderr, "auto_tradeoff = %d (%d %d %d)\n", jitter->auto_tradeoff, best, worst, i);*/ + + /* FIXME: Compute a short-term estimate too and combine with the long-term one */ + + /* Prevents reducing the buffer size when we haven't really had much data */ + if (tot_count < TOP_DELAY && opt > 0) + return 0; + return opt; +} + + +/** Initialise jitter buffer */ +EXPORT JitterBuffer *jitter_buffer_init(int step_size) +{ + JitterBuffer *jitter = (JitterBuffer*)speex_alloc(sizeof(JitterBuffer)); + if (jitter) + { + int i; + spx_int32_t tmp; + for (i=0;ipackets[i].data=NULL; + jitter->delay_step = step_size; + jitter->concealment_size = step_size; + /*FIXME: Should this be 0 or 1?*/ + jitter->buffer_margin = 0; + jitter->late_cutoff = 50; + jitter->destroy = NULL; + jitter->latency_tradeoff = 0; + jitter->auto_adjust = 1; + tmp = 4; + jitter_buffer_ctl(jitter, JITTER_BUFFER_SET_MAX_LATE_RATE, &tmp); + jitter_buffer_reset(jitter); + } + return jitter; +} + +/** Reset jitter buffer */ +EXPORT void jitter_buffer_reset(JitterBuffer *jitter) +{ + int i; + for (i=0;ipackets[i].data) + { + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data = NULL; + } + } + /* Timestamp is actually undefined at this point */ + jitter->pointer_timestamp = 0; + jitter->next_stop = 0; + jitter->reset_state = 1; + jitter->lost_count = 0; + jitter->buffered = 0; + jitter->auto_tradeoff = 32000; + + for (i=0;i_tb[i]); + jitter->timeBuffers[i] = &jitter->_tb[i]; + } + /*fprintf (stderr, "reset\n");*/ +} + +/** Destroy jitter buffer */ +EXPORT void jitter_buffer_destroy(JitterBuffer *jitter) +{ + jitter_buffer_reset(jitter); + speex_free(jitter); +} + +/** Take the following timing into consideration for future calculations */ +static void update_timings(JitterBuffer *jitter, spx_int32_t timing) +{ + if (timing < -32767) + timing = -32767; + if (timing > 32767) + timing = 32767; + /* If the current sub-window is full, perform a rotation and discard oldest sub-widow */ + if (jitter->timeBuffers[0]->curr_count >= jitter->subwindow_size) + { + int i; + /*fprintf(stderr, "Rotate buffer\n");*/ + struct TimingBuffer *tmp = jitter->timeBuffers[MAX_BUFFERS-1]; + for (i=MAX_BUFFERS-1;i>=1;i--) + jitter->timeBuffers[i] = jitter->timeBuffers[i-1]; + jitter->timeBuffers[0] = tmp; + tb_init(jitter->timeBuffers[0]); + } + tb_add(jitter->timeBuffers[0], timing); +} + +/** Compensate all timings when we do an adjustment of the buffering */ +static void shift_timings(JitterBuffer *jitter, spx_int16_t amount) +{ + int i, j; + for (i=0;itimeBuffers[i]->filled;j++) + jitter->timeBuffers[i]->timing[j] += amount; + } +} + + +/** Put one packet into the jitter buffer */ +EXPORT void jitter_buffer_put(JitterBuffer *jitter, const JitterBufferPacket *packet) +{ + int i,j; + int late; + /*fprintf (stderr, "put packet %d %d\n", timestamp, span);*/ + + /* Cleanup buffer (remove old packets that weren't played) */ + if (!jitter->reset_state) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp + jitter->packets[i].span, jitter->pointer_timestamp)) + { + /*fprintf (stderr, "cleaned (not played)\n");*/ + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data = NULL; + } + } + } + + /*fprintf(stderr, "arrival: %d %d %d\n", packet->timestamp, jitter->next_stop, jitter->pointer_timestamp);*/ + /* Check if packet is late (could still be useful though) */ + if (!jitter->reset_state && LT32(packet->timestamp, jitter->next_stop)) + { + update_timings(jitter, ((spx_int32_t)packet->timestamp) - ((spx_int32_t)jitter->next_stop) - jitter->buffer_margin); + late = 1; + } else { + late = 0; + } + + /* For some reason, the consumer has failed the last 20 fetches. Make sure this packet is + * used to resync. */ + if (jitter->lost_count>20) + { + jitter_buffer_reset(jitter); + } + + /* Only insert the packet if it's not hopelessly late (i.e. totally useless) */ + if (jitter->reset_state || GE32(packet->timestamp+packet->span+jitter->delay_step, jitter->pointer_timestamp)) + { + + /*Find an empty slot in the buffer*/ + for (i=0;ipackets[i].data==NULL) + break; + } + + /*No place left in the buffer, need to make room for it by discarding the oldest packet */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + int earliest=jitter->packets[0].timestamp; + i=0; + for (j=1;jpackets[i].data || LT32(jitter->packets[j].timestamp,earliest)) + { + earliest = jitter->packets[j].timestamp; + i=j; + } + } + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data=NULL; + /*fprintf (stderr, "Buffer is full, discarding earliest frame %d (currently at %d)\n", timestamp, jitter->pointer_timestamp);*/ + } + + /* Copy packet in buffer */ + if (jitter->destroy) + { + jitter->packets[i].data = packet->data; + } else { + jitter->packets[i].data=(char*)speex_alloc(packet->len); + for (j=0;jlen;j++) + jitter->packets[i].data[j]=packet->data[j]; + } + jitter->packets[i].timestamp=packet->timestamp; + jitter->packets[i].span=packet->span; + jitter->packets[i].len=packet->len; + jitter->packets[i].sequence=packet->sequence; + jitter->packets[i].user_data=packet->user_data; + if (jitter->reset_state || late) + jitter->arrival[i] = 0; + else + jitter->arrival[i] = jitter->next_stop; + } + + +} + +/** Get one packet from the jitter buffer */ +EXPORT int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset) +{ + int i; + unsigned int j; + int incomplete = 0; + spx_int16_t opt; + + if (start_offset != NULL) + *start_offset = 0; + + /* Syncing on the first call */ + if (jitter->reset_state) + { + int found = 0; + /* Find the oldest packet */ + spx_uint32_t oldest=0; + for (i=0;ipackets[i].data && (!found || LT32(jitter->packets[i].timestamp,oldest))) + { + oldest = jitter->packets[i].timestamp; + found = 1; + } + } + if (found) + { + jitter->reset_state=0; + jitter->pointer_timestamp = oldest; + jitter->next_stop = oldest; + } else { + packet->timestamp = 0; + packet->span = jitter->interp_requested; + return JITTER_BUFFER_MISSING; + } + } + + + jitter->last_returned_timestamp = jitter->pointer_timestamp; + + if (jitter->interp_requested != 0) + { + packet->timestamp = jitter->pointer_timestamp; + packet->span = jitter->interp_requested; + + /* Increment the pointer because it got decremented in the delay update */ + jitter->pointer_timestamp += jitter->interp_requested; + packet->len = 0; + /*fprintf (stderr, "Deferred interpolate\n");*/ + + jitter->interp_requested = 0; + + jitter->buffered = packet->span - desired_span; + + return JITTER_BUFFER_INSERTION; + } + + /* Searching for the packet that fits best */ + + /* Search the buffer for a packet with the right timestamp and spanning the whole current chunk */ + for (i=0;ipackets[i].data && jitter->packets[i].timestamp==jitter->pointer_timestamp && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) + break; + } + + /* If no match, try for an "older" packet that still spans (fully) the current chunk */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) + break; + } + } + + /* If still no match, try for an "older" packet that spans part of the current chunk */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GT32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp)) + break; + } + } + + /* If still no match, try for earliest packet possible */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + int found = 0; + spx_uint32_t best_time=0; + int best_span=0; + int besti=0; + for (i=0;ipackets[i].data && LT32(jitter->packets[i].timestamp,jitter->pointer_timestamp+desired_span) && GE32(jitter->packets[i].timestamp,jitter->pointer_timestamp)) + { + if (!found || LT32(jitter->packets[i].timestamp,best_time) || (jitter->packets[i].timestamp==best_time && GT32(jitter->packets[i].span,best_span))) + { + best_time = jitter->packets[i].timestamp; + best_span = jitter->packets[i].span; + besti = i; + found = 1; + } + } + } + if (found) + { + i=besti; + incomplete = 1; + /*fprintf (stderr, "incomplete: %d %d %d %d\n", jitter->packets[i].timestamp, jitter->pointer_timestamp, chunk_size, jitter->packets[i].span);*/ + } + } + + /* If we find something */ + if (i!=SPEEX_JITTER_MAX_BUFFER_SIZE) + { + spx_int32_t offset; + + /* We (obviously) haven't lost this packet */ + jitter->lost_count = 0; + + /* In this case, 0 isn't as a valid timestamp */ + if (jitter->arrival[i] != 0) + { + update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); + } + + + /* Copy packet */ + if (jitter->destroy) + { + packet->data = jitter->packets[i].data; + packet->len = jitter->packets[i].len; + } else { + if (jitter->packets[i].len > packet->len) + { + speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); + } else { + packet->len = jitter->packets[i].len; + } + for (j=0;jlen;j++) + packet->data[j] = jitter->packets[i].data[j]; + /* Remove packet */ + speex_free(jitter->packets[i].data); + } + jitter->packets[i].data = NULL; + /* Set timestamp and span (if requested) */ + offset = (spx_int32_t)jitter->packets[i].timestamp-(spx_int32_t)jitter->pointer_timestamp; + if (start_offset != NULL) + *start_offset = offset; + else if (offset != 0) + speex_warning_int("jitter_buffer_get() discarding non-zero start_offset", offset); + + packet->timestamp = jitter->packets[i].timestamp; + jitter->last_returned_timestamp = packet->timestamp; + + packet->span = jitter->packets[i].span; + packet->sequence = jitter->packets[i].sequence; + packet->user_data = jitter->packets[i].user_data; + /* Point to the end of the current packet */ + jitter->pointer_timestamp = jitter->packets[i].timestamp+jitter->packets[i].span; + + jitter->buffered = packet->span - desired_span; + + if (start_offset != NULL) + jitter->buffered += *start_offset; + + return JITTER_BUFFER_OK; + } + + + /* If we haven't found anything worth returning */ + + /*fprintf (stderr, "not found\n");*/ + jitter->lost_count++; + /*fprintf (stderr, "m");*/ + /*fprintf (stderr, "lost_count = %d\n", jitter->lost_count);*/ + + opt = compute_opt_delay(jitter); + + /* Should we force an increase in the buffer or just do normal interpolation? */ + if (opt < 0) + { + /* Need to increase buffering */ + + /* Shift histogram to compensate */ + shift_timings(jitter, -opt); + + packet->timestamp = jitter->pointer_timestamp; + packet->span = -opt; + /* Don't move the pointer_timestamp forward */ + packet->len = 0; + + jitter->buffered = packet->span - desired_span; + return JITTER_BUFFER_INSERTION; + /*jitter->pointer_timestamp -= jitter->delay_step;*/ + /*fprintf (stderr, "Forced to interpolate\n");*/ + } else { + /* Normal packet loss */ + packet->timestamp = jitter->pointer_timestamp; + + desired_span = ROUND_DOWN(desired_span, jitter->concealment_size); + packet->span = desired_span; + jitter->pointer_timestamp += desired_span; + packet->len = 0; + + jitter->buffered = packet->span - desired_span; + return JITTER_BUFFER_MISSING; + /*fprintf (stderr, "Normal loss\n");*/ + } + + +} + +EXPORT int jitter_buffer_get_another(JitterBuffer *jitter, JitterBufferPacket *packet) +{ + int i, j; + for (i=0;ipackets[i].data && jitter->packets[i].timestamp==jitter->last_returned_timestamp) + break; + } + if (i!=SPEEX_JITTER_MAX_BUFFER_SIZE) + { + /* Copy packet */ + packet->len = jitter->packets[i].len; + if (jitter->destroy) + { + packet->data = jitter->packets[i].data; + } else { + for (j=0;jlen;j++) + packet->data[j] = jitter->packets[i].data[j]; + /* Remove packet */ + speex_free(jitter->packets[i].data); + } + jitter->packets[i].data = NULL; + packet->timestamp = jitter->packets[i].timestamp; + packet->span = jitter->packets[i].span; + packet->sequence = jitter->packets[i].sequence; + packet->user_data = jitter->packets[i].user_data; + return JITTER_BUFFER_OK; + } else { + packet->data = NULL; + packet->len = 0; + packet->span = 0; + return JITTER_BUFFER_MISSING; + } +} + +/* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ +static int _jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) +{ + spx_int16_t opt = compute_opt_delay(jitter); + /*fprintf(stderr, "opt adjustment is %d ", opt);*/ + + if (opt < 0) + { + shift_timings(jitter, -opt); + + jitter->pointer_timestamp += opt; + jitter->interp_requested = -opt; + /*fprintf (stderr, "Decision to interpolate %d samples\n", -opt);*/ + } else if (opt > 0) + { + shift_timings(jitter, -opt); + jitter->pointer_timestamp += opt; + /*fprintf (stderr, "Decision to drop %d samples\n", opt);*/ + } + + return opt; +} + +/* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ +EXPORT int jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) +{ + /* If the programmer calls jitter_buffer_update_delay() directly, + automatically disable auto-adjustment */ + jitter->auto_adjust = 0; + + return _jitter_buffer_update_delay(jitter, packet, start_offset); +} + +/** Get pointer timestamp of jitter buffer */ +EXPORT int jitter_buffer_get_pointer_timestamp(JitterBuffer *jitter) +{ + return jitter->pointer_timestamp; +} + +EXPORT void jitter_buffer_tick(JitterBuffer *jitter) +{ + /* Automatically-adjust the buffering delay if requested */ + if (jitter->auto_adjust) + _jitter_buffer_update_delay(jitter, NULL, NULL); + + if (jitter->buffered >= 0) + { + jitter->next_stop = jitter->pointer_timestamp - jitter->buffered; + } else { + jitter->next_stop = jitter->pointer_timestamp; + speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); + } + jitter->buffered = 0; +} + +EXPORT void jitter_buffer_remaining_span(JitterBuffer *jitter, spx_uint32_t rem) +{ + /* Automatically-adjust the buffering delay if requested */ + if (jitter->auto_adjust) + _jitter_buffer_update_delay(jitter, NULL, NULL); + + if (jitter->buffered < 0) + speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); + jitter->next_stop = jitter->pointer_timestamp - rem; +} + + +/* Used like the ioctl function to control the jitter buffer parameters */ +EXPORT int jitter_buffer_ctl(JitterBuffer *jitter, int request, void *ptr) +{ + int count, i; + switch(request) + { + case JITTER_BUFFER_SET_MARGIN: + jitter->buffer_margin = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_MARGIN: + *(spx_int32_t*)ptr = jitter->buffer_margin; + break; + case JITTER_BUFFER_GET_AVALIABLE_COUNT: + count = 0; + for (i=0;ipackets[i].data && LE32(jitter->pointer_timestamp, jitter->packets[i].timestamp)) + { + count++; + } + } + *(spx_int32_t*)ptr = count; + break; + case JITTER_BUFFER_SET_DESTROY_CALLBACK: + jitter->destroy = (void (*) (void *))ptr; + break; + case JITTER_BUFFER_GET_DESTROY_CALLBACK: + *(void (**) (void *))ptr = jitter->destroy; + break; + case JITTER_BUFFER_SET_DELAY_STEP: + jitter->delay_step = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_DELAY_STEP: + *(spx_int32_t*)ptr = jitter->delay_step; + break; + case JITTER_BUFFER_SET_CONCEALMENT_SIZE: + jitter->concealment_size = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_CONCEALMENT_SIZE: + *(spx_int32_t*)ptr = jitter->concealment_size; + break; + case JITTER_BUFFER_SET_MAX_LATE_RATE: + jitter->max_late_rate = *(spx_int32_t*)ptr; + jitter->window_size = 100*TOP_DELAY/jitter->max_late_rate; + jitter->subwindow_size = jitter->window_size/MAX_BUFFERS; + break; + case JITTER_BUFFER_GET_MAX_LATE_RATE: + *(spx_int32_t*)ptr = jitter->max_late_rate; + break; + case JITTER_BUFFER_SET_LATE_COST: + jitter->latency_tradeoff = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_LATE_COST: + *(spx_int32_t*)ptr = jitter->latency_tradeoff; + break; + default: + speex_warning_int("Unknown jitter_buffer_ctl request: ", request); + return -1; + } + return 0; +} + diff --git a/Libraries/speex/kiss_fft.c b/Libraries/speex/kiss_fft.c new file mode 100644 index 000000000..67782810f --- /dev/null +++ b/Libraries/speex/kiss_fft.c @@ -0,0 +1,523 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding +Copyright (c) 2005-2007, Jean-Marc Valin + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "_kiss_fft_guts.h" +#include "arch.h" +#include "os_support.h" + +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int N, + int mm + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1; + kiss_fft_cpx t; + if (!st->inverse) { + int i,j; + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for(j=0;jr , tw1->r),MULT16_16(Fout2->i , tw1->i)), 1); + ti = SHR32(ADD32(MULT16_16(Fout2->i , tw1->r),MULT16_16(Fout2->r , tw1->i)), 1); + tw1 += fstride; + Fout2->r = PSHR32(SUB32(SHL32(EXTEND32(Fout->r), 14), tr), 15); + Fout2->i = PSHR32(SUB32(SHL32(EXTEND32(Fout->i), 14), ti), 15); + Fout->r = PSHR32(ADD32(SHL32(EXTEND32(Fout->r), 14), tr), 15); + Fout->i = PSHR32(ADD32(SHL32(EXTEND32(Fout->i), 14), ti), 15); + ++Fout2; + ++Fout; + } + } + } else { + int i,j; + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for(j=0;jinverse) + { + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for (j=0;jtwiddles; + for (j=0;jr = PSHR16(Fout->r, 2); + Fout->i = PSHR16(Fout->i, 2); + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + Fout[m2].r = PSHR16(Fout[m2].r, 2); + Fout[m2].i = PSHR16(Fout[m2].i, 2); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + ++Fout; + } + } + } +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + if (!st->inverse) { + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + } + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; uinverse) { + C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); + } + scratch[0] = *Fout0; + + C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); + C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); + C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); + C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); + + C_ADD( scratch[7],scratch[1],scratch[4]); + C_SUB( scratch[10],scratch[1],scratch[4]); + C_ADD( scratch[8],scratch[2],scratch[3]); + C_SUB( scratch[9],scratch[2],scratch[3]); + + Fout0->r += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + kiss_fft_cpx scratchbuf[17]; + int Norig = st->nfft; + + /*CHECKBUF(scratchbuf,nscratchbuf,p);*/ + if (p>17) + speex_fatal("KissFFT: max radix supported is 17"); + + for ( u=0; uinverse) { + C_FIXDIV(scratchbuf[q1],p); + } + k += m; + } + + k=u; + for ( q1=0 ; q1

=Norig) twidx-=Norig; + C_MUL(t,scratchbuf[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } +} + +static +void kf_shuffle( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + + /*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/ + if (m==1) + { + int j; + for (j=0;j32000 || (spx_int32_t)p*(spx_int32_t)p > n) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; +#ifdef FIXED_POINT + for (i=0;iinverse) + phase = -phase; + kf_cexp2(st->twiddles+i, DIV32(SHL32(phase,17),nfft)); + } +#else + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } +#endif + kf_factor(nfft,st->factors); + } + return st; +} + + + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) + { + speex_fatal("In-place FFT not supported"); + /*CHECKBUF(tmpbuf,ntmpbuf,st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + SPEEX_MOVE(fout,tmpbuf,st->nfft);*/ + } else { + kf_shuffle( fout, fin, 1,in_stride, st->factors,st); + kf_work( fout, fin, 1,in_stride, st->factors,st, 1, in_stride, 1); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + diff --git a/Libraries/speex/kiss_fft.h b/Libraries/speex/kiss_fft.h new file mode 100644 index 000000000..fa3f2c604 --- /dev/null +++ b/Libraries/speex/kiss_fft.h @@ -0,0 +1,108 @@ +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include "arch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes) +#else +#define KISS_FFT_MALLOC speex_alloc +#endif + + +#ifdef FIXED_POINT +#include "arch.h" +# define kiss_fft_scalar spx_int16_t +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free speex_free + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Libraries/speex/kiss_fftr.c b/Libraries/speex/kiss_fftr.c new file mode 100644 index 000000000..f6275b879 --- /dev/null +++ b/Libraries/speex/kiss_fftr.c @@ -0,0 +1,297 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "os_support.h" +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + long pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize, memneeded; + + if (nfft & 1) { + speex_warning("Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + +#ifdef FIXED_POINT + for (i=0;i>1); + if (!inverse_fft) + phase = -phase; + kf_cexp2(st->super_twiddles+i, DIV32(SHL32(phase,16),nfft)); + } +#else + for (i=0;isuper_twiddles+i, phase ); + } +#endif + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata, kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + /*C_FIXDIV(st->tmpbuf[0],2);*/ + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + /*C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 );*/ + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} + +void kiss_fftr2(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_scalar *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx f2k,tdc; + spx_word32_t f1kr, f1ki, twr, twi; + + if ( st->substate->inverse) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0] = tdc.r + tdc.i; + freqdata[2*ncfft-1] = tdc.r - tdc.i; + + for ( k=1;k <= ncfft/2 ; ++k ) + { + /*fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[2*k-1] = HALF_OF(f1k.r + tw.r); + freqdata[2*k] = HALF_OF(f1k.i + tw.i); + freqdata[2*(ncfft-k)-1] = HALF_OF(f1k.r - tw.r); + freqdata[2*(ncfft-k)] = HALF_OF(tw.i - f1k.i); + */ + + /*f1k.r = PSHR32(ADD32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f1k.i = PSHR32(SUB32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + f2k.r = PSHR32(SUB32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f2k.i = SHR32(ADD32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[2*k-1] = HALF_OF(f1k.r + tw.r); + freqdata[2*k] = HALF_OF(f1k.i + tw.i); + freqdata[2*(ncfft-k)-1] = HALF_OF(f1k.r - tw.r); + freqdata[2*(ncfft-k)] = HALF_OF(tw.i - f1k.i); + */ + f2k.r = SHR32(SUB32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f2k.i = PSHR32(ADD32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + + f1kr = SHL32(ADD32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),13); + f1ki = SHL32(SUB32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),13); + + twr = SHR32(SUB32(MULT16_16(f2k.r,st->super_twiddles[k].r),MULT16_16(f2k.i,st->super_twiddles[k].i)), 1); + twi = SHR32(ADD32(MULT16_16(f2k.i,st->super_twiddles[k].r),MULT16_16(f2k.r,st->super_twiddles[k].i)), 1); + +#ifdef FIXED_POINT + freqdata[2*k-1] = PSHR32(f1kr + twr, 15); + freqdata[2*k] = PSHR32(f1ki + twi, 15); + freqdata[2*(ncfft-k)-1] = PSHR32(f1kr - twr, 15); + freqdata[2*(ncfft-k)] = PSHR32(twi - f1ki, 15); +#else + freqdata[2*k-1] = .5f*(f1kr + twr); + freqdata[2*k] = .5f*(f1ki + twi); + freqdata[2*(ncfft-k)-1] = .5f*(f1kr - twr); + freqdata[2*(ncfft-k)] = .5f*(twi - f1ki); + +#endif + } +} + +void kiss_fftri2(kiss_fftr_cfg st,const kiss_fft_scalar *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + speex_fatal ("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0] + freqdata[2*ncfft-1]; + st->tmpbuf[0].i = freqdata[0] - freqdata[2*ncfft-1]; + /*C_FIXDIV(st->tmpbuf[0],2);*/ + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk.r = freqdata[2*k-1]; + fk.i = freqdata[2*k]; + fnkc.r = freqdata[2*(ncfft - k)-1]; + fnkc.i = -freqdata[2*(ncfft - k)]; + /*C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 );*/ + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} diff --git a/Libraries/speex/kiss_fftr.h b/Libraries/speex/kiss_fftr.h new file mode 100644 index 000000000..7bfb42334 --- /dev/null +++ b/Libraries/speex/kiss_fftr.h @@ -0,0 +1,51 @@ +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftr2(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_scalar *freqdata); + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); + +void kiss_fftri2(kiss_fftr_cfg st,const kiss_fft_scalar *freqdata, kiss_fft_scalar *timedata); + +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free speex_free + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Libraries/speex/lpc.c b/Libraries/speex/lpc.c new file mode 100644 index 000000000..fd5d3821e --- /dev/null +++ b/Libraries/speex/lpc.c @@ -0,0 +1,201 @@ +/* + Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, + Technische Universitaet Berlin + + Any use of this software is permitted provided that this notice is not + removed and that neither the authors nor the Technische Universitaet Berlin + are deemed to have made any representations as to the suitability of this + software for any purpose nor are held responsible for any defects of + this software. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + + As a matter of courtesy, the authors request to be informed about uses + this software has found, about bugs in this software, and about any + improvements that may be of general interest. + + Berlin, 28.11.1994 + Jutta Degener + Carsten Bormann + + + Code modified by Jean-Marc Valin + + Speex License: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "lpc.h" + +#ifdef BFIN_ASM +#include "lpc_bfin.h" +#endif + +/* LPC analysis + * + * The next two functions calculate linear prediction coefficients + * and/or the related reflection coefficients from the first P_MAX+1 + * values of the autocorrelation function. + */ + +/* Invented by N. Levinson in 1947, modified by J. Durbin in 1959. + */ + +/* returns minimum mean square error */ +spx_word32_t _spx_lpc( +spx_coef_t *lpc, /* out: [0...p-1] LPC coefficients */ +const spx_word16_t *ac, /* in: [0...p] autocorrelation values */ +int p +) +{ + int i, j; + spx_word16_t r; + spx_word16_t error = ac[0]; + + if (ac[0] == 0) + { + for (i = 0; i < p; i++) + lpc[i] = 0; + return 0; + } + + for (i = 0; i < p; i++) { + + /* Sum up this iteration's reflection coefficient */ + spx_word32_t rr = NEG32(SHL32(EXTEND32(ac[i + 1]),13)); + for (j = 0; j < i; j++) + rr = SUB32(rr,MULT16_16(lpc[j],ac[i - j])); +#ifdef FIXED_POINT + r = DIV32_16(rr+PSHR32(error,1),ADD16(error,8)); +#else + r = rr/(error+.003*ac[0]); +#endif + /* Update LPC coefficients and total error */ + lpc[i] = r; + for (j = 0; j < i>>1; j++) + { + spx_word16_t tmp = lpc[j]; + lpc[j] = MAC16_16_P13(lpc[j],r,lpc[i-1-j]); + lpc[i-1-j] = MAC16_16_P13(lpc[i-1-j],r,tmp); + } + if (i & 1) + lpc[j] = MAC16_16_P13(lpc[j],lpc[j],r); + + error = SUB16(error,MULT16_16_Q13(r,MULT16_16_Q13(error,r))); + } + return error; +} + + +#ifdef FIXED_POINT + +/* Compute the autocorrelation + * ,--, + * ac(i) = > x(n) * x(n-i) for all n + * `--' + * for lags between 0 and lag-1, and x == 0 outside 0...n-1 + */ + +#ifndef OVERRIDE_SPEEX_AUTOCORR +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +spx_word16_t *ac, /* out: [0...lag-1] ac values */ +int lag, +int n +) +{ + spx_word32_t d; + int i, j; + spx_word32_t ac0=1; + int shift, ac_shift; + + for (j=0;j x(n) * x(n-i) for all n + * `--' + * for lags between 0 and lag-1, and x == 0 outside 0...n-1 + */ +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +float *ac, /* out: [0...lag-1] ac values */ +int lag, +int n +) +{ + float d; + int i; + while (lag--) + { + for (i = lag, d = 0; i < n; i++) + d += x[i] * x[i-lag]; + ac[lag] = d; + } + ac[0] += 10; +} + +#endif + + diff --git a/Libraries/speex/lpc.h b/Libraries/speex/lpc.h new file mode 100644 index 000000000..952ecdd93 --- /dev/null +++ b/Libraries/speex/lpc.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file lpc.h + @brief Functions for LPC (Linear Prediction Coefficients) analysis +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef LPC_H +#define LPC_H + +#include "arch.h" + +void _spx_autocorr( + const spx_word16_t * x, /* in: [0...n-1] samples x */ + spx_word16_t *ac, /* out: [0...lag-1] ac values */ + int lag, int n); + +spx_word32_t /* returns minimum mean square error */ +_spx_lpc( + spx_coef_t * lpc, /* [0...p-1] LPC coefficients */ + const spx_word16_t * ac, /* in: [0...p] autocorrelation values */ + int p + ); + + +#endif diff --git a/Libraries/speex/lpc_bfin.h b/Libraries/speex/lpc_bfin.h new file mode 100644 index 000000000..7310ffba5 --- /dev/null +++ b/Libraries/speex/lpc_bfin.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file lpc_bfin.h + @author Jean-Marc Valin + @brief Functions for LPC (Linear Prediction Coefficients) analysis (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_SPEEX_AUTOCORR +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +spx_word16_t *ac, /* out: [0...lag-1] ac values */ +int lag, +int n + ) +{ + spx_word32_t d; + const spx_word16_t *xs; + int i, j; + spx_word32_t ac0=1; + spx_word32_t ac32[11], *ac32top; + int shift, ac_shift; + ac32top = ac32+lag-1; + int lag_1, N_lag; + int nshift; + lag_1 = lag-1; + N_lag = n-lag_1; + for (j=0;j> 1;\n\t" + "LOOP_BEGIN pitch%=;\n\t" + "I1 = P0;\n\t" + "A1 = A0 = 0;\n\t" + "R1 = [I1++];\n\t" + "LOOP inner_prod%= LC1 = P3 >> 1;\n\t" + "LOOP_BEGIN inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R1.H = W[I1++] || R0 = [I0++];\n\t" + "LOOP_END inner_prod%=;\n\t" + "A0 = ASHIFT A0 by R4.L;\n\t" + "A1 = ASHIFT A1 by R4.L;\n\t" + + "R2 = A0, R3 = A1;\n\t" + "[P1--] = R2;\n\t" + "[P1--] = R3;\n\t" + "P0 += 4;\n\t" + "LOOP_END pitch%=;\n\t" + : : "m" (xs), "m" (x), "m" (ac32top), "m" (N_lag), "m" (lag_1), "m" (nshift) + : "A0", "A1", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "R3", "R4", "I0", "I1", "L0", "L1", "B0", "B1", "memory" + ); + d=0; + for (j=0;j +#include "lsp.h" +#include "stack_alloc.h" +#include "math_approx.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef FIXED_POINT + +#define FREQ_SCALE 16384 + +/*#define ANGLE2X(a) (32768*cos(((a)/8192.)))*/ +#define ANGLE2X(a) (SHL16(spx_cos(a),2)) + +/*#define X2ANGLE(x) (acos(.00006103515625*(x))*LSP_SCALING)*/ +#define X2ANGLE(x) (spx_acos(x)) + +#ifdef BFIN_ASM +#include "lsp_bfin.h" +#endif + +#else + +/*#define C1 0.99940307 +#define C2 -0.49558072 +#define C3 0.03679168*/ + +#define FREQ_SCALE 1. +#define ANGLE2X(a) (spx_cos(a)) +#define X2ANGLE(x) (acos(x)) + +#endif + + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: cheb_poly_eva() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + This function evaluates a series of Chebyshev polynomials + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT + +#ifndef OVERRIDE_CHEB_POLY_EVA +static inline spx_word32_t cheb_poly_eva( + spx_word16_t *coef, /* P or Q coefs in Q13 format */ + spx_word16_t x, /* cos of freq (-1.0 to 1.0) in Q14 format */ + int m, /* LPC order/2 */ + char *stack +) +{ + int i; + spx_word16_t b0, b1; + spx_word32_t sum; + + /*Prevents overflows*/ + if (x>16383) + x = 16383; + if (x<-16383) + x = -16383; + + /* Initialise values */ + b1=16384; + b0=x; + + /* Evaluate Chebyshev series formulation usin g iterative approach */ + sum = ADD32(EXTEND32(coef[m]), EXTEND32(MULT16_16_P14(coef[m-1],x))); + for(i=2;i<=m;i++) + { + spx_word16_t tmp=b0; + b0 = SUB16(MULT16_16_Q13(x,b0), b1); + b1 = tmp; + sum = ADD32(sum, EXTEND32(MULT16_16_P14(coef[m-i],b0))); + } + + return sum; +} +#endif + +#else + +static float cheb_poly_eva(spx_word32_t *coef, spx_word16_t x, int m, char *stack) +{ + int k; + float b0, b1, tmp; + + /* Initial conditions */ + b0=0; /* b_(m+1) */ + b1=0; /* b_(m+2) */ + + x*=2; + + /* Calculate the b_(k) */ + for(k=m;k>0;k--) + { + tmp=b0; /* tmp holds the previous value of b0 */ + b0=x*b0-b1+coef[m-k]; /* b0 holds its new value based on b0 and b1 */ + b1=tmp; /* b1 holds the previous value of b0 */ + } + + return(-b1+.5*x*b0+coef[m]); +} +#endif + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: lpc_to_lsp() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + This function converts LPC coefficients to LSP + coefficients. + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT +#define SIGN_CHANGE(a,b) (((a)&0x70000000)^((b)&0x70000000)||(b==0)) +#else +#define SIGN_CHANGE(a,b) (((a)*(b))<0.0) +#endif + + +int lpc_to_lsp (spx_coef_t *a,int lpcrdr,spx_lsp_t *freq,int nb,spx_word16_t delta, char *stack) +/* float *a lpc coefficients */ +/* int lpcrdr order of LPC coefficients (10) */ +/* float *freq LSP frequencies in the x domain */ +/* int nb number of sub-intervals (4) */ +/* float delta grid spacing interval (0.02) */ + + +{ + spx_word16_t temp_xr,xl,xr,xm=0; + spx_word32_t psuml,psumr,psumm,temp_psumr/*,temp_qsumr*/; + int i,j,m,flag,k; + VARDECL(spx_word32_t *Q); /* ptrs for memory allocation */ + VARDECL(spx_word32_t *P); + VARDECL(spx_word16_t *Q16); /* ptrs for memory allocation */ + VARDECL(spx_word16_t *P16); + spx_word32_t *px; /* ptrs of respective P'(z) & Q'(z) */ + spx_word32_t *qx; + spx_word32_t *p; + spx_word32_t *q; + spx_word16_t *pt; /* ptr used for cheb_poly_eval() + whether P' or Q' */ + int roots=0; /* DR 8/2/94: number of roots found */ + flag = 1; /* program is searching for a root when, + 1 else has found one */ + m = lpcrdr/2; /* order of P'(z) & Q'(z) polynomials */ + + /* Allocate memory space for polynomials */ + ALLOC(Q, (m+1), spx_word32_t); + ALLOC(P, (m+1), spx_word32_t); + + /* determine P'(z)'s and Q'(z)'s coefficients where + P'(z) = P(z)/(1 + z^(-1)) and Q'(z) = Q(z)/(1-z^(-1)) */ + + px = P; /* initialise ptrs */ + qx = Q; + p = px; + q = qx; + +#ifdef FIXED_POINT + *px++ = LPC_SCALING; + *qx++ = LPC_SCALING; + for(i=0;i=32768) + speex_warning_int("px", *px); + if (fabs(*qx)>=32768) + speex_warning_int("qx", *qx);*/ + *px = PSHR32(*px,2); + *qx = PSHR32(*qx,2); + px++; + qx++; + } + /* The reason for this lies in the way cheb_poly_eva() is implemented for fixed-point */ + P[m] = PSHR32(P[m],3); + Q[m] = PSHR32(Q[m],3); +#else + *px++ = LPC_SCALING; + *qx++ = LPC_SCALING; + for(i=0;i= -FREQ_SCALE)){ + spx_word16_t dd; + /* Modified by JMV to provide smaller steps around x=+-1 */ +#ifdef FIXED_POINT + dd = MULT16_16_Q15(delta,SUB16(FREQ_SCALE, MULT16_16_Q14(MULT16_16_Q14(xl,xl),14000))); + if (psuml<512 && psuml>-512) + dd = PSHR16(dd,1); +#else + dd=delta*(1-.9*xl*xl); + if (fabs(psuml)<.2) + dd *= .5; +#endif + xr = SUB16(xl, dd); /* interval spacing */ + psumr = cheb_poly_eva(pt,xr,m,stack);/* poly(xl-delta_x) */ + temp_psumr = psumr; + temp_xr = xr; + + /* if no sign change increment xr and re-evaluate poly(xr). Repeat til + sign change. + if a sign change has occurred the interval is bisected and then + checked again for a sign change which determines in which + interval the zero lies in. + If there is no sign change between poly(xm) and poly(xl) set interval + between xm and xr else set interval between xl and xr and repeat till + root is located within the specified limits */ + + if(SIGN_CHANGE(psumr,psuml)) + { + roots++; + + psumm=psuml; + for(k=0;k<=nb;k++){ +#ifdef FIXED_POINT + xm = ADD16(PSHR16(xl,1),PSHR16(xr,1)); /* bisect the interval */ +#else + xm = .5*(xl+xr); /* bisect the interval */ +#endif + psumm=cheb_poly_eva(pt,xm,m,stack); + /*if(psumm*psuml>0.)*/ + if(!SIGN_CHANGE(psumm,psuml)) + { + psuml=psumm; + xl=xm; + } else { + psumr=psumm; + xr=xm; + } + } + + /* once zero is found, reset initial interval to xr */ + freq[j] = X2ANGLE(xm); + xl = xm; + flag = 0; /* reset flag for next search */ + } + else{ + psuml=temp_psumr; + xl=temp_xr; + } + } + } + return(roots); +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: lsp_to_lpc() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + Converts LSP coefficients to LPC coefficients. + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT + +void lsp_to_lpc(spx_lsp_t *freq,spx_coef_t *ak,int lpcrdr, char *stack) +/* float *freq array of LSP frequencies in the x domain */ +/* float *ak array of LPC coefficients */ +/* int lpcrdr order of LPC coefficients */ +{ + int i,j; + spx_word32_t xout1,xout2,xin; + spx_word32_t mult, a; + VARDECL(spx_word16_t *freqn); + VARDECL(spx_word32_t **xp); + VARDECL(spx_word32_t *xpmem); + VARDECL(spx_word32_t **xq); + VARDECL(spx_word32_t *xqmem); + int m = lpcrdr>>1; + + /* + + Reconstruct P(z) and Q(z) by cascading second order polynomials + in form 1 - 2cos(w)z(-1) + z(-2), where w is the LSP frequency. + In the time domain this is: + + y(n) = x(n) - 2cos(w)x(n-1) + x(n-2) + + This is what the ALLOCS below are trying to do: + + int xp[m+1][lpcrdr+1+2]; // P matrix in QIMP + int xq[m+1][lpcrdr+1+2]; // Q matrix in QIMP + + These matrices store the output of each stage on each row. The + final (m-th) row has the output of the final (m-th) cascaded + 2nd order filter. The first row is the impulse input to the + system (not written as it is known). + + The version below takes advantage of the fact that a lot of the + outputs are zero or known, for example if we put an inpulse + into the first section the "clock" it 10 times only the first 3 + outputs samples are non-zero (it's an FIR filter). + */ + + ALLOC(xp, (m+1), spx_word32_t*); + ALLOC(xpmem, (m+1)*(lpcrdr+1+2), spx_word32_t); + + ALLOC(xq, (m+1), spx_word32_t*); + ALLOC(xqmem, (m+1)*(lpcrdr+1+2), spx_word32_t); + + for(i=0; i<=m; i++) { + xp[i] = xpmem + i*(lpcrdr+1+2); + xq[i] = xqmem + i*(lpcrdr+1+2); + } + + /* work out 2cos terms in Q14 */ + + ALLOC(freqn, lpcrdr, spx_word16_t); + for (i=0;i 32767) a = 32767; + ak[j-1] = (short)a; + + } + +} + +#else + +void lsp_to_lpc(spx_lsp_t *freq,spx_coef_t *ak,int lpcrdr, char *stack) +/* float *freq array of LSP frequencies in the x domain */ +/* float *ak array of LPC coefficients */ +/* int lpcrdr order of LPC coefficients */ + + +{ + int i,j; + float xout1,xout2,xin1,xin2; + VARDECL(float *Wp); + float *pw,*n1,*n2,*n3,*n4=NULL; + VARDECL(float *x_freq); + int m = lpcrdr>>1; + + ALLOC(Wp, 4*m+2, float); + pw = Wp; + + /* initialise contents of array */ + + for(i=0;i<=4*m+1;i++){ /* set contents of buffer to 0 */ + *pw++ = 0.0; + } + + /* Set pointers up */ + + pw = Wp; + xin1 = 1.0; + xin2 = 1.0; + + ALLOC(x_freq, lpcrdr, float); + for (i=0;i0) + ak[j-1] = (xout1 + xout2)*0.5f; + *(n4+1) = xin1; + *(n4+2) = xin2; + + xin1 = 0.0; + xin2 = 0.0; + } + +} +#endif + + +#ifdef FIXED_POINT + +/*Makes sure the LSPs are stable*/ +void lsp_enforce_margin(spx_lsp_t *lsp, int len, spx_word16_t margin) +{ + int i; + spx_word16_t m = margin; + spx_word16_t m2 = 25736-margin; + + if (lsp[0]m2) + lsp[len-1]=m2; + for (i=1;ilsp[i+1]-m) + lsp[i]= SHR16(lsp[i],1) + SHR16(lsp[i+1]-m,1); + } +} + + +void lsp_interpolate(spx_lsp_t *old_lsp, spx_lsp_t *new_lsp, spx_lsp_t *interp_lsp, int len, int subframe, int nb_subframes) +{ + int i; + spx_word16_t tmp = DIV32_16(SHL32(EXTEND32(1 + subframe),14),nb_subframes); + spx_word16_t tmp2 = 16384-tmp; + for (i=0;iLSP_SCALING*(M_PI-margin)) + lsp[len-1]=LSP_SCALING*(M_PI-margin); + for (i=1;ilsp[i+1]-LSP_SCALING*margin) + lsp[i]= .5f* (lsp[i] + lsp[i+1]-LSP_SCALING*margin); + } +} + + +void lsp_interpolate(spx_lsp_t *old_lsp, spx_lsp_t *new_lsp, spx_lsp_t *interp_lsp, int len, int subframe, int nb_subframes) +{ + int i; + float tmp = (1.0f + subframe)/nb_subframes; + for (i=0;i>>= 14;\n\t" + "R3 = R3 + R5;\n\t" + + "R0 = R2;\n\t" /* R0: b0 */ + "R1 = 16384;\n\t" /* R1: b1 */ + "LOOP cpe%= LC0 = %3;\n\t" + "LOOP_BEGIN cpe%=;\n\t" + "P1 = R0;\n\t" + "R0 = R2.L * R0.L (IS) || R5 = W[P0--] (X);\n\t" + "R0 >>>= 13;\n\t" + "R0 = R0 - R1;\n\t" + "R1 = P1;\n\t" + "R5 = R5.L * R0.L (IS);\n\t" + "R5 = R5 + R4;\n\t" + "R5 >>>= 14;\n\t" + "R3 = R3 + R5;\n\t" + "LOOP_END cpe%=;\n\t" + "%0 = R3;\n\t" + : "=&d" (sum) + : "a" (x), "a" (&coef[m]), "a" (m-1) + : "R0", "R1", "R3", "R2", "R4", "R5", "P0", "P1" + ); + return sum; +} +#endif + + + diff --git a/Libraries/speex/lsp_tables_nb.c b/Libraries/speex/lsp_tables_nb.c new file mode 100644 index 000000000..16f2e1b64 --- /dev/null +++ b/Libraries/speex/lsp_tables_nb.c @@ -0,0 +1,360 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: lsp_tables_nb.c + Codebooks for LSPs in narrowband CELP mode + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char cdbk_nb[640]={ +30,19,38,34,40,32,46,43,58,43, +5,-18,-25,-40,-33,-55,-52,20,34,28, +-20,-63,-97,-92,61,53,47,49,53,75, +-14,-53,-77,-79,0,-3,-5,19,22,26, +-9,-53,-55,66,90,72,85,68,74,52, +-4,-41,-58,-31,-18,-31,27,32,30,18, +24,3,8,5,-12,-3,26,28,74,63, +-2,-39,-67,-77,-106,-74,59,59,73,65, +44,40,71,72,82,83,98,88,89,60, +-6,-31,-47,-48,-13,-39,-9,7,2,79, +-1,-39,-60,-17,87,81,65,50,45,19, +-21,-67,-91,-87,-41,-50,7,18,39,74, +10,-31,-28,39,24,13,23,5,56,45, +29,10,-5,-13,-11,-35,-18,-8,-10,-8, +-25,-71,-77,-21,2,16,50,63,87,87, +5,-32,-40,-51,-68,0,12,6,54,34, +5,-12,32,52,68,64,69,59,65,45, +14,-16,-31,-40,-65,-67,41,49,47,37, +-11,-52,-75,-84,-4,57,48,42,42,33, +-11,-51,-68,-6,13,0,8,-8,26,32, +-23,-53,0,36,56,76,97,105,111,97, +-1,-28,-39,-40,-43,-54,-44,-40,-18,35, +16,-20,-19,-28,-42,29,47,38,74,45, +3,-29,-48,-62,-80,-104,-33,56,59,59, +10,17,46,72,84,101,117,123,123,106, +-7,-33,-49,-51,-70,-67,-27,-31,70,67, +-16,-62,-85,-20,82,71,86,80,85,74, +-19,-58,-75,-45,-29,-33,-18,-25,45,57, +-12,-42,-5,12,28,36,52,64,81,82, +13,-9,-27,-28,22,3,2,22,26,6, +-6,-44,-51,2,15,10,48,43,49,34, +-19,-62,-84,-89,-102,-24,8,17,61,68, +39,24,23,19,16,-5,12,15,27,15, +-8,-44,-49,-60,-18,-32,-28,52,54,62, +-8,-48,-77,-70,66,101,83,63,61,37, +-12,-50,-75,-64,33,17,13,25,15,77, +1,-42,-29,72,64,46,49,31,61,44, +-8,-47,-54,-46,-30,19,20,-1,-16,0, +16,-12,-18,-9,-26,-27,-10,-22,53,45, +-10,-47,-75,-82,-105,-109,8,25,49,77, +50,65,114,117,124,118,115,96,90,61, +-9,-45,-63,-60,-75,-57,8,11,20,29, +0,-35,-49,-43,40,47,35,40,55,38, +-24,-76,-103,-112,-27,3,23,34,52,75, +8,-29,-43,12,63,38,35,29,24,8, +25,11,1,-15,-18,-43,-7,37,40,21, +-20,-56,-19,-19,-4,-2,11,29,51,63, +-2,-44,-62,-75,-89,30,57,51,74,51, +50,46,68,64,65,52,63,55,65,43, +18,-9,-26,-35,-55,-69,3,6,8,17, +-15,-61,-86,-97,1,86,93,74,78,67, +-1,-38,-66,-48,48,39,29,25,17,-1, +13,13,29,39,50,51,69,82,97,98, +-2,-36,-46,-27,-16,-30,-13,-4,-7,-4, +25,-5,-11,-6,-25,-21,33,12,31,29, +-8,-38,-52,-63,-68,-89,-33,-1,10,74, +-2,-15,59,91,105,105,101,87,84,62, +-7,-33,-50,-35,-54,-47,25,17,82,81, +-13,-56,-83,21,58,31,42,25,72,65, +-24,-66,-91,-56,9,-2,21,10,69,75, +2,-24,11,22,25,28,38,34,48,33, +7,-29,-26,17,15,-1,14,0,-2,0, +-6,-41,-67,6,-2,-9,19,2,85,74, +-22,-67,-84,-71,-50,3,11,-9,2,62}; + +const signed char cdbk_nb_low1[320]={ +-34,-52,-15,45,2, +23,21,52,24,-33, +-9,-1,9,-44,-41, +-13,-17,44,22,-17, +-6,-4,-1,22,38, +26,16,2,50,27, +-35,-34,-9,-41,6, +0,-16,-34,51,8, +-14,-31,-49,15,-33, +45,49,33,-11,-37, +-62,-54,45,11,-5, +-72,11,-1,-12,-11, +24,27,-11,-43,46, +43,33,-12,-9,-1, +1,-4,-23,-57,-71, +11,8,16,17,-8, +-20,-31,-41,53,48, +-16,3,65,-24,-8, +-23,-32,-37,-32,-49, +-10,-17,6,38,5, +-9,-17,-46,8,52, +3,6,45,40,39, +-7,-6,-34,-74,31, +8,1,-16,43,68, +-11,-19,-31,4,6, +0,-6,-17,-16,-38, +-16,-30,2,9,-39, +-16,-1,43,-10,48, +3,3,-16,-31,-3, +62,68,43,13,3, +-10,8,20,-56,12, +12,-2,-18,22,-15, +-40,-36,1,7,41, +0,1,46,-6,-62, +-4,-12,-2,-11,-83, +-13,-2,91,33,-10, +0,4,-11,-16,79, +32,37,14,9,51, +-21,-28,-56,-34,0, +21,9,-26,11,28, +-42,-54,-23,-2,-15, +31,30,8,-39,-66, +-39,-36,31,-28,-40, +-46,35,40,22,24, +33,48,23,-34,14, +40,32,17,27,-3, +25,26,-13,-61,-17, +11,4,31,60,-6, +-26,-41,-64,13,16, +-26,54,31,-11,-23, +-9,-11,-34,-71,-21, +-34,-35,55,50,29, +-22,-27,-50,-38,57, +33,42,57,48,26, +11,0,-49,-31,26, +-4,-14,5,78,37, +17,0,-49,-12,-23, +26,14,2,2,-43, +-17,-12,10,-8,-4, +8,18,12,-6,20, +-12,-6,-13,-25,34, +15,40,49,7,8, +13,20,20,-19,-22, +-2,-8,2,51,-51}; + +const signed char cdbk_nb_low2[320]={ +-6,53,-21,-24,4, +26,17,-4,-37,25, +17,-36,-13,31,3, +-6,27,15,-10,31, +28,26,-10,-10,-40, +16,-7,15,13,41, +-9,0,-4,50,-6, +-7,14,38,22,0, +-48,2,1,-13,-19, +32,-3,-60,11,-17, +-1,-24,-34,-1,35, +-5,-27,28,44,13, +25,15,42,-11,15, +51,35,-36,20,8, +-4,-12,-29,19,-47, +49,-15,-4,16,-29, +-39,14,-30,4,25, +-9,-5,-51,-14,-3, +-40,-32,38,5,-9, +-8,-4,-1,-22,71, +-3,14,26,-18,-22, +24,-41,-25,-24,6, +23,19,-10,39,-26, +-27,65,45,2,-7, +-26,-8,22,-12,16, +15,16,-35,-5,33, +-21,-8,0,23,33, +34,6,21,36,6, +-7,-22,8,-37,-14, +31,38,11,-4,-3, +-39,-32,-8,32,-23, +-6,-12,16,20,-28, +-4,23,13,-52,-1, +22,6,-33,-40,-6, +4,-62,13,5,-26, +35,39,11,2,57, +-11,9,-20,-28,-33, +52,-5,-6,-2,22, +-14,-16,-48,35,1, +-58,20,13,33,-1, +-74,56,-18,-22,-31, +12,6,-14,4,-2, +-9,-47,10,-3,29, +-17,-5,61,14,47, +-12,2,72,-39,-17, +92,64,-53,-51,-15, +-30,-38,-41,-29,-28, +27,9,36,9,-35, +-42,81,-21,20,25, +-16,-5,-17,-35,21, +15,-28,48,2,-2, +9,-19,29,-40,30, +-18,-18,18,-16,-57, +15,-20,-12,-15,-37, +-15,33,-39,21,-22, +-13,35,11,13,-38, +-63,29,23,-27,32, +18,3,-26,42,33, +-64,-66,-17,16,56, +2,36,3,31,21, +-41,-39,8,-57,14, +37,-2,19,-36,-19, +-23,-29,-16,1,-3, +-8,-10,31,64,-65}; + +const signed char cdbk_nb_high1[320]={ +-26,-8,29,21,4, +19,-39,33,-7,-36, +56,54,48,40,29, +-4,-24,-42,-66,-43, +-60,19,-2,37,41, +-10,-37,-60,-64,18, +-22,77,73,40,25, +4,19,-19,-66,-2, +11,5,21,14,26, +-25,-86,-4,18,1, +26,-37,10,37,-1, +24,-12,-59,-11,20, +-6,34,-16,-16,42, +19,-28,-51,53,32, +4,10,62,21,-12, +-34,27,4,-48,-48, +-50,-49,31,-7,-21, +-42,-25,-4,-43,-22, +59,2,27,12,-9, +-6,-16,-8,-32,-58, +-16,-29,-5,41,23, +-30,-33,-46,-13,-10, +-38,52,52,1,-17, +-9,10,26,-25,-6, +33,-20,53,55,25, +-32,-5,-42,23,21, +66,5,-28,20,9, +75,29,-7,-42,-39, +15,3,-23,21,6, +11,1,-29,14,63, +10,54,26,-24,-51, +-49,7,-23,-51,15, +-66,1,60,25,10, +0,-30,-4,-15,17, +19,59,40,4,-5, +33,6,-22,-58,-70, +-5,23,-6,60,44, +-29,-16,-47,-29,52, +-19,50,28,16,35, +31,36,0,-21,6, +21,27,22,42,7, +-66,-40,-8,7,19, +46,0,-4,60,36, +45,-7,-29,-6,-32, +-39,2,6,-9,33, +20,-51,-34,18,-6, +19,6,11,5,-19, +-29,-2,42,-11,-45, +-21,-55,57,37,2, +-14,-67,-16,-27,-38, +69,48,19,2,-17, +20,-20,-16,-34,-17, +-25,-61,10,73,45, +16,-40,-64,-17,-29, +-22,56,17,-39,8, +-11,8,-25,-18,-13, +-19,8,54,57,36, +-17,-26,-4,6,-21, +40,42,-4,20,31, +53,10,-34,-53,31, +-17,35,0,15,-6, +-20,-63,-73,22,25, +29,17,8,-29,-39, +-69,18,15,-15,-5}; + +const signed char cdbk_nb_high2[320]={ +11,47,16,-9,-46, +-32,26,-64,34,-5, +38,-7,47,20,2, +-73,-99,-3,-45,20, +70,-52,15,-6,-7, +-82,31,21,47,51, +39,-3,9,0,-41, +-7,-15,-54,2,0, +27,-31,9,-45,-22, +-38,-24,-24,8,-33, +23,5,50,-36,-17, +-18,-51,-2,13,19, +43,12,-15,-12,61, +38,38,7,13,0, +6,-1,3,62,9, +27,22,-33,38,-35, +-9,30,-43,-9,-32, +-1,4,-4,1,-5, +-11,-8,38,31,11, +-10,-42,-21,-37,1, +43,15,-13,-35,-19, +-18,15,23,-26,59, +1,-21,53,8,-41, +-50,-14,-28,4,21, +25,-28,-40,5,-40, +-41,4,51,-33,-8, +-8,1,17,-60,12, +25,-41,17,34,43, +19,45,7,-37,24, +-15,56,-2,35,-10, +48,4,-47,-2,5, +-5,-54,5,-3,-33, +-10,30,-2,-44,-24, +-38,9,-9,42,4, +6,-56,44,-16,9, +-40,-26,18,-20,10, +28,-41,-21,-4,13, +-18,32,-30,-3,37, +15,22,28,50,-40, +3,-29,-64,7,51, +-19,-11,17,-27,-40, +-64,24,-12,-7,-27, +3,37,48,-1,2, +-9,-38,-34,46,1, +27,-6,19,-13,26, +10,34,20,25,40, +50,-6,-7,30,9, +-24,0,-23,71,-61, +22,58,-34,-4,2, +-49,-33,25,30,-8, +-6,-16,77,2,38, +-8,-35,-6,-30,56, +78,31,33,-20,13, +-39,20,22,4,21, +-8,4,-6,10,-83, +-41,9,-25,-43,15, +-7,-12,-34,-39,-37, +-33,19,30,16,-33, +42,-25,25,-68,44, +-15,-11,-4,23,50, +14,4,-39,-43,20, +-30,60,9,-20,7, +16,19,-33,37,29, +16,-35,7,38,-27}; diff --git a/Libraries/speex/ltp.c b/Libraries/speex/ltp.c new file mode 100644 index 000000000..0129c95f1 --- /dev/null +++ b/Libraries/speex/ltp.c @@ -0,0 +1,839 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: ltp.c + Long-Term Prediction functions + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "ltp.h" +#include "stack_alloc.h" +#include "filters.h" +#include +#include "math_approx.h" +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + + +#ifdef _USE_SSE +#include "ltp_sse.h" +#elif defined (ARM4_ASM) || defined(ARM5E_ASM) +#include "ltp_arm4.h" +#elif defined (BFIN_ASM) +#include "ltp_bfin.h" +#endif + +#ifndef OVERRIDE_INNER_PROD +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len) +{ + spx_word32_t sum=0; + len >>= 2; + while(len--) + { + spx_word32_t part=0; + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + /* HINT: If you had a 40-bit accumulator, you could shift only at the end */ + sum = ADD32(sum,SHR32(part,6)); + } + return sum; +} +#endif + +#ifndef OVERRIDE_PITCH_XCORR +#if 0 /* HINT: Enable this for machines with enough registers (i.e. not x86) */ +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + int i,j; + for (i=0;i16383) + { + scaledown=1; + break; + } + } + /* If the weighted input is close to saturation, then we scale it down */ + if (scaledown) + { + for (i=-end;iMULT16_16(best_score[N-1],ADD16(1,ener16[i-start]))) + { + /* We can safely put it last and then check */ + best_score[N-1]=tmp; + best_ener[N-1]=ener16[i-start]+1; + pitch[N-1]=i; + /* Check if it comes in front of others */ + for (j=0;jMULT16_16(best_score[j],ADD16(1,ener16[i-start]))) + { + for (k=N-1;k>j;k--) + { + best_score[k]=best_score[k-1]; + best_ener[k]=best_ener[k-1]; + pitch[k]=pitch[k-1]; + } + best_score[j]=tmp; + best_ener[j]=ener16[i-start]+1; + pitch[j]=i; + break; + } + } + } + } + + /* Compute open-loop gain if necessary */ + if (gain) + { + for (j=0;jbest_sum && gain_sum<=max_gain) { + best_sum=sum; + best_cdbk=i; + } + } + + return best_cdbk; +} +#endif + +/** Finds the best quantized 3-tap pitch predictor by analysis by synthesis */ +static spx_word32_t pitch_gain_search_3tap( +const spx_word16_t target[], /* Target vector */ +const spx_coef_t ak[], /* LPCs for this subframe */ +const spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +const spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Excitation */ +const signed char *gain_cdbk, +int gain_cdbk_size, +int pitch, /* Pitch value */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +const spx_word16_t *exc2, +const spx_word16_t *r, +spx_word16_t *new_target, +int *cdbk_index, +int plc_tuning, +spx_word32_t cumul_gain, +int scaledown +) +{ + int i,j; + VARDECL(spx_word16_t *tmp1); + VARDECL(spx_word16_t *e); + spx_word16_t *x[3]; + spx_word32_t corr[3]; + spx_word32_t A[3][3]; + spx_word16_t gain[3]; + spx_word32_t err; + spx_word16_t max_gain=128; + int best_cdbk=0; + + ALLOC(tmp1, 3*nsf, spx_word16_t); + ALLOC(e, nsf, spx_word16_t); + + if (cumul_gain > 262144) + max_gain = 31; + + x[0]=tmp1; + x[1]=tmp1+nsf; + x[2]=tmp1+2*nsf; + + for (j=0;j=0;i--) + { + spx_word16_t e0=exc2[-pitch-1+i]; +#ifdef FIXED_POINT + /* Scale excitation down if needed (avoiding overflow) */ + if (scaledown) + e0 = SHR16(e0,1); +#endif + x[i][0]=MULT16_16_Q14(r[0], e0); + for (j=0;j30) + plc_tuning=30; +#ifdef FIXED_POINT + C[0] = SHL32(C[0],1); + C[1] = SHL32(C[1],1); + C[2] = SHL32(C[2],1); + C[3] = SHL32(C[3],1); + C[4] = SHL32(C[4],1); + C[5] = SHL32(C[5],1); + C[6] = MAC16_32_Q15(C[6],MULT16_16_16(plc_tuning,655),C[6]); + C[7] = MAC16_32_Q15(C[7],MULT16_16_16(plc_tuning,655),C[7]); + C[8] = MAC16_32_Q15(C[8],MULT16_16_16(plc_tuning,655),C[8]); + normalize16(C, C16, 32767, 9); +#else + C[6]*=.5*(1+.02*plc_tuning); + C[7]*=.5*(1+.02*plc_tuning); + C[8]*=.5*(1+.02*plc_tuning); +#endif + + best_cdbk = pitch_gain_search_3tap_vq(gain_cdbk, gain_cdbk_size, C16, max_gain); + +#ifdef FIXED_POINT + gain[0] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4]); + gain[1] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4+1]); + gain[2] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4+2]); + /*printf ("%d %d %d %d\n",gain[0],gain[1],gain[2], best_cdbk);*/ +#else + gain[0] = 0.015625*gain_cdbk[best_cdbk*4] + .5; + gain[1] = 0.015625*gain_cdbk[best_cdbk*4+1]+ .5; + gain[2] = 0.015625*gain_cdbk[best_cdbk*4+2]+ .5; +#endif + *cdbk_index=best_cdbk; + } + + SPEEX_MEMSET(exc, 0, nsf); + for (i=0;i<3;i++) + { + int j; + int tmp1, tmp3; + int pp=pitch+1-i; + tmp1=nsf; + if (tmp1>pp) + tmp1=pp; + for (j=0;jpp+pitch) + tmp3=pp+pitch; + for (j=tmp1;jgain_bits; + gain_cdbk = params->gain_cdbk + 4*gain_cdbk_size*cdbk_offset; + + N=complexity; + if (N>10) + N=10; + if (N<1) + N=1; + + ALLOC(nbest, N, int); + params = (const ltp_params*) par; + + if (endpitch_bits); + speex_bits_pack(bits, 0, params->gain_bits); + SPEEX_MEMSET(exc, 0, nsf); + return start; + } + +#ifdef FIXED_POINT + /* Check if we need to scale everything down in the pitch search to avoid overflows */ + for (i=0;i16383) + { + scaledown=1; + break; + } + } + for (i=-end;i16383) + { + scaledown=1; + break; + } + } +#endif + if (N>end-start+1) + N=end-start+1; + if (end != start) + open_loop_nbest_pitch(sw, start, end, nsf, nbest, NULL, N, stack); + else + nbest[0] = start; + + ALLOC(best_exc, nsf, spx_sig_t); + ALLOC(new_target, nsf, spx_word16_t); + ALLOC(best_target, nsf, spx_word16_t); + + for (i=0;ipitch_bits); + speex_bits_pack(bits, best_gain_index, params->gain_bits); +#ifdef FIXED_POINT + *cumul_gain = MULT16_32_Q13(SHL16(params->gain_cdbk[4*best_gain_index+3],8), MAX32(1024,*cumul_gain)); +#else + *cumul_gain = 0.03125*MAX32(1024,*cumul_gain)*params->gain_cdbk[4*best_gain_index+3]; +#endif + /*printf ("%f\n", cumul_gain);*/ + /*printf ("encode pitch: %d %d\n", best_pitch, best_gain_index);*/ + SPEEX_COPY(exc, best_exc, nsf); + SPEEX_COPY(target, best_target, nsf); +#ifdef FIXED_POINT + /* Scale target back up if needed */ + if (scaledown) + { + for (i=0;igain_bits; + gain_cdbk = params->gain_cdbk + 4*gain_cdbk_size*cdbk_offset; + + pitch = speex_bits_unpack_unsigned(bits, params->pitch_bits); + pitch += start; + gain_index = speex_bits_unpack_unsigned(bits, params->gain_bits); + /*printf ("decode pitch: %d %d\n", pitch, gain_index);*/ +#ifdef FIXED_POINT + gain[0] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4]); + gain[1] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4+1]); + gain[2] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4+2]); +#else + gain[0] = 0.015625*gain_cdbk[gain_index*4]+.5; + gain[1] = 0.015625*gain_cdbk[gain_index*4+1]+.5; + gain[2] = 0.015625*gain_cdbk[gain_index*4+2]+.5; +#endif + + if (count_lost && pitch > subframe_offset) + { + spx_word16_t gain_sum; + if (1) { +#ifdef FIXED_POINT + spx_word16_t tmp = count_lost < 4 ? last_pitch_gain : SHR16(last_pitch_gain,1); + if (tmp>62) + tmp=62; +#else + spx_word16_t tmp = count_lost < 4 ? last_pitch_gain : 0.5 * last_pitch_gain; + if (tmp>.95) + tmp=.95; +#endif + gain_sum = gain_3tap_to_1tap(gain); + + if (gain_sum > tmp) + { + spx_word16_t fact = DIV32_16(SHL32(EXTEND32(tmp),14),gain_sum); + for (i=0;i<3;i++) + gain[i]=MULT16_16_Q14(fact,gain[i]); + } + + } + + } + + *pitch_val = pitch; + gain_val[0]=gain[0]; + gain_val[1]=gain[1]; + gain_val[2]=gain[2]; + gain[0] = SHL16(gain[0],7); + gain[1] = SHL16(gain[1],7); + gain[2] = SHL16(gain[2],7); + SPEEX_MEMSET(exc_out, 0, nsf); + for (i=0;i<3;i++) + { + int j; + int tmp1, tmp3; + int pp=pitch+1-i; + tmp1=nsf; + if (tmp1>pp) + tmp1=pp; + for (j=0;jpp+pitch) + tmp3=pp+pitch; + for (j=tmp1;j63) + pitch_coef=63; +#else + if (pitch_coef>.99) + pitch_coef=.99; +#endif + for (i=0;i63) + pitch_coef=63; +#else + if (pitch_coef>.99) + pitch_coef=.99; +#endif + for (i=0;i +#include "arch.h" + +/** LTP parameters. */ +typedef struct { + const signed char *gain_cdbk; + int gain_bits; + int pitch_bits; +} ltp_params; + +#ifdef FIXED_POINT +#define gain_3tap_to_1tap(g) (ABS(g[1]) + (g[0]>0 ? g[0] : -SHR16(g[0],1)) + (g[2]>0 ? g[2] : -SHR16(g[2],1))) +#else +#define gain_3tap_to_1tap(g) (ABS(g[1]) + (g[0]>0 ? g[0] : -.5*g[0]) + (g[2]>0 ? g[2] : -.5*g[2])) +#endif + +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len); +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack); + +void open_loop_nbest_pitch(spx_word16_t *sw, int start, int end, int len, int *pitch, spx_word16_t *gain, int N, char *stack); + + +/** Finds the best quantized 3-tap pitch predictor by analysis by synthesis */ +int pitch_search_3tap( +spx_word16_t target[], /* Target vector */ +spx_word16_t *sw, +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Overlapping codebook */ +const void *par, +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_word16_t *exc2, +spx_word16_t *r, +int complexity, +int cdbk_offset, +int plc_tuning, +spx_word32_t *cumul_gain +); + +/*Unquantize adaptive codebook and update pitch contribution*/ +void pitch_unquant_3tap( +spx_word16_t exc[], /* Input excitation */ +spx_word32_t exc_out[], /* Output excitation */ +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +const void *par, +int nsf, /* Number of samples in subframe */ +int *pitch_val, +spx_word16_t *gain_val, +SpeexBits *bits, +char *stack, +int lost, +int subframe_offset, +spx_word16_t last_pitch_gain, +int cdbk_offset +); + +/** Forced pitch delay and gain */ +int forced_pitch_quant( +spx_word16_t target[], /* Target vector */ +spx_word16_t *sw, +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Excitation */ +const void *par, +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_word16_t *exc2, +spx_word16_t *r, +int complexity, +int cdbk_offset, +int plc_tuning, +spx_word32_t *cumul_gain +); + +/** Unquantize forced pitch delay and gain */ +void forced_pitch_unquant( +spx_word16_t exc[], /* Input excitation */ +spx_word32_t exc_out[], /* Output excitation */ +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +const void *par, +int nsf, /* Number of samples in subframe */ +int *pitch_val, +spx_word16_t *gain_val, +SpeexBits *bits, +char *stack, +int lost, +int subframe_offset, +spx_word16_t last_pitch_gain, +int cdbk_offset +); diff --git a/Libraries/speex/ltp_arm4.h b/Libraries/speex/ltp_arm4.h new file mode 100644 index 000000000..cdb94e603 --- /dev/null +++ b/Libraries/speex/ltp_arm4.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file ltp_arm4.h + @brief Long-Term Prediction functions (ARM4 version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_INNER_PROD +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len) +{ + spx_word32_t sum1=0,sum2=0; + spx_word16_t *deadx, *deady; + int deadlen, dead1, dead2, dead3, dead4, dead5, dead6; + __asm__ __volatile__ ( + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + ".inner_prod_loop%=:\n" + "\tsub %7, %7, %7\n" + "\tsub %10, %10, %10\n" + + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + + "\tsubs %4, %4, #1\n" + "\tadd %2, %2, %7, asr #5\n" + "\tadd %3, %3, %10, asr #5\n" + "\tbne .inner_prod_loop%=\n" + : "=r" (deadx), "=r" (deady), "+r" (sum1), "+r" (sum2), + "=r" (deadlen), "=r" (dead1), "=r" (dead2), "=r" (dead3), + "=r" (dead4), "=r" (dead5), "=r" (dead6) + : "0" (x), "1" (y), "4" (len>>3) + : "cc" + ); + return (sum1+sum2)>>1; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + int i,j; + for (i=0;i>> 6;\n\t" + "R0 = A0;\n\t" + "%0 = R0;\n\t" + : "=m" (sum) + : "m" (x), "m" (y), "d" (len-1) + : "P0", "P1", "P2", "R0", "R1", "A0", "I0", "I1", "L0", "L1", "R3" + ); + return sum; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + corr += nb_pitch - 1; + __asm__ __volatile__ ( + "P2 = %0;\n\t" + "I0 = P2;\n\t" /* x in I0 */ + "B0 = P2;\n\t" /* x in B0 */ + "R0 = %3;\n\t" /* len in R0 */ + "P3 = %3;\n\t" + "P3 += -2;\n\t" /* len in R0 */ + "P4 = %4;\n\t" /* nb_pitch in R0 */ + "R1 = R0 << 1;\n\t" /* number of bytes in x */ + "L0 = R1;\n\t" + "P0 = %1;\n\t" + + "P1 = %2;\n\t" + "B1 = P1;\n\t" + "L1 = 0;\n\t" /*Disable looping on I1*/ + + "r0 = [I0++];\n\t" + "LOOP pitch%= LC0 = P4 >> 1;\n\t" + "LOOP_BEGIN pitch%=;\n\t" + "I1 = P0;\n\t" + "A1 = A0 = 0;\n\t" + "R1 = [I1++];\n\t" + "LOOP inner_prod%= LC1 = P3 >> 1;\n\t" + "LOOP_BEGIN inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R1.H = W[I1++] || R0 = [I0++];\n\t" + "LOOP_END inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R0 = [I0++];\n\t" + "A0 = A0 >>> 6;\n\t" + "A1 = A1 >>> 6;\n\t" + "R2 = A0, R3 = A1;\n\t" + "[P1--] = r2;\n\t" + "[P1--] = r3;\n\t" + "P0 += 4;\n\t" + "LOOP_END pitch%=;\n\t" + "L0 = 0;\n\t" + : : "m" (_x), "m" (_y), "m" (corr), "m" (len), "m" (nb_pitch) + : "A0", "A1", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "R3", "I0", "I1", "L0", "L1", "B0", "B1", "memory" + ); +} + +#define OVERRIDE_COMPUTE_PITCH_ERROR +static inline spx_word32_t compute_pitch_error(spx_word16_t *C, spx_word16_t *g, spx_word16_t pitch_control) +{ + spx_word32_t sum; + __asm__ __volatile__ + ( + "A0 = 0;\n\t" + + "R0 = W[%1++];\n\t" + "R1.L = %2.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %3.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %2.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%2.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %2.L*%2.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %3.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%4.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS);\n\t" + + "%0 = A0;\n\t" + : "=&D" (sum), "=a" (C) + : "d" (g[0]), "d" (g[1]), "d" (g[2]), "d" (pitch_control), "1" (C) + : "R0", "R1", "R2", "A0" + ); + return sum; +} + +#define OVERRIDE_OPEN_LOOP_NBEST_PITCH +#ifdef OVERRIDE_OPEN_LOOP_NBEST_PITCH +void open_loop_nbest_pitch(spx_word16_t *sw, int start, int end, int len, int *pitch, spx_word16_t *gain, int N, char *stack) +{ + int i,j,k; + VARDECL(spx_word32_t *best_score); + VARDECL(spx_word32_t *best_ener); + spx_word32_t e0; + VARDECL(spx_word32_t *corr); + VARDECL(spx_word32_t *energy); + + ALLOC(best_score, N, spx_word32_t); + ALLOC(best_ener, N, spx_word32_t); + ALLOC(corr, end-start+1, spx_word32_t); + ALLOC(energy, end-start+2, spx_word32_t); + + for (i=0;i>>= 6;\n\t" +" R1 = R1 + R2;\n\t" +" R0 >>>= 6;\n\t" +" R1 = R1 - R0;\n\t" +" R2 = MAX(R1,R3);\n\t" +"eu2: [P0++] = R2;\n\t" + : : "d" (energy), "d" (&sw[-start-1]), "d" (&sw[-start+len-1]), + "a" (end-start) + : "P0", "I1", "I2", "R0", "R1", "R2", "R3" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + pitch_xcorr(sw, sw-end, corr, len, end-start+1, stack); + + /* FIXME: Fixed-point and floating-point code should be merged */ + { + VARDECL(spx_word16_t *corr16); + VARDECL(spx_word16_t *ener16); + ALLOC(corr16, end-start+1, spx_word16_t); + ALLOC(ener16, end-start+1, spx_word16_t); + /* Normalize to 180 so we can square it and it still fits in 16 bits */ + normalize16(corr, corr16, 180, end-start+1); + normalize16(energy, ener16, 180, end-start+1); + + if (N == 1) { + /* optimised asm to handle N==1 case */ + __asm__ __volatile__ + ( +" I0 = %1;\n\t" /* I0: corr16[] */ +" L0 = 0;\n\t" +" I1 = %2;\n\t" /* I1: energy */ +" L1 = 0;\n\t" +" R2 = -1;\n\t" /* R2: best score */ +" R3 = 0;\n\t" /* R3: best energy */ +" P0 = %4;\n\t" /* P0: best pitch */ +" P1 = %4;\n\t" /* P1: counter */ +" LSETUP (sl1, sl2) LC1 = %3;\n\t" +"sl1: R0.L = W [I0++] || R1.L = W [I1++];\n\t" +" R0 = R0.L * R0.L (IS);\n\t" +" R1 += 1;\n\t" +" R4 = R0.L * R3.L;\n\t" +" R5 = R2.L * R1.L;\n\t" +" cc = R5 < R4;\n\t" +" if cc R2 = R0;\n\t" +" if cc R3 = R1;\n\t" +" if cc P0 = P1;\n\t" +"sl2: P1 += 1;\n\t" +" %0 = P0;\n\t" + : "=&d" (pitch[0]) + : "a" (corr16), "a" (ener16), "a" (end+1-start), "d" (start) + : "P0", "P1", "I0", "I1", "R0", "R1", "R2", "R3", "R4", "R5" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + } + else { + for (i=start;i<=end;i++) + { + spx_word16_t tmp = MULT16_16_16(corr16[i-start],corr16[i-start]); + /* Instead of dividing the tmp by the energy, we multiply on the other side */ + if (MULT16_16(tmp,best_ener[N-1])>MULT16_16(best_score[N-1],ADD16(1,ener16[i-start]))) + { + /* We can safely put it last and then check */ + best_score[N-1]=tmp; + best_ener[N-1]=ener16[i-start]+1; + pitch[N-1]=i; + /* Check if it comes in front of others */ + for (j=0;jMULT16_16(best_score[j],ADD16(1,ener16[i-start]))) + { + for (k=N-1;k>j;k--) + { + best_score[k]=best_score[k-1]; + best_ener[k]=best_ener[k-1]; + pitch[k]=pitch[k-1]; + } + best_score[j]=tmp; + best_ener[j]=ener16[i-start]+1; + pitch[j]=i; + break; + } + } + } + } + } + } + + /* Compute open-loop gain */ + if (gain) + { + for (j=0;jbest_sum && gain_sum<=max_gain) ------ (1) + + if (sum>best_sum && !(gain_sum>max_gain)) ------ (2) + + if (max_gain<=gain_sum) { ------ (3) + sum = -VERY_LARGE32; + } + if (best_sum<=sum) + + The blackin cc instructions are all of the form: + + cc = x < y (or cc = x <= y) +*/ +" R1 = B0\n\t" +" R2 = %5\n\t" +" R3 = %6\n\t" +" cc = R2 <= R1;\n\t" +" if cc R0 = R3;\n\t" +" cc = %0 <= R0;\n\t" +" if cc %0 = R0;\n\t" +" if cc %1 = P1;\n\t" + +"pgs2: P1 += 1;\n\t" + + : "=&d" (best_sum), "=&d" (best_cdbk) + : "a" (gain_cdbk), "a" (C16), "a" (gain_cdbk_size), "a" (max_gain), + "b" (-VERY_LARGE32) + : "R0", "R1", "R2", "R3", "R4", "P0", + "P1", "I1", "L1", "A0", "B0" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + return best_cdbk; +} +#endif + diff --git a/Libraries/speex/ltp_sse.h b/Libraries/speex/ltp_sse.h new file mode 100644 index 000000000..bed6eaac9 --- /dev/null +++ b/Libraries/speex/ltp_sse.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file ltp_sse.h + @brief Long-Term Prediction functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#define OVERRIDE_INNER_PROD +float inner_prod(const float *a, const float *b, int len) +{ + int i; + float ret; + __m128 sum = _mm_setzero_ps(); + for (i=0;i<(len>>2);i+=2) + { + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+0), _mm_loadu_ps(b+0))); + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+4), _mm_loadu_ps(b+4))); + a += 8; + b += 8; + } + sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum)); + sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55)); + _mm_store_ss(&ret, sum); + return ret; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const float *_x, const float *_y, float *corr, int len, int nb_pitch, char *stack) +{ + int i, offset; + VARDECL(__m128 *x); + VARDECL(__m128 *y); + int N, L; + N = len>>2; + L = nb_pitch>>2; + ALLOC(x, N, __m128); + ALLOC(y, N+L, __m128); + for (i=0;i=(spx_int32_t)65536) + { + x >>= 16; + r += 16; + } + if (x>=256) + { + x >>= 8; + r += 8; + } + if (x>=16) + { + x >>= 4; + r += 4; + } + if (x>=4) + { + x >>= 2; + r += 2; + } + if (x>=2) + { + r += 1; + } + return r; +} + +static inline spx_int16_t spx_ilog4(spx_uint32_t x) +{ + int r=0; + if (x>=(spx_int32_t)65536) + { + x >>= 16; + r += 8; + } + if (x>=256) + { + x >>= 8; + r += 4; + } + if (x>=16) + { + x >>= 4; + r += 2; + } + if (x>=4) + { + r += 1; + } + return r; +} + +#ifdef FIXED_POINT + +/** Generate a pseudo-random number */ +static inline spx_word16_t speex_rand(spx_word16_t std, spx_int32_t *seed) +{ + spx_word32_t res; + *seed = 1664525 * *seed + 1013904223; + res = MULT16_16(EXTRACT16(SHR32(*seed,16)),std); + return EXTRACT16(PSHR32(SUB32(res, SHR32(res, 3)),14)); +} + +/* sqrt(x) ~= 0.22178 + 1.29227*x - 0.77070*x^2 + 0.25723*x^3 (for .25 < x < 1) */ +/*#define C0 3634 +#define C1 21173 +#define C2 -12627 +#define C3 4215*/ + +/* sqrt(x) ~= 0.22178 + 1.29227*x - 0.77070*x^2 + 0.25659*x^3 (for .25 < x < 1) */ +#define C0 3634 +#define C1 21173 +#define C2 -12627 +#define C3 4204 + +static inline spx_word16_t spx_sqrt(spx_word32_t x) +{ + int k; + spx_word32_t rt; + k = spx_ilog4(x)-6; + x = VSHR32(x, (k<<1)); + rt = ADD16(C0, MULT16_16_Q14(x, ADD16(C1, MULT16_16_Q14(x, ADD16(C2, MULT16_16_Q14(x, (C3))))))); + rt = VSHR32(rt,7-k); + return rt; +} + +/* log(x) ~= -2.18151 + 4.20592*x - 2.88938*x^2 + 0.86535*x^3 (for .5 < x < 1) */ + + +#define A1 16469 +#define A2 2242 +#define A3 1486 + +static inline spx_word16_t spx_acos(spx_word16_t x) +{ + int s=0; + spx_word16_t ret; + spx_word16_t sq; + if (x<0) + { + s=1; + x = NEG16(x); + } + x = SUB16(16384,x); + + x = x >> 1; + sq = MULT16_16_Q13(x, ADD16(A1, MULT16_16_Q13(x, ADD16(A2, MULT16_16_Q13(x, (A3)))))); + ret = spx_sqrt(SHL32(EXTEND32(sq),13)); + + /*ret = spx_sqrt(67108864*(-1.6129e-04 + 2.0104e+00*f + 2.7373e-01*f*f + 1.8136e-01*f*f*f));*/ + if (s) + ret = SUB16(25736,ret); + return ret; +} + + +#define K1 8192 +#define K2 -4096 +#define K3 340 +#define K4 -10 + +static inline spx_word16_t spx_cos(spx_word16_t x) +{ + spx_word16_t x2; + + if (x<12868) + { + x2 = MULT16_16_P13(x,x); + return ADD32(K1, MULT16_16_P13(x2, ADD32(K2, MULT16_16_P13(x2, ADD32(K3, MULT16_16_P13(K4, x2)))))); + } else { + x = SUB16(25736,x); + x2 = MULT16_16_P13(x,x); + return SUB32(-K1, MULT16_16_P13(x2, ADD32(K2, MULT16_16_P13(x2, ADD32(K3, MULT16_16_P13(K4, x2)))))); + } +} + +#define L1 32767 +#define L2 -7651 +#define L3 8277 +#define L4 -626 + +static inline spx_word16_t _spx_cos_pi_2(spx_word16_t x) +{ + spx_word16_t x2; + + x2 = MULT16_16_P15(x,x); + return ADD16(1,MIN16(32766,ADD32(SUB16(L1,x2), MULT16_16_P15(x2, ADD32(L2, MULT16_16_P15(x2, ADD32(L3, MULT16_16_P15(L4, x2)))))))); +} + +static inline spx_word16_t spx_cos_norm(spx_word32_t x) +{ + x = x&0x0001ffff; + if (x>SHL32(EXTEND32(1), 16)) + x = SUB32(SHL32(EXTEND32(1), 17),x); + if (x&0x00007fff) + { + if (x14) + return 0x7fffffff; + else if (integer < -15) + return 0; + frac = SHL16(x-SHL16(integer,11),3); + frac = ADD16(D0, MULT16_16_Q14(frac, ADD16(D1, MULT16_16_Q14(frac, ADD16(D2 , MULT16_16_Q14(D3,frac)))))); + return VSHR32(EXTEND32(frac), -integer-2); +} + +/* Input in Q11 format, output in Q16 */ +static inline spx_word32_t spx_exp(spx_word16_t x) +{ + if (x>21290) + return 0x7fffffff; + else if (x<-21290) + return 0; + else + return spx_exp2(MULT16_16_P14(23637,x)); +} +#define M1 32767 +#define M2 -21 +#define M3 -11943 +#define M4 4936 + +static inline spx_word16_t spx_atan01(spx_word16_t x) +{ + return MULT16_16_P15(x, ADD32(M1, MULT16_16_P15(x, ADD32(M2, MULT16_16_P15(x, ADD32(M3, MULT16_16_P15(M4, x))))))); +} + +#undef M1 +#undef M2 +#undef M3 +#undef M4 + +/* Input in Q15, output in Q14 */ +static inline spx_word16_t spx_atan(spx_word32_t x) +{ + if (x <= 32767) + { + return SHR16(spx_atan01(x),1); + } else { + int e = spx_ilog2(x); + if (e>=29) + return 25736; + x = DIV32_16(SHL32(EXTEND32(32767),29-e), EXTRACT16(SHR32(x, e-14))); + return SUB16(25736, SHR16(spx_atan01(x),1)); + } +} +#else + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#define C1 0.9999932946f +#define C2 -0.4999124376f +#define C3 0.0414877472f +#define C4 -0.0012712095f + + +#define SPX_PI_2 1.5707963268 +static inline spx_word16_t spx_cos(spx_word16_t x) +{ + if (x 32766 ? 32767 : (x))) +#else +#define WORD2INT(x) ((x) < -32767.5f ? -32768 : ((x) > 32766.5f ? 32767 : floor(.5+(x)))) +#endif + +/* If enabled, the AEC will use a foreground filter and a background filter to be more robust to double-talk + and difficult signals in general. The cost is an extra FFT and a matrix-vector multiply */ +#define TWO_PATH + +#ifdef FIXED_POINT +static const spx_float_t MIN_LEAK = {20972, -22}; + +/* Constants for the two-path filter */ +static const spx_float_t VAR1_SMOOTH = {23593, -16}; +static const spx_float_t VAR2_SMOOTH = {23675, -15}; +static const spx_float_t VAR1_UPDATE = {16384, -15}; +static const spx_float_t VAR2_UPDATE = {16384, -16}; +static const spx_float_t VAR_BACKTRACK = {16384, -12}; +#define TOP16(x) ((x)>>16) + +#else + +static const spx_float_t MIN_LEAK = .005f; + +/* Constants for the two-path filter */ +static const spx_float_t VAR1_SMOOTH = .36f; +static const spx_float_t VAR2_SMOOTH = .7225f; +static const spx_float_t VAR1_UPDATE = .5f; +static const spx_float_t VAR2_UPDATE = .25f; +static const spx_float_t VAR_BACKTRACK = 4.f; +#define TOP16(x) (x) +#endif + + +#define PLAYBACK_DELAY 2 + +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *Yout, int len); + + +/** Speex echo cancellation state. */ +struct SpeexEchoState_ { + int frame_size; /**< Number of samples processed each time */ + int window_size; + int M; + int cancel_count; + int adapted; + int saturated; + int screwed_up; + int C; /** Number of input channels (microphones) */ + int K; /** Number of output channels (loudspeakers) */ + spx_int32_t sampling_rate; + spx_word16_t spec_average; + spx_word16_t beta0; + spx_word16_t beta_max; + spx_word32_t sum_adapt; + spx_word16_t leak_estimate; + + spx_word16_t *e; /* scratch */ + spx_word16_t *x; /* Far-end input buffer (2N) */ + spx_word16_t *X; /* Far-end buffer (M+1 frames) in frequency domain */ + spx_word16_t *input; /* scratch */ + spx_word16_t *y; /* scratch */ + spx_word16_t *last_y; + spx_word16_t *Y; /* scratch */ + spx_word16_t *E; + spx_word32_t *PHI; /* scratch */ + spx_word32_t *W; /* (Background) filter weights */ +#ifdef TWO_PATH + spx_word16_t *foreground; /* Foreground filter weights */ + spx_word32_t Davg1; /* 1st recursive average of the residual power difference */ + spx_word32_t Davg2; /* 2nd recursive average of the residual power difference */ + spx_float_t Dvar1; /* Estimated variance of 1st estimator */ + spx_float_t Dvar2; /* Estimated variance of 2nd estimator */ +#endif + spx_word32_t *power; /* Power of the far-end signal */ + spx_float_t *power_1;/* Inverse power of far-end */ + spx_word16_t *wtmp; /* scratch */ +#ifdef FIXED_POINT + spx_word16_t *wtmp2; /* scratch */ +#endif + spx_word32_t *Rf; /* scratch */ + spx_word32_t *Yf; /* scratch */ + spx_word32_t *Xf; /* scratch */ + spx_word32_t *Eh; + spx_word32_t *Yh; + spx_float_t Pey; + spx_float_t Pyy; + spx_word16_t *window; + spx_word16_t *prop; + void *fft_table; + spx_word16_t *memX, *memD, *memE; + spx_word16_t preemph; + spx_word16_t notch_radius; + spx_mem_t *notch_mem; + + /* NOTE: If you only use speex_echo_cancel() and want to save some memory, remove this */ + spx_int16_t *play_buf; + int play_buf_pos; + int play_buf_started; +}; + +static inline void filter_dc_notch16(const spx_int16_t *in, spx_word16_t radius, spx_word16_t *out, int len, spx_mem_t *mem, int stride) +{ + int i; + spx_word16_t den2; +#ifdef FIXED_POINT + den2 = MULT16_16_Q15(radius,radius) + MULT16_16_Q15(QCONST16(.7,15),MULT16_16_Q15(32767-radius,32767-radius)); +#else + den2 = radius*radius + .7*(1-radius)*(1-radius); +#endif + /*printf ("%d %d %d %d %d %d\n", num[0], num[1], num[2], den[0], den[1], den[2]);*/ + for (i=0;i>= 1; + while(len--) + { + spx_word32_t part=0; + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + /* HINT: If you had a 40-bit accumulator, you could shift only at the end */ + sum = ADD32(sum,SHR32(part,6)); + } + return sum; +} + +/** Compute power spectrum of a half-complex (packed) vector */ +static inline void power_spectrum(const spx_word16_t *X, spx_word32_t *ps, int N) +{ + int i, j; + ps[0]=MULT16_16(X[0],X[0]); + for (i=1,j=1;i max_sum) + max_sum = prop[i]; + } + for (i=0;i +static FILE *rFile=NULL, *pFile=NULL, *oFile=NULL; + +static void dump_audio(const spx_int16_t *rec, const spx_int16_t *play, const spx_int16_t *out, int len) +{ + if (!(rFile && pFile && oFile)) + { + speex_fatal("Dump files not open"); + } + fwrite(rec, sizeof(spx_int16_t), len, rFile); + fwrite(play, sizeof(spx_int16_t), len, pFile); + fwrite(out, sizeof(spx_int16_t), len, oFile); +} +#endif + +/** Creates a new echo canceller state */ +EXPORT SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length) +{ + return speex_echo_state_init_mc(frame_size, filter_length, 1, 1); +} + +EXPORT SpeexEchoState *speex_echo_state_init_mc(int frame_size, int filter_length, int nb_mic, int nb_speakers) +{ + int i,N,M, C, K; + SpeexEchoState *st = (SpeexEchoState *)speex_alloc(sizeof(SpeexEchoState)); + + st->K = nb_speakers; + st->C = nb_mic; + C=st->C; + K=st->K; +#ifdef DUMP_ECHO_CANCEL_DATA + if (rFile || pFile || oFile) + speex_fatal("Opening dump files twice"); + rFile = fopen("aec_rec.sw", "wb"); + pFile = fopen("aec_play.sw", "wb"); + oFile = fopen("aec_out.sw", "wb"); +#endif + + st->frame_size = frame_size; + st->window_size = 2*frame_size; + N = st->window_size; + M = st->M = (filter_length+st->frame_size-1)/frame_size; + st->cancel_count=0; + st->sum_adapt = 0; + st->saturated = 0; + st->screwed_up = 0; + /* This is the default sampling rate */ + st->sampling_rate = 8000; + st->spec_average = DIV32_16(SHL32(EXTEND32(st->frame_size), 15), st->sampling_rate); +#ifdef FIXED_POINT + st->beta0 = DIV32_16(SHL32(EXTEND32(st->frame_size), 16), st->sampling_rate); + st->beta_max = DIV32_16(SHL32(EXTEND32(st->frame_size), 14), st->sampling_rate); +#else + st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; + st->beta_max = (.5f*st->frame_size)/st->sampling_rate; +#endif + st->leak_estimate = 0; + + st->fft_table = spx_fft_init(N); + + st->e = (spx_word16_t*)speex_alloc(C*N*sizeof(spx_word16_t)); + st->x = (spx_word16_t*)speex_alloc(K*N*sizeof(spx_word16_t)); + st->input = (spx_word16_t*)speex_alloc(C*st->frame_size*sizeof(spx_word16_t)); + st->y = (spx_word16_t*)speex_alloc(C*N*sizeof(spx_word16_t)); + st->last_y = (spx_word16_t*)speex_alloc(C*N*sizeof(spx_word16_t)); + st->Yf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Rf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Xf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Yh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Eh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + + st->X = (spx_word16_t*)speex_alloc(K*(M+1)*N*sizeof(spx_word16_t)); + st->Y = (spx_word16_t*)speex_alloc(C*N*sizeof(spx_word16_t)); + st->E = (spx_word16_t*)speex_alloc(C*N*sizeof(spx_word16_t)); + st->W = (spx_word32_t*)speex_alloc(C*K*M*N*sizeof(spx_word32_t)); +#ifdef TWO_PATH + st->foreground = (spx_word16_t*)speex_alloc(M*N*C*K*sizeof(spx_word16_t)); +#endif + st->PHI = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->power = (spx_word32_t*)speex_alloc((frame_size+1)*sizeof(spx_word32_t)); + st->power_1 = (spx_float_t*)speex_alloc((frame_size+1)*sizeof(spx_float_t)); + st->window = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->prop = (spx_word16_t*)speex_alloc(M*sizeof(spx_word16_t)); + st->wtmp = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); +#ifdef FIXED_POINT + st->wtmp2 = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + for (i=0;i>1;i++) + { + st->window[i] = (16383-SHL16(spx_cos(DIV32_16(MULT16_16(25736,i<<1),N)),1)); + st->window[N-i-1] = st->window[i]; + } +#else + for (i=0;iwindow[i] = .5-.5*cos(2*M_PI*i/N); +#endif + for (i=0;i<=st->frame_size;i++) + st->power_1[i] = FLOAT_ONE; + for (i=0;iW[i] = 0; + { + spx_word32_t sum = 0; + /* Ratio of ~10 between adaptation rate of first and last block */ + spx_word16_t decay = SHR32(spx_exp(NEG16(DIV32_16(QCONST16(2.4,11),M))),1); + st->prop[0] = QCONST16(.7, 15); + sum = EXTEND32(st->prop[0]); + for (i=1;iprop[i] = MULT16_16_Q15(st->prop[i-1], decay); + sum = ADD32(sum, EXTEND32(st->prop[i])); + } + for (i=M-1;i>=0;i--) + { + st->prop[i] = DIV32(MULT16_16(QCONST16(.8f,15), st->prop[i]),sum); + } + } + + st->memX = (spx_word16_t*)speex_alloc(K*sizeof(spx_word16_t)); + st->memD = (spx_word16_t*)speex_alloc(C*sizeof(spx_word16_t)); + st->memE = (spx_word16_t*)speex_alloc(C*sizeof(spx_word16_t)); + st->preemph = QCONST16(.9,15); + if (st->sampling_rate<12000) + st->notch_radius = QCONST16(.9, 15); + else if (st->sampling_rate<24000) + st->notch_radius = QCONST16(.982, 15); + else + st->notch_radius = QCONST16(.992, 15); + + st->notch_mem = (spx_mem_t*)speex_alloc(2*C*sizeof(spx_mem_t)); + st->adapted = 0; + st->Pey = st->Pyy = FLOAT_ONE; + +#ifdef TWO_PATH + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; +#endif + + st->play_buf = (spx_int16_t*)speex_alloc(K*(PLAYBACK_DELAY+1)*st->frame_size*sizeof(spx_int16_t)); + st->play_buf_pos = PLAYBACK_DELAY*st->frame_size; + st->play_buf_started = 0; + + return st; +} + +/** Resets echo canceller state */ +EXPORT void speex_echo_state_reset(SpeexEchoState *st) +{ + int i, M, N, C, K; + st->cancel_count=0; + st->screwed_up = 0; + N = st->window_size; + M = st->M; + C=st->C; + K=st->K; + for (i=0;iW[i] = 0; +#ifdef TWO_PATH + for (i=0;iforeground[i] = 0; +#endif + for (i=0;iX[i] = 0; + for (i=0;i<=st->frame_size;i++) + { + st->power[i] = 0; + st->power_1[i] = FLOAT_ONE; + st->Eh[i] = 0; + st->Yh[i] = 0; + } + for (i=0;iframe_size;i++) + { + st->last_y[i] = 0; + } + for (i=0;iE[i] = 0; + } + for (i=0;ix[i] = 0; + } + for (i=0;i<2*C;i++) + st->notch_mem[i] = 0; + for (i=0;imemD[i]=st->memE[i]=0; + for (i=0;imemX[i]=0; + + st->saturated = 0; + st->adapted = 0; + st->sum_adapt = 0; + st->Pey = st->Pyy = FLOAT_ONE; +#ifdef TWO_PATH + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; +#endif + for (i=0;i<3*st->frame_size;i++) + st->play_buf[i] = 0; + st->play_buf_pos = PLAYBACK_DELAY*st->frame_size; + st->play_buf_started = 0; + +} + +/** Destroys an echo canceller state */ +EXPORT void speex_echo_state_destroy(SpeexEchoState *st) +{ + spx_fft_destroy(st->fft_table); + + speex_free(st->e); + speex_free(st->x); + speex_free(st->input); + speex_free(st->y); + speex_free(st->last_y); + speex_free(st->Yf); + speex_free(st->Rf); + speex_free(st->Xf); + speex_free(st->Yh); + speex_free(st->Eh); + + speex_free(st->X); + speex_free(st->Y); + speex_free(st->E); + speex_free(st->W); +#ifdef TWO_PATH + speex_free(st->foreground); +#endif + speex_free(st->PHI); + speex_free(st->power); + speex_free(st->power_1); + speex_free(st->window); + speex_free(st->prop); + speex_free(st->wtmp); +#ifdef FIXED_POINT + speex_free(st->wtmp2); +#endif + speex_free(st->memX); + speex_free(st->memD); + speex_free(st->memE); + speex_free(st->notch_mem); + + speex_free(st->play_buf); + speex_free(st); + +#ifdef DUMP_ECHO_CANCEL_DATA + fclose(rFile); + fclose(pFile); + fclose(oFile); + rFile = pFile = oFile = NULL; +#endif +} + +EXPORT void speex_echo_capture(SpeexEchoState *st, const spx_int16_t *rec, spx_int16_t *out) +{ + int i; + /*speex_warning_int("capture with fill level ", st->play_buf_pos/st->frame_size);*/ + st->play_buf_started = 1; + if (st->play_buf_pos>=st->frame_size) + { + speex_echo_cancellation(st, rec, st->play_buf, out); + st->play_buf_pos -= st->frame_size; + for (i=0;iplay_buf_pos;i++) + st->play_buf[i] = st->play_buf[i+st->frame_size]; + } else { + speex_warning("No playback frame available (your application is buggy and/or got xruns)"); + if (st->play_buf_pos!=0) + { + speex_warning("internal playback buffer corruption?"); + st->play_buf_pos = 0; + } + for (i=0;iframe_size;i++) + out[i] = rec[i]; + } +} + +EXPORT void speex_echo_playback(SpeexEchoState *st, const spx_int16_t *play) +{ + /*speex_warning_int("playback with fill level ", st->play_buf_pos/st->frame_size);*/ + if (!st->play_buf_started) + { + speex_warning("discarded first playback frame"); + return; + } + if (st->play_buf_pos<=PLAYBACK_DELAY*st->frame_size) + { + int i; + for (i=0;iframe_size;i++) + st->play_buf[st->play_buf_pos+i] = play[i]; + st->play_buf_pos += st->frame_size; + if (st->play_buf_pos <= (PLAYBACK_DELAY-1)*st->frame_size) + { + speex_warning("Auto-filling the buffer (your application is buggy and/or got xruns)"); + for (i=0;iframe_size;i++) + st->play_buf[st->play_buf_pos+i] = play[i]; + st->play_buf_pos += st->frame_size; + } + } else { + speex_warning("Had to discard a playback frame (your application is buggy and/or got xruns)"); + } +} + +/** Performs echo cancellation on a frame (deprecated, last arg now ignored) */ +EXPORT void speex_echo_cancel(SpeexEchoState *st, const spx_int16_t *in, const spx_int16_t *far_end, spx_int16_t *out, spx_int32_t *Yout) +{ + speex_echo_cancellation(st, in, far_end, out); +} + +/** Performs echo cancellation on a frame */ +EXPORT void speex_echo_cancellation(SpeexEchoState *st, const spx_int16_t *in, const spx_int16_t *far_end, spx_int16_t *out) +{ + int i,j, chan, speak; + int N,M, C, K; + spx_word32_t Syy,See,Sxx,Sdd, Sff; +#ifdef TWO_PATH + spx_word32_t Dbf; + int update_foreground; +#endif + spx_word32_t Sey; + spx_word16_t ss, ss_1; + spx_float_t Pey = FLOAT_ONE, Pyy=FLOAT_ONE; + spx_float_t alpha, alpha_1; + spx_word16_t RER; + spx_word32_t tmp32; + + N = st->window_size; + M = st->M; + C = st->C; + K = st->K; + + st->cancel_count++; +#ifdef FIXED_POINT + ss=DIV32_16(11469,M); + ss_1 = SUB16(32767,ss); +#else + ss=.35/M; + ss_1 = 1-ss; +#endif + + for (chan = 0; chan < C; chan++) + { + /* Apply a notch filter to make sure DC doesn't end up causing problems */ + filter_dc_notch16(in+chan, st->notch_radius, st->input+chan*st->frame_size, st->frame_size, st->notch_mem+2*chan, C); + /* Copy input data to buffer and apply pre-emphasis */ + /* Copy input data to buffer */ + for (i=0;iframe_size;i++) + { + spx_word32_t tmp32; + /* FIXME: This core has changed a bit, need to merge properly */ + tmp32 = SUB32(EXTEND32(st->input[chan*st->frame_size+i]), EXTEND32(MULT16_16_P15(st->preemph, st->memD[chan]))); +#ifdef FIXED_POINT + if (tmp32 > 32767) + { + tmp32 = 32767; + if (st->saturated == 0) + st->saturated = 1; + } + if (tmp32 < -32767) + { + tmp32 = -32767; + if (st->saturated == 0) + st->saturated = 1; + } +#endif + st->memD[chan] = st->input[chan*st->frame_size+i]; + st->input[chan*st->frame_size+i] = EXTRACT16(tmp32); + } + } + + for (speak = 0; speak < K; speak++) + { + for (i=0;iframe_size;i++) + { + spx_word32_t tmp32; + st->x[speak*N+i] = st->x[speak*N+i+st->frame_size]; + tmp32 = SUB32(EXTEND32(far_end[i*K+speak]), EXTEND32(MULT16_16_P15(st->preemph, st->memX[speak]))); +#ifdef FIXED_POINT + /*FIXME: If saturation occurs here, we need to freeze adaptation for M frames (not just one) */ + if (tmp32 > 32767) + { + tmp32 = 32767; + st->saturated = M+1; + } + if (tmp32 < -32767) + { + tmp32 = -32767; + st->saturated = M+1; + } +#endif + st->x[speak*N+i+st->frame_size] = EXTRACT16(tmp32); + st->memX[speak] = far_end[i*K+speak]; + } + } + + for (speak = 0; speak < K; speak++) + { + /* Shift memory: this could be optimized eventually*/ + for (j=M-1;j>=0;j--) + { + for (i=0;iX[(j+1)*N*K+speak*N+i] = st->X[j*N*K+speak*N+i]; + } + /* Convert x (echo input) to frequency domain */ + spx_fft(st->fft_table, st->x+speak*N, &st->X[speak*N]); + } + + Sxx = 0; + for (speak = 0; speak < K; speak++) + { + Sxx += mdf_inner_prod(st->x+speak*N+st->frame_size, st->x+speak*N+st->frame_size, st->frame_size); + power_spectrum_accum(st->X+speak*N, st->Xf, N); + } + + Sff = 0; + for (chan = 0; chan < C; chan++) + { +#ifdef TWO_PATH + /* Compute foreground filter */ + spectral_mul_accum16(st->X, st->foreground+chan*N*K*M, st->Y+chan*N, N, M*K); + spx_ifft(st->fft_table, st->Y+chan*N, st->e+chan*N); + for (i=0;iframe_size;i++) + st->e[chan*N+i] = SUB16(st->input[chan*st->frame_size+i], st->e[chan*N+i+st->frame_size]); + Sff += mdf_inner_prod(st->e+chan*N, st->e+chan*N, st->frame_size); +#endif + } + + /* Adjust proportional adaption rate */ + /* FIXME: Adjust that for C, K*/ + if (st->adapted) + mdf_adjust_prop (st->W, N, M, C*K, st->prop); + /* Compute weight gradient */ + if (st->saturated == 0) + { + for (chan = 0; chan < C; chan++) + { + for (speak = 0; speak < K; speak++) + { + for (j=M-1;j>=0;j--) + { + weighted_spectral_mul_conj(st->power_1, FLOAT_SHL(PSEUDOFLOAT(st->prop[j]),-15), &st->X[(j+1)*N*K+speak*N], st->E+chan*N, st->PHI, N); + for (i=0;iW[chan*N*K*M + j*N*K + speak*N + i] += st->PHI[i]; + } + } + } + } else { + st->saturated--; + } + + /* FIXME: MC conversion required */ + /* Update weight to prevent circular convolution (MDF / AUMDF) */ + for (chan = 0; chan < C; chan++) + { + for (speak = 0; speak < K; speak++) + { + for (j=0;jcancel_count%(M-1) == j-1) + { +#ifdef FIXED_POINT + for (i=0;iwtmp2[i] = EXTRACT16(PSHR32(st->W[chan*N*K*M + j*N*K + speak*N + i],NORMALIZE_SCALEDOWN+16)); + spx_ifft(st->fft_table, st->wtmp2, st->wtmp); + for (i=0;iframe_size;i++) + { + st->wtmp[i]=0; + } + for (i=st->frame_size;iwtmp[i]=SHL16(st->wtmp[i],NORMALIZE_SCALEUP); + } + spx_fft(st->fft_table, st->wtmp, st->wtmp2); + /* The "-1" in the shift is a sort of kludge that trades less efficient update speed for decrease noise */ + for (i=0;iW[chan*N*K*M + j*N*K + speak*N + i] -= SHL32(EXTEND32(st->wtmp2[i]),16+NORMALIZE_SCALEDOWN-NORMALIZE_SCALEUP-1); +#else + spx_ifft(st->fft_table, &st->W[chan*N*K*M + j*N*K + speak*N], st->wtmp); + for (i=st->frame_size;iwtmp[i]=0; + } + spx_fft(st->fft_table, st->wtmp, &st->W[chan*N*K*M + j*N*K + speak*N]); +#endif + } + } + } + } + + /* So we can use power_spectrum_accum */ + for (i=0;i<=st->frame_size;i++) + st->Rf[i] = st->Yf[i] = st->Xf[i] = 0; + + Dbf = 0; + See = 0; +#ifdef TWO_PATH + /* Difference in response, this is used to estimate the variance of our residual power estimate */ + for (chan = 0; chan < C; chan++) + { + spectral_mul_accum(st->X, st->W+chan*N*K*M, st->Y+chan*N, N, M*K); + spx_ifft(st->fft_table, st->Y+chan*N, st->y+chan*N); + for (i=0;iframe_size;i++) + st->e[chan*N+i] = SUB16(st->e[chan*N+i+st->frame_size], st->y[chan*N+i+st->frame_size]); + Dbf += 10+mdf_inner_prod(st->e+chan*N, st->e+chan*N, st->frame_size); + for (i=0;iframe_size;i++) + st->e[chan*N+i] = SUB16(st->input[chan*st->frame_size+i], st->y[chan*N+i+st->frame_size]); + See += mdf_inner_prod(st->e+chan*N, st->e+chan*N, st->frame_size); + } +#endif + +#ifndef TWO_PATH + Sff = See; +#endif + +#ifdef TWO_PATH + /* Logic for updating the foreground filter */ + + /* For two time windows, compute the mean of the energy difference, as well as the variance */ + st->Davg1 = ADD32(MULT16_32_Q15(QCONST16(.6f,15),st->Davg1), MULT16_32_Q15(QCONST16(.4f,15),SUB32(Sff,See))); + st->Davg2 = ADD32(MULT16_32_Q15(QCONST16(.85f,15),st->Davg2), MULT16_32_Q15(QCONST16(.15f,15),SUB32(Sff,See))); + st->Dvar1 = FLOAT_ADD(FLOAT_MULT(VAR1_SMOOTH, st->Dvar1), FLOAT_MUL32U(MULT16_32_Q15(QCONST16(.4f,15),Sff), MULT16_32_Q15(QCONST16(.4f,15),Dbf))); + st->Dvar2 = FLOAT_ADD(FLOAT_MULT(VAR2_SMOOTH, st->Dvar2), FLOAT_MUL32U(MULT16_32_Q15(QCONST16(.15f,15),Sff), MULT16_32_Q15(QCONST16(.15f,15),Dbf))); + + /* Equivalent float code: + st->Davg1 = .6*st->Davg1 + .4*(Sff-See); + st->Davg2 = .85*st->Davg2 + .15*(Sff-See); + st->Dvar1 = .36*st->Dvar1 + .16*Sff*Dbf; + st->Dvar2 = .7225*st->Dvar2 + .0225*Sff*Dbf; + */ + + update_foreground = 0; + /* Check if we have a statistically significant reduction in the residual echo */ + /* Note that this is *not* Gaussian, so we need to be careful about the longer tail */ + if (FLOAT_GT(FLOAT_MUL32U(SUB32(Sff,See),ABS32(SUB32(Sff,See))), FLOAT_MUL32U(Sff,Dbf))) + update_foreground = 1; + else if (FLOAT_GT(FLOAT_MUL32U(st->Davg1, ABS32(st->Davg1)), FLOAT_MULT(VAR1_UPDATE,(st->Dvar1)))) + update_foreground = 1; + else if (FLOAT_GT(FLOAT_MUL32U(st->Davg2, ABS32(st->Davg2)), FLOAT_MULT(VAR2_UPDATE,(st->Dvar2)))) + update_foreground = 1; + + /* Do we update? */ + if (update_foreground) + { + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; + /* Copy background filter to foreground filter */ + for (i=0;iforeground[i] = EXTRACT16(PSHR32(st->W[i],16)); + /* Apply a smooth transition so as to not introduce blocking artifacts */ + for (chan = 0; chan < C; chan++) + for (i=0;iframe_size;i++) + st->e[chan*N+i+st->frame_size] = MULT16_16_Q15(st->window[i+st->frame_size],st->e[chan*N+i+st->frame_size]) + MULT16_16_Q15(st->window[i],st->y[chan*N+i+st->frame_size]); + } else { + int reset_background=0; + /* Otherwise, check if the background filter is significantly worse */ + if (FLOAT_GT(FLOAT_MUL32U(NEG32(SUB32(Sff,See)),ABS32(SUB32(Sff,See))), FLOAT_MULT(VAR_BACKTRACK,FLOAT_MUL32U(Sff,Dbf)))) + reset_background = 1; + if (FLOAT_GT(FLOAT_MUL32U(NEG32(st->Davg1), ABS32(st->Davg1)), FLOAT_MULT(VAR_BACKTRACK,st->Dvar1))) + reset_background = 1; + if (FLOAT_GT(FLOAT_MUL32U(NEG32(st->Davg2), ABS32(st->Davg2)), FLOAT_MULT(VAR_BACKTRACK,st->Dvar2))) + reset_background = 1; + if (reset_background) + { + /* Copy foreground filter to background filter */ + for (i=0;iW[i] = SHL32(EXTEND32(st->foreground[i]),16); + /* We also need to copy the output so as to get correct adaptation */ + for (chan = 0; chan < C; chan++) + { + for (i=0;iframe_size;i++) + st->y[chan*N+i+st->frame_size] = st->e[chan*N+i+st->frame_size]; + for (i=0;iframe_size;i++) + st->e[chan*N+i] = SUB16(st->input[chan*st->frame_size+i], st->y[chan*N+i+st->frame_size]); + } + See = Sff; + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; + } + } +#endif + + Sey = Syy = Sdd = 0; + for (chan = 0; chan < C; chan++) + { + /* Compute error signal (for the output with de-emphasis) */ + for (i=0;iframe_size;i++) + { + spx_word32_t tmp_out; +#ifdef TWO_PATH + tmp_out = SUB32(EXTEND32(st->input[chan*st->frame_size+i]), EXTEND32(st->e[chan*N+i+st->frame_size])); +#else + tmp_out = SUB32(EXTEND32(st->input[chan*st->frame_size+i]), EXTEND32(st->y[chan*N+i+st->frame_size])); +#endif + tmp_out = ADD32(tmp_out, EXTEND32(MULT16_16_P15(st->preemph, st->memE[chan]))); + /* This is an arbitrary test for saturation in the microphone signal */ + if (in[i*C+chan] <= -32000 || in[i*C+chan] >= 32000) + { + if (st->saturated == 0) + st->saturated = 1; + } + out[i*C+chan] = WORD2INT(tmp_out); + st->memE[chan] = tmp_out; + } + +#ifdef DUMP_ECHO_CANCEL_DATA + dump_audio(in, far_end, out, st->frame_size); +#endif + + /* Compute error signal (filter update version) */ + for (i=0;iframe_size;i++) + { + st->e[chan*N+i+st->frame_size] = st->e[chan*N+i]; + st->e[chan*N+i] = 0; + } + + /* Compute a bunch of correlations */ + /* FIXME: bad merge */ + Sey += mdf_inner_prod(st->e+chan*N+st->frame_size, st->y+chan*N+st->frame_size, st->frame_size); + Syy += mdf_inner_prod(st->y+chan*N+st->frame_size, st->y+chan*N+st->frame_size, st->frame_size); + Sdd += mdf_inner_prod(st->input+chan*st->frame_size, st->input+chan*st->frame_size, st->frame_size); + + /* Convert error to frequency domain */ + spx_fft(st->fft_table, st->e+chan*N, st->E+chan*N); + for (i=0;iframe_size;i++) + st->y[i+chan*N] = 0; + spx_fft(st->fft_table, st->y+chan*N, st->Y+chan*N); + + /* Compute power spectrum of echo (X), error (E) and filter response (Y) */ + power_spectrum_accum(st->E+chan*N, st->Rf, N); + power_spectrum_accum(st->Y+chan*N, st->Yf, N); + + } + + /*printf ("%f %f %f %f\n", Sff, See, Syy, Sdd, st->update_cond);*/ + + /* Do some sanity check */ + if (!(Syy>=0 && Sxx>=0 && See >= 0) +#ifndef FIXED_POINT + || !(Sff < N*1e9 && Syy < N*1e9 && Sxx < N*1e9) +#endif + ) + { + /* Things have gone really bad */ + st->screwed_up += 50; + for (i=0;iframe_size*C;i++) + out[i] = 0; + } else if (SHR32(Sff, 2) > ADD32(Sdd, SHR32(MULT16_16(N, 10000),6))) + { + /* AEC seems to add lots of echo instead of removing it, let's see if it will improve */ + st->screwed_up++; + } else { + /* Everything's fine */ + st->screwed_up=0; + } + if (st->screwed_up>=50) + { + speex_warning("The echo canceller started acting funny and got slapped (reset). It swears it will behave now."); + speex_echo_state_reset(st); + return; + } + + /* Add a small noise floor to make sure not to have problems when dividing */ + See = MAX32(See, SHR32(MULT16_16(N, 100),6)); + + for (speak = 0; speak < K; speak++) + { + Sxx += mdf_inner_prod(st->x+speak*N+st->frame_size, st->x+speak*N+st->frame_size, st->frame_size); + power_spectrum_accum(st->X+speak*N, st->Xf, N); + } + + + /* Smooth far end energy estimate over time */ + for (j=0;j<=st->frame_size;j++) + st->power[j] = MULT16_32_Q15(ss_1,st->power[j]) + 1 + MULT16_32_Q15(ss,st->Xf[j]); + + /* Compute filtered spectra and (cross-)correlations */ + for (j=st->frame_size;j>=0;j--) + { + spx_float_t Eh, Yh; + Eh = PSEUDOFLOAT(st->Rf[j] - st->Eh[j]); + Yh = PSEUDOFLOAT(st->Yf[j] - st->Yh[j]); + Pey = FLOAT_ADD(Pey,FLOAT_MULT(Eh,Yh)); + Pyy = FLOAT_ADD(Pyy,FLOAT_MULT(Yh,Yh)); +#ifdef FIXED_POINT + st->Eh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Eh[j]), st->spec_average, st->Rf[j]); + st->Yh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Yh[j]), st->spec_average, st->Yf[j]); +#else + st->Eh[j] = (1-st->spec_average)*st->Eh[j] + st->spec_average*st->Rf[j]; + st->Yh[j] = (1-st->spec_average)*st->Yh[j] + st->spec_average*st->Yf[j]; +#endif + } + + Pyy = FLOAT_SQRT(Pyy); + Pey = FLOAT_DIVU(Pey,Pyy); + + /* Compute correlation updatete rate */ + tmp32 = MULT16_32_Q15(st->beta0,Syy); + if (tmp32 > MULT16_32_Q15(st->beta_max,See)) + tmp32 = MULT16_32_Q15(st->beta_max,See); + alpha = FLOAT_DIV32(tmp32, See); + alpha_1 = FLOAT_SUB(FLOAT_ONE, alpha); + /* Update correlations (recursive average) */ + st->Pey = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pey) , FLOAT_MULT(alpha,Pey)); + st->Pyy = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pyy) , FLOAT_MULT(alpha,Pyy)); + if (FLOAT_LT(st->Pyy, FLOAT_ONE)) + st->Pyy = FLOAT_ONE; + /* We don't really hope to get better than 33 dB (MIN_LEAK-3dB) attenuation anyway */ + if (FLOAT_LT(st->Pey, FLOAT_MULT(MIN_LEAK,st->Pyy))) + st->Pey = FLOAT_MULT(MIN_LEAK,st->Pyy); + if (FLOAT_GT(st->Pey, st->Pyy)) + st->Pey = st->Pyy; + /* leak_estimate is the linear regression result */ + st->leak_estimate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIVU(st->Pey, st->Pyy),14)); + /* This looks like a stupid bug, but it's right (because we convert from Q14 to Q15) */ + if (st->leak_estimate > 16383) + st->leak_estimate = 32767; + else + st->leak_estimate = SHL16(st->leak_estimate,1); + /*printf ("%f\n", st->leak_estimate);*/ + + /* Compute Residual to Error Ratio */ +#ifdef FIXED_POINT + tmp32 = MULT16_32_Q15(st->leak_estimate,Syy); + tmp32 = ADD32(SHR32(Sxx,13), ADD32(tmp32, SHL32(tmp32,1))); + /* Check for y in e (lower bound on RER) */ + { + spx_float_t bound = PSEUDOFLOAT(Sey); + bound = FLOAT_DIVU(FLOAT_MULT(bound, bound), PSEUDOFLOAT(ADD32(1,Syy))); + if (FLOAT_GT(bound, PSEUDOFLOAT(See))) + tmp32 = See; + else if (tmp32 < FLOAT_EXTRACT32(bound)) + tmp32 = FLOAT_EXTRACT32(bound); + } + if (tmp32 > SHR32(See,1)) + tmp32 = SHR32(See,1); + RER = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(tmp32,See),15)); +#else + RER = (.0001*Sxx + 3.*MULT16_32_Q15(st->leak_estimate,Syy)) / See; + /* Check for y in e (lower bound on RER) */ + if (RER < Sey*Sey/(1+See*Syy)) + RER = Sey*Sey/(1+See*Syy); + if (RER > .5) + RER = .5; +#endif + + /* We consider that the filter has had minimal adaptation if the following is true*/ + if (!st->adapted && st->sum_adapt > SHL32(EXTEND32(M),15) && MULT16_32_Q15(st->leak_estimate,Syy) > MULT16_32_Q15(QCONST16(.03f,15),Syy)) + { + st->adapted = 1; + } + + if (st->adapted) + { + /* Normal learning rate calculation once we're past the minimal adaptation phase */ + for (i=0;i<=st->frame_size;i++) + { + spx_word32_t r, e; + /* Compute frequency-domain adaptation mask */ + r = MULT16_32_Q15(st->leak_estimate,SHL32(st->Yf[i],3)); + e = SHL32(st->Rf[i],3)+1; +#ifdef FIXED_POINT + if (r>SHR32(e,1)) + r = SHR32(e,1); +#else + if (r>.5*e) + r = .5*e; +#endif + r = MULT16_32_Q15(QCONST16(.7,15),r) + MULT16_32_Q15(QCONST16(.3,15),(spx_word32_t)(MULT16_32_Q15(RER,e))); + /*st->power_1[i] = adapt_rate*r/(e*(1+st->power[i]));*/ + st->power_1[i] = FLOAT_SHL(FLOAT_DIV32_FLOAT(r,FLOAT_MUL32U(e,st->power[i]+10)),WEIGHT_SHIFT+16); + } + } else { + /* Temporary adaption rate if filter is not yet adapted enough */ + spx_word16_t adapt_rate=0; + + if (Sxx > SHR32(MULT16_16(N, 1000),6)) + { + tmp32 = MULT16_32_Q15(QCONST16(.25f, 15), Sxx); +#ifdef FIXED_POINT + if (tmp32 > SHR32(See,2)) + tmp32 = SHR32(See,2); +#else + if (tmp32 > .25*See) + tmp32 = .25*See; +#endif + adapt_rate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(tmp32, See),15)); + } + for (i=0;i<=st->frame_size;i++) + st->power_1[i] = FLOAT_SHL(FLOAT_DIV32(EXTEND32(adapt_rate),ADD32(st->power[i],10)),WEIGHT_SHIFT+1); + + + /* How much have we adapted so far? */ + st->sum_adapt = ADD32(st->sum_adapt,adapt_rate); + } + + /* FIXME: MC conversion required */ + for (i=0;iframe_size;i++) + st->last_y[i] = st->last_y[st->frame_size+i]; + if (st->adapted) + { + /* If the filter is adapted, take the filtered echo */ + for (i=0;iframe_size;i++) + st->last_y[st->frame_size+i] = in[i]-out[i]; + } else { + /* If filter isn't adapted yet, all we can do is take the far end signal directly */ + /* moved earlier: for (i=0;ilast_y[i] = st->x[i];*/ + } + +} + +/* Compute spectrum of estimated echo for use in an echo post-filter */ +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *residual_echo, int len) +{ + int i; + spx_word16_t leak2; + int N; + + N = st->window_size; + + /* Apply hanning window (should pre-compute it)*/ + for (i=0;iy[i] = MULT16_16_Q15(st->window[i],st->last_y[i]); + + /* Compute power spectrum of the echo */ + spx_fft(st->fft_table, st->y, st->Y); + power_spectrum(st->Y, residual_echo, N); + +#ifdef FIXED_POINT + if (st->leak_estimate > 16383) + leak2 = 32767; + else + leak2 = SHL16(st->leak_estimate, 1); +#else + if (st->leak_estimate>.5) + leak2 = 1; + else + leak2 = 2*st->leak_estimate; +#endif + /* Estimate residual echo */ + for (i=0;i<=st->frame_size;i++) + residual_echo[i] = (spx_int32_t)MULT16_32_Q15(leak2,residual_echo[i]); + +} + +EXPORT int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr) +{ + switch(request) + { + + case SPEEX_ECHO_GET_FRAME_SIZE: + (*(int*)ptr) = st->frame_size; + break; + case SPEEX_ECHO_SET_SAMPLING_RATE: + st->sampling_rate = (*(int*)ptr); + st->spec_average = DIV32_16(SHL32(EXTEND32(st->frame_size), 15), st->sampling_rate); +#ifdef FIXED_POINT + st->beta0 = DIV32_16(SHL32(EXTEND32(st->frame_size), 16), st->sampling_rate); + st->beta_max = DIV32_16(SHL32(EXTEND32(st->frame_size), 14), st->sampling_rate); +#else + st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; + st->beta_max = (.5f*st->frame_size)/st->sampling_rate; +#endif + if (st->sampling_rate<12000) + st->notch_radius = QCONST16(.9, 15); + else if (st->sampling_rate<24000) + st->notch_radius = QCONST16(.982, 15); + else + st->notch_radius = QCONST16(.992, 15); + break; + case SPEEX_ECHO_GET_SAMPLING_RATE: + (*(int*)ptr) = st->sampling_rate; + break; + case SPEEX_ECHO_GET_IMPULSE_RESPONSE_SIZE: + /*FIXME: Implement this for multiple channels */ + *((spx_int32_t *)ptr) = st->M * st->frame_size; + break; + case SPEEX_ECHO_GET_IMPULSE_RESPONSE: + { + int M = st->M, N = st->window_size, n = st->frame_size, i, j; + spx_int32_t *filt = (spx_int32_t *) ptr; + for(j=0;jwtmp2[i] = EXTRACT16(PSHR32(st->W[j*N+i],16+NORMALIZE_SCALEDOWN)); + spx_ifft(st->fft_table, st->wtmp2, st->wtmp); +#else + spx_ifft(st->fft_table, &st->W[j*N], st->wtmp); +#endif + for(i=0;iwtmp[i]), WEIGHT_SHIFT-NORMALIZE_SCALEDOWN); + } + } + break; + default: + speex_warning_int("Unknown speex_echo_ctl request: ", request); + return -1; + } + return 0; +} diff --git a/Libraries/speex/misc_bfin.h b/Libraries/speex/misc_bfin.h new file mode 100644 index 000000000..77b082c05 --- /dev/null +++ b/Libraries/speex/misc_bfin.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file misc_bfin.h + @author Jean-Marc Valin + @brief Various compatibility routines for Speex (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_SPEEX_MOVE +void *speex_move (void *dest, void *src, int n) +{ + __asm__ __volatile__ + ( + "L0 = 0;\n\t" + "I0 = %0;\n\t" + "R0 = [I0++];\n\t" + "LOOP move%= LC0 = %2;\n\t" + "LOOP_BEGIN move%=;\n\t" + "[%1++] = R0 || R0 = [I0++];\n\t" + "LOOP_END move%=;\n\t" + "[%1++] = R0;\n\t" + : "=a" (src), "=a" (dest) + : "a" ((n>>2)-1), "0" (src), "1" (dest) + : "R0", "I0", "L0", "memory" + ); + return dest; +} diff --git a/Libraries/speex/modes.c b/Libraries/speex/modes.c new file mode 100644 index 000000000..e10a32e8e --- /dev/null +++ b/Libraries/speex/modes.c @@ -0,0 +1,366 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: modes.c + + Describes the different modes of the codec + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "sb_celp.h" +#include "nb_celp.h" +#include "vbr.h" +#include "arch.h" +#include + +#ifndef NULL +#define NULL 0 +#endif + + +/* Extern declarations for all codebooks we use here */ +extern const signed char gain_cdbk_nb[]; +extern const signed char gain_cdbk_lbr[]; +extern const signed char exc_5_256_table[]; +extern const signed char exc_5_64_table[]; +extern const signed char exc_8_128_table[]; +extern const signed char exc_10_32_table[]; +extern const signed char exc_10_16_table[]; +extern const signed char exc_20_32_table[]; + + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_nb = { + gain_cdbk_nb, + 7, + 7 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_vlbr = { + gain_cdbk_lbr, + 5, + 0 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_lbr = { + gain_cdbk_lbr, + 5, + 7 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_med = { + gain_cdbk_lbr, + 5, + 7 +}; + +/* Split-VQ innovation parameters for very low bit-rate narrowband */ +static const split_cb_params split_cb_nb_vlbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + exc_10_16_table, /*shape_cb*/ + 4, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters for very low bit-rate narrowband */ +static const split_cb_params split_cb_nb_ulbr = { + 20, /*subvect_size*/ + 2, /*nb_subvect*/ + exc_20_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters for low bit-rate narrowband */ +static const split_cb_params split_cb_nb_lbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + exc_10_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + + +/* Split-VQ innovation parameters narrowband */ +static const split_cb_params split_cb_nb = { + 5, /*subvect_size*/ + 8, /*nb_subvect*/ + exc_5_64_table, /*shape_cb*/ + 6, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters narrowband */ +static const split_cb_params split_cb_nb_med = { + 8, /*subvect_size*/ + 5, /*nb_subvect*/ + exc_8_128_table, /*shape_cb*/ + 7, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation for low-band wideband */ +static const split_cb_params split_cb_sb = { + 5, /*subvect_size*/ + 8, /*nb_subvect*/ + exc_5_256_table, /*shape_cb*/ + 8, /*shape_bits*/ + 0, +}; + + + +/* 2150 bps "vocoder-like" mode for comfort noise */ +static const SpeexSubmode nb_submode1 = { + 0, + 1, + 0, + 0, + /* LSP quantization */ + lsp_quant_lbr, + lsp_unquant_lbr, + /* No pitch quantization */ + forced_pitch_quant, + forced_pitch_unquant, + NULL, + /* No innovation quantization (noise only) */ + noise_codebook_quant, + noise_codebook_unquant, + NULL, + -1, + 43 +}; + +/* 3.95 kbps very low bit-rate mode */ +static const SpeexSubmode nb_submode8 = { + 0, + 1, + 0, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*No pitch quantization*/ + forced_pitch_quant, + forced_pitch_unquant, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_ulbr, + QCONST16(.5,15), + 79 +}; + +/* 5.95 kbps very low bit-rate mode */ +static const SpeexSubmode nb_submode2 = { + 0, + 0, + 0, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*No pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_vlbr, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_vlbr, + QCONST16(.6,15), + 119 +}; + +/* 8 kbps low bit-rate mode */ +static const SpeexSubmode nb_submode3 = { + -1, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_lbr, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_lbr, + QCONST16(.55,15), + 160 +}; + +/* 11 kbps medium bit-rate mode */ +static const SpeexSubmode nb_submode4 = { + -1, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_med, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_med, + QCONST16(.45,15), + 220 +}; + +/* 15 kbps high bit-rate mode */ +static const SpeexSubmode nb_submode5 = { + -1, + 0, + 3, + 0, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb, + QCONST16(.3,15), + 300 +}; + +/* 18.2 high bit-rate mode */ +static const SpeexSubmode nb_submode6 = { + -1, + 0, + 3, + 0, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_sb, + QCONST16(.2,15), + 364 +}; + +/* 24.6 kbps high bit-rate mode */ +static const SpeexSubmode nb_submode7 = { + -1, + 0, + 3, + 1, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb, + QCONST16(.1,15), + 492 +}; + + +/* Default mode for narrowband */ +static const SpeexNBMode nb_mode = { + 160, /*frameSize*/ + 40, /*subframeSize*/ + 10, /*lpcSize*/ + 17, /*pitchStart*/ + 144, /*pitchEnd*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + {NULL, &nb_submode1, &nb_submode2, &nb_submode3, &nb_submode4, &nb_submode5, &nb_submode6, &nb_submode7, + &nb_submode8, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + 5, + {1, 8, 2, 3, 3, 4, 4, 5, 5, 6, 7} +}; + + +/* Default mode for narrowband */ +EXPORT const SpeexMode speex_nb_mode = { + &nb_mode, + nb_mode_query, + "narrowband", + 0, + 4, + &nb_encoder_init, + &nb_encoder_destroy, + &nb_encode, + &nb_decoder_init, + &nb_decoder_destroy, + &nb_decode, + &nb_encoder_ctl, + &nb_decoder_ctl, +}; + + + +EXPORT int speex_mode_query(const SpeexMode *mode, int request, void *ptr) +{ + return mode->query(mode->mode, request, ptr); +} + +#ifdef FIXED_DEBUG +long long spx_mips=0; +#endif + diff --git a/Libraries/speex/modes.h b/Libraries/speex/modes.h new file mode 100644 index 000000000..26e2d8618 --- /dev/null +++ b/Libraries/speex/modes.h @@ -0,0 +1,161 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file modes.h + @brief Describes the different modes of the codec +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MODES_H +#define MODES_H + +#include +#include +#include "arch.h" + +#define NB_SUBMODES 16 +#define NB_SUBMODE_BITS 4 + +#define SB_SUBMODES 8 +#define SB_SUBMODE_BITS 3 + +/* Used internally, NOT TO BE USED in applications */ +/** Used internally*/ +#define SPEEX_GET_PI_GAIN 100 +/** Used internally*/ +#define SPEEX_GET_EXC 101 +/** Used internally*/ +#define SPEEX_GET_INNOV 102 +/** Used internally*/ +#define SPEEX_GET_DTX_STATUS 103 +/** Used internally*/ +#define SPEEX_SET_INNOVATION_SAVE 104 +/** Used internally*/ +#define SPEEX_SET_WIDEBAND 105 + +/** Used internally*/ +#define SPEEX_GET_STACK 106 + + +/** Quantizes LSPs */ +typedef void (*lsp_quant_func)(spx_lsp_t *, spx_lsp_t *, int, SpeexBits *); + +/** Decodes quantized LSPs */ +typedef void (*lsp_unquant_func)(spx_lsp_t *, int, SpeexBits *); + + +/** Long-term predictor quantization */ +typedef int (*ltp_quant_func)(spx_word16_t *, spx_word16_t *, spx_coef_t *, spx_coef_t *, + spx_coef_t *, spx_sig_t *, const void *, int, int, spx_word16_t, + int, int, SpeexBits*, char *, spx_word16_t *, spx_word16_t *, int, int, int, spx_word32_t *); + +/** Long-term un-quantize */ +typedef void (*ltp_unquant_func)(spx_word16_t *, spx_word32_t *, int, int, spx_word16_t, const void *, int, int *, + spx_word16_t *, SpeexBits*, char*, int, int, spx_word16_t, int); + + +/** Innovation quantization function */ +typedef void (*innovation_quant_func)(spx_word16_t *, spx_coef_t *, spx_coef_t *, spx_coef_t *, const void *, int, int, + spx_sig_t *, spx_word16_t *, SpeexBits *, char *, int, int); + +/** Innovation unquantization function */ +typedef void (*innovation_unquant_func)(spx_sig_t *, const void *, int, SpeexBits*, char *, spx_int32_t *); + +/** Description of a Speex sub-mode (wither narrowband or wideband */ +typedef struct SpeexSubmode { + int lbr_pitch; /**< Set to -1 for "normal" modes, otherwise encode pitch using a global pitch and allowing a +- lbr_pitch variation (for low not-rates)*/ + int forced_pitch_gain; /**< Use the same (forced) pitch gain for all sub-frames */ + int have_subframe_gain; /**< Number of bits to use as sub-frame innovation gain */ + int double_codebook; /**< Apply innovation quantization twice for higher quality (and higher bit-rate)*/ + /*LSP functions*/ + lsp_quant_func lsp_quant; /**< LSP quantization function */ + lsp_unquant_func lsp_unquant; /**< LSP unquantization function */ + + /*Long-term predictor functions*/ + ltp_quant_func ltp_quant; /**< Long-term predictor (pitch) quantizer */ + ltp_unquant_func ltp_unquant; /**< Long-term predictor (pitch) un-quantizer */ + const void *ltp_params; /**< Pitch parameters (options) */ + + /*Quantization of innovation*/ + innovation_quant_func innovation_quant; /**< Innovation quantization */ + innovation_unquant_func innovation_unquant; /**< Innovation un-quantization */ + const void *innovation_params; /**< Innovation quantization parameters*/ + + spx_word16_t comb_gain; /**< Gain of enhancer comb filter */ + + int bits_per_frame; /**< Number of bits per frame after encoding*/ +} SpeexSubmode; + +/** Struct defining the encoding/decoding mode*/ +typedef struct SpeexNBMode { + int frameSize; /**< Size of frames used for encoding */ + int subframeSize; /**< Size of sub-frames used for encoding */ + int lpcSize; /**< Order of LPC filter */ + int pitchStart; /**< Smallest pitch value allowed */ + int pitchEnd; /**< Largest pitch value allowed */ + + spx_word16_t gamma1; /**< Perceptual filter parameter #1 */ + spx_word16_t gamma2; /**< Perceptual filter parameter #2 */ + spx_word16_t lpc_floor; /**< Noise floor for LPC analysis */ + + const SpeexSubmode *submodes[NB_SUBMODES]; /**< Sub-mode data for the mode */ + int defaultSubmode; /**< Default sub-mode to use when encoding */ + int quality_map[11]; /**< Mode corresponding to each quality setting */ +} SpeexNBMode; + + +/** Struct defining the encoding/decoding mode for SB-CELP (wideband) */ +typedef struct SpeexSBMode { + const SpeexMode *nb_mode; /**< Embedded narrowband mode */ + int frameSize; /**< Size of frames used for encoding */ + int subframeSize; /**< Size of sub-frames used for encoding */ + int lpcSize; /**< Order of LPC filter */ + spx_word16_t gamma1; /**< Perceptual filter parameter #1 */ + spx_word16_t gamma2; /**< Perceptual filter parameter #1 */ + spx_word16_t lpc_floor; /**< Noise floor for LPC analysis */ + spx_word16_t folding_gain; + + const SpeexSubmode *submodes[SB_SUBMODES]; /**< Sub-mode data for the mode */ + int defaultSubmode; /**< Default sub-mode to use when encoding */ + int low_quality_map[11]; /**< Mode corresponding to each quality setting */ + int quality_map[11]; /**< Mode corresponding to each quality setting */ +#ifndef DISABLE_VBR + const float (*vbr_thresh)[11]; +#endif + int nb_modes; +} SpeexSBMode; + +int speex_encode_native(void *state, spx_word16_t *in, SpeexBits *bits); +int speex_decode_native(void *state, SpeexBits *bits, spx_word16_t *out); + +int nb_mode_query(const void *mode, int request, void *ptr); +int wb_mode_query(const void *mode, int request, void *ptr); + +#endif diff --git a/Libraries/speex/modes_wb.c b/Libraries/speex/modes_wb.c new file mode 100644 index 000000000..e3b484223 --- /dev/null +++ b/Libraries/speex/modes_wb.c @@ -0,0 +1,300 @@ +/* Copyright (C) 2002-2007 Jean-Marc Valin + File: modes.c + + Describes the wideband modes of the codec + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "sb_celp.h" +#include "nb_celp.h" +#include "vbr.h" +#include "arch.h" +#include +#include "os_support.h" + + +#ifndef NULL +#define NULL 0 +#endif + +EXPORT const SpeexMode * const speex_mode_list[SPEEX_NB_MODES] = {&speex_nb_mode, &speex_wb_mode, &speex_uwb_mode}; + +extern const signed char hexc_table[]; +extern const signed char hexc_10_32_table[]; + +#ifndef DISABLE_WIDEBAND + +/* Split-VQ innovation for high-band wideband */ +static const split_cb_params split_cb_high = { + 8, /*subvect_size*/ + 5, /*nb_subvect*/ + hexc_table, /*shape_cb*/ + 7, /*shape_bits*/ + 1, +}; + + +/* Split-VQ innovation for high-band wideband */ +static const split_cb_params split_cb_high_lbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + hexc_10_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + +#endif + + +static const SpeexSubmode wb_submode1 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*No innovation quantization*/ + NULL, + NULL, + NULL, + -1, + 36 +}; + + +static const SpeexSubmode wb_submode2 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high_lbr, +#endif + -1, + 112 +}; + + +static const SpeexSubmode wb_submode3 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high, +#endif + -1, + 192 +}; + +static const SpeexSubmode wb_submode4 = { + 0, + 0, + 1, + 1, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high, +#endif + -1, + 352 +}; + + +/* Split-band wideband CELP mode*/ +static const SpeexSBMode sb_wb_mode = { + &speex_nb_mode, + 160, /*frameSize*/ + 40, /*subframeSize*/ + 8, /*lpcSize*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + QCONST16(0.9f,15), + {NULL, &wb_submode1, &wb_submode2, &wb_submode3, &wb_submode4, NULL, NULL, NULL}, + 3, + {1, 8, 2, 3, 4, 5, 5, 6, 6, 7, 7}, + {1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4}, +#ifndef DISABLE_VBR + vbr_hb_thresh, +#endif + 5 +}; + + +EXPORT const SpeexMode speex_wb_mode = { + &sb_wb_mode, + wb_mode_query, + "wideband (sub-band CELP)", + 1, + 4, + &sb_encoder_init, + &sb_encoder_destroy, + &sb_encode, + &sb_decoder_init, + &sb_decoder_destroy, + &sb_decode, + &sb_encoder_ctl, + &sb_decoder_ctl, +}; + + + +/* "Ultra-wideband" mode stuff */ + + + +/* Split-band "ultra-wideband" (32 kbps) CELP mode*/ +static const SpeexSBMode sb_uwb_mode = { + &speex_wb_mode, + 320, /*frameSize*/ + 80, /*subframeSize*/ + 8, /*lpcSize*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + QCONST16(0.7f,15), + {NULL, &wb_submode1, NULL, NULL, NULL, NULL, NULL, NULL}, + 1, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, +#ifndef DISABLE_VBR + vbr_uhb_thresh, +#endif + 2 +}; + +int wb_mode_query(const void *mode, int request, void *ptr) +{ + const SpeexSBMode *m = (const SpeexSBMode*)mode; + + switch (request) + { + case SPEEX_MODE_FRAME_SIZE: + *((int*)ptr)=2*m->frameSize; + break; + case SPEEX_SUBMODE_BITS_PER_FRAME: + if (*((int*)ptr)==0) + *((int*)ptr) = SB_SUBMODE_BITS+1; + else if (m->submodes[*((int*)ptr)]==NULL) + *((int*)ptr) = -1; + else + *((int*)ptr) = m->submodes[*((int*)ptr)]->bits_per_frame; + break; + default: + speex_warning_int("Unknown wb_mode_query request: ", request); + return -1; + } + return 0; +} + + +EXPORT const SpeexMode speex_uwb_mode = { + &sb_uwb_mode, + wb_mode_query, + "ultra-wideband (sub-band CELP)", + 2, + 4, + &sb_encoder_init, + &sb_encoder_destroy, + &sb_encode, + &sb_decoder_init, + &sb_decoder_destroy, + &sb_decode, + &sb_encoder_ctl, + &sb_decoder_ctl, +}; + +/* We have defined speex_lib_get_mode() as a macro in speex.h */ +#undef speex_lib_get_mode + +EXPORT const SpeexMode * speex_lib_get_mode (int mode) +{ + if (mode < 0 || mode >= SPEEX_NB_MODES) return NULL; + + return speex_mode_list[mode]; +} + + + diff --git a/Libraries/speex/nb_celp.c b/Libraries/speex/nb_celp.c new file mode 100644 index 000000000..9dd726a0c --- /dev/null +++ b/Libraries/speex/nb_celp.c @@ -0,0 +1,1903 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: nb_celp.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "nb_celp.h" +#include "lpc.h" +#include "lsp.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "filters.h" +#include "stack_alloc.h" +#include "vq.h" +#include +#include "vbr.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" +#include + +#ifdef VORBIS_PSYCHO +#include "vorbis_psy.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#define SUBMODE(x) st->submodes[st->submodeID]->x + +/* Default size for the encoder and decoder stack (can be changed at compile time). + This does not apply when using variable-size arrays or alloca. */ +#ifndef NB_ENC_STACK +#define NB_ENC_STACK (8000*sizeof(spx_sig_t)) +#endif + +#ifndef NB_DEC_STACK +#define NB_DEC_STACK (4000*sizeof(spx_sig_t)) +#endif + + +#ifdef FIXED_POINT +const spx_word32_t ol_gain_table[32]={18900, 25150, 33468, 44536, 59265, 78865, 104946, 139653, 185838, 247297, 329081, 437913, 582736, 775454, 1031906, 1373169, 1827293, 2431601, 3235761, 4305867, 5729870, 7624808, 10146425, 13501971, 17967238, 23909222, 31816294, 42338330, 56340132, 74972501, 99766822, 132760927}; +const spx_word16_t exc_gain_quant_scal3_bound[7]={1841, 3883, 6051, 8062, 10444, 13580, 18560}; +const spx_word16_t exc_gain_quant_scal3[8]={1002, 2680, 5086, 7016, 9108, 11781, 15380, 21740}; +const spx_word16_t exc_gain_quant_scal1_bound[1]={14385}; +const spx_word16_t exc_gain_quant_scal1[2]={11546, 17224}; + +#define LSP_MARGIN 16 +#define LSP_DELTA1 6553 +#define LSP_DELTA2 1638 + +#else + +const float exc_gain_quant_scal3_bound[7]={0.112338f, 0.236980f, 0.369316f, 0.492054f, 0.637471f, 0.828874f, 1.132784f}; +const float exc_gain_quant_scal3[8]={0.061130f, 0.163546f, 0.310413f, 0.428220f, 0.555887f, 0.719055f, 0.938694f, 1.326874f}; +const float exc_gain_quant_scal1_bound[1]={0.87798f}; +const float exc_gain_quant_scal1[2]={0.70469f, 1.05127f}; + +#define LSP_MARGIN .002f +#define LSP_DELTA1 .2f +#define LSP_DELTA2 .05f + +#endif + +#ifdef VORBIS_PSYCHO +#define EXTRA_BUFFER 100 +#else +#define EXTRA_BUFFER 0 +#endif + + +#define sqr(x) ((x)*(x)) + +extern const spx_word16_t lag_window[]; +extern const spx_word16_t lpc_window[]; + +void *nb_encoder_init(const SpeexMode *m) +{ + EncState *st; + const SpeexNBMode *mode; + int i; + + mode=(const SpeexNBMode *)m->mode; + st = (EncState*)speex_alloc(sizeof(EncState)); + if (!st) + return NULL; +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + st->stack = (char*)speex_alloc_scratch(NB_ENC_STACK); +#endif + + st->mode=m; + + st->frameSize = mode->frameSize; + st->nbSubframes=mode->frameSize/mode->subframeSize; + st->subframeSize=mode->subframeSize; + st->windowSize = st->frameSize+st->subframeSize; + st->lpcSize = mode->lpcSize; + st->gamma1=mode->gamma1; + st->gamma2=mode->gamma2; + st->min_pitch=mode->pitchStart; + st->max_pitch=mode->pitchEnd; + st->lpc_floor = mode->lpc_floor; + + st->submodes=mode->submodes; + st->submodeID=st->submodeSelect=mode->defaultSubmode; + st->bounded_pitch = 1; + + st->encode_submode = 1; + +#ifdef VORBIS_PSYCHO + st->psy = vorbis_psy_init(8000, 256); + st->curve = (float*)speex_alloc(128*sizeof(float)); + st->old_curve = (float*)speex_alloc(128*sizeof(float)); + st->psy_window = (float*)speex_alloc(256*sizeof(float)); +#endif + + st->cumul_gain = 1024; + + /* Allocating input buffer */ + st->winBuf = (spx_word16_t*)speex_alloc((st->windowSize-st->frameSize)*sizeof(spx_word16_t)); + /* Allocating excitation buffer */ + st->excBuf = (spx_word16_t*)speex_alloc((mode->frameSize+mode->pitchEnd+2)*sizeof(spx_word16_t)); + st->exc = st->excBuf + mode->pitchEnd + 2; + st->swBuf = (spx_word16_t*)speex_alloc((mode->frameSize+mode->pitchEnd+2)*sizeof(spx_word16_t)); + st->sw = st->swBuf + mode->pitchEnd + 2; + + st->window= lpc_window; + + /* Create the window for autocorrelation (lag-windowing) */ + st->lagWindow = lag_window; + + st->old_lsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + + st->mem_sp = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw_whole = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_exc = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_exc2 = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->innov_rms_save = NULL; + + st->pitch = (int*)speex_alloc((st->nbSubframes)*sizeof(int)); + +#ifndef DISABLE_VBR + st->vbr = (VBRState*)speex_alloc(sizeof(VBRState)); + vbr_init(st->vbr); + st->vbr_quality = 8; + st->vbr_enabled = 0; + st->vbr_max = 0; + st->vad_enabled = 0; + st->dtx_enabled = 0; + st->dtx_count=0; + st->abr_enabled = 0; + st->abr_drift = 0; + st->abr_drift2 = 0; +#endif /* #ifndef DISABLE_VBR */ + + st->plc_tuning = 2; + st->complexity=2; + st->sampling_rate=8000; + st->isWideband = 0; + st->highpass_enabled = 1; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, NB_ENC_STACK); +#endif + return st; +} + +void nb_encoder_destroy(void *state) +{ + EncState *st=(EncState *)state; + /* Free all allocated memory */ +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + speex_free_scratch(st->stack); +#endif + + speex_free (st->winBuf); + speex_free (st->excBuf); + speex_free (st->old_qlsp); + speex_free (st->swBuf); + + speex_free (st->old_lsp); + speex_free (st->mem_sp); + speex_free (st->mem_sw); + speex_free (st->mem_sw_whole); + speex_free (st->mem_exc); + speex_free (st->mem_exc2); + speex_free (st->pi_gain); + speex_free (st->pitch); + +#ifndef DISABLE_VBR + vbr_destroy(st->vbr); + speex_free (st->vbr); +#endif /* #ifndef DISABLE_VBR */ + +#ifdef VORBIS_PSYCHO + vorbis_psy_destroy(st->psy); + speex_free (st->curve); + speex_free (st->old_curve); + speex_free (st->psy_window); +#endif + + /*Free state memory... should be last*/ + speex_free(st); +} + +int nb_encode(void *state, void *vin, SpeexBits *bits) +{ + EncState *st; + int i, sub, roots; + int ol_pitch; + spx_word16_t ol_pitch_coef; + spx_word32_t ol_gain; + VARDECL(spx_word16_t *ringing); + VARDECL(spx_word16_t *target); + VARDECL(spx_sig_t *innov); + VARDECL(spx_word32_t *exc32); + VARDECL(spx_mem_t *mem); + VARDECL(spx_coef_t *bw_lpc1); + VARDECL(spx_coef_t *bw_lpc2); + VARDECL(spx_coef_t *lpc); + VARDECL(spx_lsp_t *lsp); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_lsp); + VARDECL(spx_lsp_t *interp_qlsp); + VARDECL(spx_coef_t *interp_lpc); + VARDECL(spx_coef_t *interp_qlpc); + char *stack; + VARDECL(spx_word16_t *syn_resp); + VARDECL(spx_word16_t *real_exc); + + spx_word32_t ener=0; + spx_word16_t fine_gain; + spx_word16_t *in = (spx_word16_t*)vin; + + st=(EncState *)state; + stack=st->stack; + + ALLOC(lpc, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc1, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc2, st->lpcSize, spx_coef_t); + ALLOC(lsp, st->lpcSize, spx_lsp_t); + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lpc, st->lpcSize, spx_coef_t); + ALLOC(interp_qlpc, st->lpcSize, spx_coef_t); + + /* Move signals 1 frame towards the past */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, st->max_pitch+2); + SPEEX_MOVE(st->swBuf, st->swBuf+st->frameSize, st->max_pitch+2); + + if (st->highpass_enabled) + highpass(in, in, st->frameSize, (st->isWideband?HIGHPASS_WIDEBAND:HIGHPASS_NARROWBAND)|HIGHPASS_INPUT, st->mem_hp); + + { + VARDECL(spx_word16_t *w_sig); + VARDECL(spx_word16_t *autocorr); + ALLOC(w_sig, st->windowSize, spx_word16_t); + ALLOC(autocorr, st->lpcSize+1, spx_word16_t); + /* Window for analysis */ + for (i=0;iwindowSize-st->frameSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(st->winBuf[i],st->window[i]),SIG_SHIFT)); + for (;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(in[i-st->windowSize+st->frameSize],st->window[i]),SIG_SHIFT)); + /* Compute auto-correlation */ + _spx_autocorr(w_sig, autocorr, st->lpcSize+1, st->windowSize); + autocorr[0] = ADD16(autocorr[0],MULT16_16_Q15(autocorr[0],st->lpc_floor)); /* Noise floor in auto-correlation domain */ + + /* Lag windowing: equivalent to filtering in the power-spectrum domain */ + for (i=0;ilpcSize+1;i++) + autocorr[i] = MULT16_16_Q14(autocorr[i],st->lagWindow[i]); + + /* Levinson-Durbin */ + _spx_lpc(lpc, autocorr, st->lpcSize); + /* LPC to LSPs (x-domain) transform */ + roots=lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA1, stack); + /* Check if we found all the roots */ + if (roots!=st->lpcSize) + { + /*If we can't find all LSP's, do some damage control and use previous filter*/ + for (i=0;ilpcSize;i++) + { + lsp[i]=st->old_lsp[i]; + } + } + } + + + + + /* Whole frame analysis (open-loop estimation of pitch and excitation gain) */ + { + int diff = st->windowSize-st->frameSize; + if (st->first) + for (i=0;ilpcSize;i++) + interp_lsp[i] = lsp[i]; + else + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, st->nbSubframes, st->nbSubframes<<1); + + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (unquantized) for whole frame*/ + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + + + /*Open-loop pitch*/ + if (!st->submodes[st->submodeID] || (st->complexity>2 && SUBMODE(have_subframe_gain)<3) || SUBMODE(forced_pitch_gain) || SUBMODE(lbr_pitch) != -1 +#ifndef DISABLE_VBR + || st->vbr_enabled || st->vad_enabled +#endif + ) + { + int nol_pitch[6]; + spx_word16_t nol_pitch_coef[6]; + + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + + SPEEX_COPY(st->sw, st->winBuf, diff); + SPEEX_COPY(st->sw+diff, in, st->frameSize-diff); + filter_mem16(st->sw, bw_lpc1, bw_lpc2, st->sw, st->frameSize, st->lpcSize, st->mem_sw_whole, stack); + + open_loop_nbest_pitch(st->sw, st->min_pitch, st->max_pitch, st->frameSize, + nol_pitch, nol_pitch_coef, 6, stack); + ol_pitch=nol_pitch[0]; + ol_pitch_coef = nol_pitch_coef[0]; + /*Try to remove pitch multiples*/ + for (i=1;i<6;i++) + { +#ifdef FIXED_POINT + if ((nol_pitch_coef[i]>MULT16_16_Q15(nol_pitch_coef[0],27853)) && +#else + if ((nol_pitch_coef[i]>.85*nol_pitch_coef[0]) && +#endif + (ABS(2*nol_pitch[i]-ol_pitch)<=2 || ABS(3*nol_pitch[i]-ol_pitch)<=3 || + ABS(4*nol_pitch[i]-ol_pitch)<=4 || ABS(5*nol_pitch[i]-ol_pitch)<=5)) + { + /*ol_pitch_coef=nol_pitch_coef[i];*/ + ol_pitch = nol_pitch[i]; + } + } + /*if (ol_pitch>50) + ol_pitch/=2;*/ + /*ol_pitch_coef = sqrt(ol_pitch_coef);*/ + + } else { + ol_pitch=0; + ol_pitch_coef=0; + } + + /*Compute "real" excitation*/ + SPEEX_COPY(st->exc, st->winBuf, diff); + SPEEX_COPY(st->exc+diff, in, st->frameSize-diff); + fir_mem16(st->exc, interp_lpc, st->exc, st->frameSize, st->lpcSize, st->mem_exc, stack); + + /* Compute open-loop excitation gain */ + { + spx_word16_t g = compute_rms16(st->exc, st->frameSize); + if (st->submodeID!=1 && ol_pitch>0) + ol_gain = MULT16_16(g, MULT16_16_Q14(QCONST16(1.1,14), + spx_sqrt(QCONST32(1.,28)-MULT16_32_Q15(QCONST16(.8,15),SHL32(MULT16_16(ol_pitch_coef,ol_pitch_coef),16))))); + else + ol_gain = SHL32(EXTEND32(g),SIG_SHIFT); + } + } + +#ifdef VORBIS_PSYCHO + SPEEX_MOVE(st->psy_window, st->psy_window+st->frameSize, 256-st->frameSize); + SPEEX_COPY(&st->psy_window[256-st->frameSize], in, st->frameSize); + compute_curve(st->psy, st->psy_window, st->curve); + /*print_vec(st->curve, 128, "curve");*/ + if (st->first) + SPEEX_COPY(st->old_curve, st->curve, 128); +#endif + + /*VBR stuff*/ +#ifndef DISABLE_VBR + if (st->vbr && (st->vbr_enabled||st->vad_enabled)) + { + float lsp_dist=0; + for (i=0;ilpcSize;i++) + lsp_dist += (st->old_lsp[i] - lsp[i])*(st->old_lsp[i] - lsp[i]); + lsp_dist /= LSP_SCALING*LSP_SCALING; + + if (st->abr_enabled) + { + float qual_change=0; + if (st->abr_drift2 * st->abr_drift > 0) + { + /* Only adapt if long-term and short-term drift are the same sign */ + qual_change = -.00001*st->abr_drift/(1+st->abr_count); + if (qual_change>.05) + qual_change=.05; + if (qual_change<-.05) + qual_change=-.05; + } + st->vbr_quality += qual_change; + if (st->vbr_quality>10) + st->vbr_quality=10; + if (st->vbr_quality<0) + st->vbr_quality=0; + } + + st->relative_quality = vbr_analysis(st->vbr, in, st->frameSize, ol_pitch, GAIN_SCALING_1*ol_pitch_coef); + /*if (delta_qual<0)*/ + /* delta_qual*=.1*(3+st->vbr_quality);*/ + if (st->vbr_enabled) + { + spx_int32_t mode; + int choice=0; + float min_diff=100; + mode = 8; + while (mode) + { + int v1; + float thresh; + v1=(int)floor(st->vbr_quality); + if (v1==10) + thresh = vbr_nb_thresh[mode][v1]; + else + thresh = (st->vbr_quality-v1)*vbr_nb_thresh[mode][v1+1] + (1+v1-st->vbr_quality)*vbr_nb_thresh[mode][v1]; + if (st->relative_quality > thresh && + st->relative_quality-threshrelative_quality-thresh; + } + mode--; + } + mode=choice; + if (mode==0) + { + if (st->dtx_count==0 || lsp_dist>.05 || !st->dtx_enabled || st->dtx_count>20) + { + mode=1; + st->dtx_count=1; + } else { + mode=0; + st->dtx_count++; + } + } else { + st->dtx_count=0; + } + + speex_encoder_ctl(state, SPEEX_SET_MODE, &mode); + if (st->vbr_max>0) + { + spx_int32_t rate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &rate); + if (rate > st->vbr_max) + { + rate = st->vbr_max; + speex_encoder_ctl(state, SPEEX_SET_BITRATE, &rate); + } + } + + if (st->abr_enabled) + { + spx_int32_t bitrate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &bitrate); + st->abr_drift+=(bitrate-st->abr_enabled); + st->abr_drift2 = .95*st->abr_drift2 + .05*(bitrate-st->abr_enabled); + st->abr_count += 1.0; + } + + } else { + /*VAD only case*/ + int mode; + if (st->relative_quality<2) + { + if (st->dtx_count==0 || lsp_dist>.05 || !st->dtx_enabled || st->dtx_count>20) + { + st->dtx_count=1; + mode=1; + } else { + mode=0; + st->dtx_count++; + } + } else { + st->dtx_count = 0; + mode=st->submodeSelect; + } + /*speex_encoder_ctl(state, SPEEX_SET_MODE, &mode);*/ + st->submodeID=mode; + } + } else { + st->relative_quality = -1; + } +#endif /* #ifndef DISABLE_VBR */ + + if (st->encode_submode) + { + /* First, transmit a zero for narrowband */ + speex_bits_pack(bits, 0, 1); + + /* Transmit the sub-mode we use for this frame */ + speex_bits_pack(bits, st->submodeID, NB_SUBMODE_BITS); + + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + for (i=0;iframeSize;i++) + st->exc[i]=st->sw[i]=VERY_SMALL; + + for (i=0;ilpcSize;i++) + st->mem_sw[i]=0; + st->first=1; + st->bounded_pitch = 1; + + SPEEX_COPY(st->winBuf, in+2*st->frameSize-st->windowSize, st->windowSize-st->frameSize); + + /* Clear memory (no need to really compute it) */ + for (i=0;ilpcSize;i++) + st->mem_sp[i] = 0; + return 0; + + } + + /* LSP Quantization */ + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + } + + + /*Quantize LSPs*/ +#if 1 /*0 for unquantized*/ + SUBMODE(lsp_quant)(lsp, qlsp, st->lpcSize, bits); +#else + for (i=0;ilpcSize;i++) + qlsp[i]=lsp[i]; +#endif + + /*If we use low bit-rate pitch mode, transmit open-loop pitch*/ + if (SUBMODE(lbr_pitch)!=-1) + { + speex_bits_pack(bits, ol_pitch-st->min_pitch, 7); + } + + if (SUBMODE(forced_pitch_gain)) + { + int quant; + /* This just damps the pitch a bit, because it tends to be too aggressive when forced */ + ol_pitch_coef = MULT16_16_Q15(QCONST16(.9,15), ol_pitch_coef); +#ifdef FIXED_POINT + quant = PSHR16(MULT16_16_16(15, ol_pitch_coef),GAIN_SHIFT); +#else + quant = (int)floor(.5+15*ol_pitch_coef*GAIN_SCALING_1); +#endif + if (quant>15) + quant=15; + if (quant<0) + quant=0; + speex_bits_pack(bits, quant, 4); + ol_pitch_coef=MULT16_16_P15(QCONST16(0.066667,15),SHL16(quant,GAIN_SHIFT)); + } + + + /*Quantize and transmit open-loop excitation gain*/ +#ifdef FIXED_POINT + { + int qe = scal_quant32(ol_gain, ol_gain_table, 32); + /*ol_gain = exp(qe/3.5)*SIG_SCALING;*/ + ol_gain = MULT16_32_Q15(28406,ol_gain_table[qe]); + speex_bits_pack(bits, qe, 5); + } +#else + { + int qe = (int)(floor(.5+3.5*log(ol_gain*1.0/SIG_SCALING))); + if (qe<0) + qe=0; + if (qe>31) + qe=31; + ol_gain = exp(qe/3.5)*SIG_SCALING; + speex_bits_pack(bits, qe, 5); + } +#endif + + + + /* Special case for first frame */ + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + /* Target signal */ + ALLOC(target, st->subframeSize, spx_word16_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(exc32, st->subframeSize, spx_word32_t); + ALLOC(ringing, st->subframeSize, spx_word16_t); + ALLOC(syn_resp, st->subframeSize, spx_word16_t); + ALLOC(real_exc, st->subframeSize, spx_word16_t); + ALLOC(mem, st->lpcSize, spx_mem_t); + + /* Loop on sub-frames */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *sw; + spx_word16_t *exc; + int pitch; + int response_bound = st->subframeSize; + + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Excitation */ + exc=st->exc+offset; + /* Weighted signal */ + sw=st->sw+offset; + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, sub, st->nbSubframes); + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + /* Make sure the filters are stable */ + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (quantized and unquantized) */ + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + + lsp_to_lpc(interp_qlsp, interp_qlpc, st->lpcSize, stack); + + /* Compute analysis filter gain at w=pi (for use in SB-CELP) */ + { + spx_word32_t pi_g=LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + /*pi_g += -st->interp_qlpc[i] + st->interp_qlpc[i+1];*/ + pi_g = ADD32(pi_g, SUB32(EXTEND32(interp_qlpc[i+1]),EXTEND32(interp_qlpc[i]))); + } + st->pi_gain[sub] = pi_g; + } + +#ifdef VORBIS_PSYCHO + { + float curr_curve[128]; + float fact = ((float)sub+1.0f)/st->nbSubframes; + for (i=0;i<128;i++) + curr_curve[i] = (1.0f-fact)*st->old_curve[i] + fact*st->curve[i]; + curve_to_lpc(st->psy, curr_curve, bw_lpc1, bw_lpc2, 10); + } +#else + /* Compute bandwidth-expanded (unquantized) LPCs for perceptual weighting */ + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + if (st->gamma2>=0) + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + else + { + for (i=0;ilpcSize;i++) + bw_lpc2[i]=0; + } + /*print_vec(st->bw_lpc1, 10, "bw_lpc");*/ +#endif + + /*FIXME: This will break if we change the window size */ + speex_assert(st->windowSize-st->frameSize == st->subframeSize); + if (sub==0) + { + for (i=0;isubframeSize;i++) + real_exc[i] = sw[i] = st->winBuf[i]; + } else { + for (i=0;isubframeSize;i++) + real_exc[i] = sw[i] = in[i+((sub-1)*st->subframeSize)]; + } + fir_mem16(real_exc, interp_qlpc, real_exc, st->subframeSize, st->lpcSize, st->mem_exc2, stack); + + if (st->complexity==0) + response_bound >>= 1; + compute_impulse_response(interp_qlpc, bw_lpc1, bw_lpc2, syn_resp, response_bound, st->lpcSize, stack); + for (i=response_bound;isubframeSize;i++) + syn_resp[i]=VERY_SMALL; + + /* Compute zero response of A(z/g1) / ( A(z/g2) * A(z) ) */ + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sp[i],1); + for (i=0;isubframeSize;i++) + ringing[i] = VERY_SMALL; +#ifdef SHORTCUTS2 + iir_mem16(ringing, interp_qlpc, ringing, response_bound, st->lpcSize, mem, stack); + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sw[i],1); + filter_mem16(ringing, st->bw_lpc1, st->bw_lpc2, ringing, response_bound, st->lpcSize, mem, stack); + SPEEX_MEMSET(&ringing[response_bound], 0, st->subframeSize-response_bound); +#else + iir_mem16(ringing, interp_qlpc, ringing, st->subframeSize, st->lpcSize, mem, stack); + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sw[i],1); + filter_mem16(ringing, bw_lpc1, bw_lpc2, ringing, st->subframeSize, st->lpcSize, mem, stack); +#endif + + /* Compute weighted signal */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(sw, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, mem, stack); + + if (st->complexity==0) + for (i=0;ilpcSize;i++) + st->mem_sw[i]=mem[i]; + + /* Compute target signal (saturation prevents overflows on clipped input speech) */ + for (i=0;isubframeSize;i++) + target[i]=EXTRACT16(SATURATE(SUB32(sw[i],PSHR32(ringing[i],1)),32767)); + + /* Reset excitation */ + SPEEX_MEMSET(exc, 0, st->subframeSize); + + /* If we have a long-term predictor (otherwise, something's wrong) */ + speex_assert (SUBMODE(ltp_quant)); + { + int pit_min, pit_max; + /* Long-term prediction */ + if (SUBMODE(lbr_pitch) != -1) + { + /* Low bit-rate pitch handling */ + int margin; + margin = SUBMODE(lbr_pitch); + if (margin) + { + if (ol_pitch < st->min_pitch+margin-1) + ol_pitch=st->min_pitch+margin-1; + if (ol_pitch > st->max_pitch-margin) + ol_pitch=st->max_pitch-margin; + pit_min = ol_pitch-margin+1; + pit_max = ol_pitch+margin; + } else { + pit_min=pit_max=ol_pitch; + } + } else { + pit_min = st->min_pitch; + pit_max = st->max_pitch; + } + + /* Force pitch to use only the current frame if needed */ + if (st->bounded_pitch && pit_max>offset) + pit_max=offset; + + /* Perform pitch search */ + pitch = SUBMODE(ltp_quant)(target, sw, interp_qlpc, bw_lpc1, bw_lpc2, + exc32, SUBMODE(ltp_params), pit_min, pit_max, ol_pitch_coef, + st->lpcSize, st->subframeSize, bits, stack, + exc, syn_resp, st->complexity, 0, st->plc_tuning, &st->cumul_gain); + + st->pitch[sub]=pitch; + } + /* Quantization of innovation */ + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /* FIXME: Make sure this is save from overflows (so far so good) */ + for (i=0;isubframeSize;i++) + real_exc[i] = EXTRACT16(SUB32(EXTEND32(real_exc[i]), PSHR32(exc32[i],SIG_SHIFT-1))); + + ener = SHL32(EXTEND32(compute_rms16(real_exc, st->subframeSize)),SIG_SHIFT); + + /*FIXME: Should use DIV32_16 and make sure result fits in 16 bits */ +#ifdef FIXED_POINT + { + spx_word32_t f = PDIV32(ener,PSHR32(ol_gain,SIG_SHIFT)); + if (f<=32767) + fine_gain = f; + else + fine_gain = 32767; + } +#else + fine_gain = PDIV32_16(ener,PSHR32(ol_gain,SIG_SHIFT)); +#endif + /* Calculate gain correction for the sub-frame (if any) */ + if (SUBMODE(have_subframe_gain)) + { + int qe; + if (SUBMODE(have_subframe_gain)==3) + { + qe = scal_quant(fine_gain, exc_gain_quant_scal3_bound, 8); + speex_bits_pack(bits, qe, 3); + ener=MULT16_32_Q14(exc_gain_quant_scal3[qe],ol_gain); + } else { + qe = scal_quant(fine_gain, exc_gain_quant_scal1_bound, 2); + speex_bits_pack(bits, qe, 1); + ener=MULT16_32_Q14(exc_gain_quant_scal1[qe],ol_gain); + } + } else { + ener=ol_gain; + } + + /*printf ("%f %f\n", ener, ol_gain);*/ + + /* Normalize innovation */ + signal_div(target, target, ener, st->subframeSize); + + /* Quantize innovation */ + speex_assert (SUBMODE(innovation_quant)); + { + /* Codebook search */ + SUBMODE(innovation_quant)(target, interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov, syn_resp, bits, stack, st->complexity, SUBMODE(double_codebook)); + + /* De-normalize innovation and update excitation */ + signal_mul(innov, innov, ener, st->subframeSize); + + for (i=0;isubframeSize;i++) + exc[i] = EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + + /* In some (rare) modes, we do a second search (more bits) to reduce noise even more */ + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + for (i=0;isubframeSize;i++) + target[i]=MULT16_16_P13(QCONST16(2.2f,13), target[i]); + SUBMODE(innovation_quant)(target, interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov2, syn_resp, bits, stack, st->complexity, 0); + signal_mul(innov2, innov2, MULT16_32_Q15(QCONST16(0.454545f,15),ener), st->subframeSize); + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i],innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i] = EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = compute_rms(innov, st->subframeSize); + } + } + + /* Final signal synthesis from excitation */ + iir_mem16(exc, interp_qlpc, sw, st->subframeSize, st->lpcSize, st->mem_sp, stack); + + /* Compute weighted signal again, from synthesized speech (not sure it's the right thing) */ + if (st->complexity!=0) + filter_mem16(sw, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, st->mem_sw, stack); + + } + + /* Store the LSPs for interpolation in the next frame */ + if (st->submodeID>=1) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + +#ifdef VORBIS_PSYCHO + if (st->submodeID>=1) + SPEEX_COPY(st->old_curve, st->curve, 128); +#endif + + if (st->submodeID==1) + { +#ifndef DISABLE_VBR + if (st->dtx_count) + speex_bits_pack(bits, 15, 4); + else +#endif + speex_bits_pack(bits, 0, 4); + } + + /* The next frame will not be the first (Duh!) */ + st->first = 0; + SPEEX_COPY(st->winBuf, in+2*st->frameSize-st->windowSize, st->windowSize-st->frameSize); + + if (SUBMODE(innovation_quant) == noise_codebook_quant || st->submodeID==0) + st->bounded_pitch = 1; + else + st->bounded_pitch = 0; + + return 1; +} + +void *nb_decoder_init(const SpeexMode *m) +{ + DecState *st; + const SpeexNBMode *mode; + int i; + + mode=(const SpeexNBMode*)m->mode; + st = (DecState *)speex_alloc(sizeof(DecState)); + if (!st) + return NULL; +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + st->stack = (char*)speex_alloc_scratch(NB_DEC_STACK); +#endif + + st->mode=m; + + + st->encode_submode = 1; + + st->first=1; + /* Codec parameters, should eventually have several "modes"*/ + st->frameSize = mode->frameSize; + st->nbSubframes=mode->frameSize/mode->subframeSize; + st->subframeSize=mode->subframeSize; + st->lpcSize = mode->lpcSize; + st->min_pitch=mode->pitchStart; + st->max_pitch=mode->pitchEnd; + + st->submodes=mode->submodes; + st->submodeID=mode->defaultSubmode; + + st->lpc_enh_enabled=1; + + st->excBuf = (spx_word16_t*)speex_alloc((st->frameSize + 2*st->max_pitch + st->subframeSize + 12)*sizeof(spx_word16_t)); + st->exc = st->excBuf + 2*st->max_pitch + st->subframeSize + 6; + SPEEX_MEMSET(st->excBuf, 0, st->frameSize + st->max_pitch); + + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->mem_sp = (spx_mem_t*)speex_alloc(st->lpcSize*sizeof(spx_mem_t)); + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->last_pitch = 40; + st->count_lost=0; + st->pitch_gain_buf[0] = st->pitch_gain_buf[1] = st->pitch_gain_buf[2] = 0; + st->pitch_gain_buf_idx = 0; + st->seed = 1000; + + st->sampling_rate=8000; + st->last_ol_gain = 0; + + st->user_callback.func = &speex_default_user_handler; + st->user_callback.data = NULL; + for (i=0;i<16;i++) + st->speex_callbacks[i].func = NULL; + + st->voc_m1=st->voc_m2=st->voc_mean=0; + st->voc_offset=0; + st->dtx_enabled=0; + st->isWideband = 0; + st->highpass_enabled = 1; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, NB_DEC_STACK); +#endif + return st; +} + +void nb_decoder_destroy(void *state) +{ + DecState *st; + st=(DecState*)state; + +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + speex_free_scratch(st->stack); +#endif + + speex_free (st->excBuf); + speex_free (st->interp_qlpc); + speex_free (st->old_qlsp); + speex_free (st->mem_sp); + speex_free (st->pi_gain); + + speex_free(state); +} + +#define median3(a, b, c) ((a) < (b) ? ((b) < (c) ? (b) : ((a) < (c) ? (c) : (a))) : ((c) < (b) ? (b) : ((c) < (a) ? (c) : (a)))) + +#ifdef FIXED_POINT +const spx_word16_t attenuation[10] = {32767, 31483, 27923, 22861, 17278, 12055, 7764, 4616, 2533, 1283}; +#else +const spx_word16_t attenuation[10] = {1., 0.961, 0.852, 0.698, 0.527, 0.368, 0.237, 0.141, 0.077, 0.039}; + +#endif + +static void nb_decode_lost(DecState *st, spx_word16_t *out, char *stack) +{ + int i; + int pitch_val; + spx_word16_t pitch_gain; + spx_word16_t fact; + spx_word16_t gain_med; + spx_word16_t innov_gain; + spx_word16_t noise_gain; + + if (st->count_lost<10) + fact = attenuation[st->count_lost]; + else + fact = 0; + + gain_med = median3(st->pitch_gain_buf[0], st->pitch_gain_buf[1], st->pitch_gain_buf[2]); + if (gain_med < st->last_pitch_gain) + st->last_pitch_gain = gain_med; + +#ifdef FIXED_POINT + pitch_gain = st->last_pitch_gain; + if (pitch_gain>54) + pitch_gain = 54; + pitch_gain = SHL16(pitch_gain, 9); +#else + pitch_gain = GAIN_SCALING_1*st->last_pitch_gain; + if (pitch_gain>.85) + pitch_gain=.85; +#endif + pitch_gain = MULT16_16_Q15(fact,pitch_gain) + VERY_SMALL; + /* FIXME: This was rms of innovation (not exc) */ + innov_gain = compute_rms16(st->exc, st->frameSize); + noise_gain = MULT16_16_Q15(innov_gain, MULT16_16_Q15(fact, SUB16(Q15ONE,MULT16_16_Q15(pitch_gain,pitch_gain)))); + /* Shift all buffers by one frame */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, 2*st->max_pitch + st->subframeSize + 12); + + + pitch_val = st->last_pitch + SHR32((spx_int32_t)speex_rand(1+st->count_lost, &st->seed),SIG_SHIFT); + if (pitch_val > st->max_pitch) + pitch_val = st->max_pitch; + if (pitch_val < st->min_pitch) + pitch_val = st->min_pitch; + for (i=0;iframeSize;i++) + { + st->exc[i]= MULT16_16_Q15(pitch_gain, (st->exc[i-pitch_val]+VERY_SMALL)) + + speex_rand(noise_gain, &st->seed); + } + + bw_lpc(QCONST16(.98,15), st->interp_qlpc, st->interp_qlpc, st->lpcSize); + iir_mem16(&st->exc[-st->subframeSize], st->interp_qlpc, out, st->frameSize, + st->lpcSize, st->mem_sp, stack); + highpass(out, out, st->frameSize, HIGHPASS_NARROWBAND|HIGHPASS_OUTPUT, st->mem_hp); + + st->first = 0; + st->count_lost++; + st->pitch_gain_buf[st->pitch_gain_buf_idx++] = PSHR16(pitch_gain,9); + if (st->pitch_gain_buf_idx > 2) /* rollover */ + st->pitch_gain_buf_idx = 0; +} + +/* Just so we don't need to carry the complete wideband mode information */ +static const int wb_skip_table[8] = {0, 36, 112, 192, 352, 0, 0, 0}; + +int nb_decode(void *state, SpeexBits *bits, void *vout) +{ + DecState *st; + int i, sub; + int pitch; + spx_word16_t pitch_gain[3]; + spx_word32_t ol_gain=0; + int ol_pitch=0; + spx_word16_t ol_pitch_coef=0; + int best_pitch=40; + spx_word16_t best_pitch_gain=0; + int wideband; + int m; + char *stack; + VARDECL(spx_sig_t *innov); + VARDECL(spx_word32_t *exc32); + VARDECL(spx_coef_t *ak); + VARDECL(spx_lsp_t *qlsp); + spx_word16_t pitch_average=0; + + spx_word16_t *out = (spx_word16_t*)vout; + VARDECL(spx_lsp_t *interp_qlsp); + + st=(DecState*)state; + stack=st->stack; + + /* Check if we're in DTX mode*/ + if (!bits && st->dtx_enabled) + { + st->submodeID=0; + } else + { + /* If bits is NULL, consider the packet to be lost (what could we do anyway) */ + if (!bits) + { + nb_decode_lost(st, out, stack); + return 0; + } + + if (st->encode_submode) + { + + /* Search for next narrowband block (handle requests, skip wideband blocks) */ + do { + if (speex_bits_remaining(bits)<5) + return -1; + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) /* Skip wideband block (for compatibility) */ + { + int submode; + int advance; + advance = submode = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + /*speex_mode_query(&speex_wb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance);*/ + advance = wb_skip_table[submode]; + if (advance < 0) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + advance -= (SB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + + if (speex_bits_remaining(bits)<5) + return -1; + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) + { + advance = submode = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + /*speex_mode_query(&speex_wb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance);*/ + advance = wb_skip_table[submode]; + if (advance < 0) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + advance -= (SB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) + { + speex_notify("More than two wideband layers found. The stream is corrupted."); + return -2; + } + + } + } + if (speex_bits_remaining(bits)<4) + return -1; + /* FIXME: Check for overflow */ + m = speex_bits_unpack_unsigned(bits, 4); + if (m==15) /* We found a terminator */ + { + return -1; + } else if (m==14) /* Speex in-band request */ + { + int ret = speex_inband_handler(bits, st->speex_callbacks, state); + if (ret) + return ret; + } else if (m==13) /* User in-band request */ + { + int ret = st->user_callback.func(bits, state, st->user_callback.data); + if (ret) + return ret; + } else if (m>8) /* Invalid mode */ + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + + } while (m>8); + + /* Get the sub-mode that was used */ + st->submodeID = m; + } + + } + + /* Shift all buffers by one frame */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, 2*st->max_pitch + st->subframeSize + 12); + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + VARDECL(spx_coef_t *lpc); + ALLOC(lpc, st->lpcSize, spx_coef_t); + bw_lpc(QCONST16(0.93f,15), st->interp_qlpc, lpc, st->lpcSize); + { + spx_word16_t innov_gain=0; + /* FIXME: This was innov, not exc */ + innov_gain = compute_rms16(st->exc, st->frameSize); + for (i=0;iframeSize;i++) + st->exc[i]=speex_rand(innov_gain, &st->seed); + } + + + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(st->exc, lpc, out, st->frameSize, st->lpcSize, st->mem_sp, stack); + + st->count_lost=0; + return 0; + } + + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + + /* Unquantize LSPs */ + SUBMODE(lsp_unquant)(qlsp, st->lpcSize, bits); + + /*Damp memory if a frame was lost and the LSP changed too much*/ + if (st->count_lost) + { + spx_word16_t fact; + spx_word32_t lsp_dist=0; + for (i=0;ilpcSize;i++) + lsp_dist = ADD32(lsp_dist, EXTEND32(ABS(st->old_qlsp[i] - qlsp[i]))); +#ifdef FIXED_POINT + fact = SHR16(19661,SHR32(lsp_dist,LSP_SHIFT+2)); +#else + fact = .6*exp(-.2*lsp_dist); +#endif + for (i=0;ilpcSize;i++) + st->mem_sp[i] = MULT16_32_Q15(fact,st->mem_sp[i]); + } + + + /* Handle first frame and lost-packet case */ + if (st->first || st->count_lost) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + /* Get open-loop pitch estimation for low bit-rate pitch coding */ + if (SUBMODE(lbr_pitch)!=-1) + { + ol_pitch = st->min_pitch+speex_bits_unpack_unsigned(bits, 7); + } + + if (SUBMODE(forced_pitch_gain)) + { + int quant; + quant = speex_bits_unpack_unsigned(bits, 4); + ol_pitch_coef=MULT16_16_P15(QCONST16(0.066667,15),SHL16(quant,GAIN_SHIFT)); + } + + /* Get global excitation gain */ + { + int qe; + qe = speex_bits_unpack_unsigned(bits, 5); +#ifdef FIXED_POINT + /* FIXME: Perhaps we could slightly lower the gain here when the output is going to saturate? */ + ol_gain = MULT16_32_Q15(28406,ol_gain_table[qe]); +#else + ol_gain = SIG_SCALING*exp(qe/3.5); +#endif + } + + ALLOC(ak, st->lpcSize, spx_coef_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(exc32, st->subframeSize, spx_word32_t); + + if (st->submodeID==1) + { + int extra; + extra = speex_bits_unpack_unsigned(bits, 4); + + if (extra==15) + st->dtx_enabled=1; + else + st->dtx_enabled=0; + } + if (st->submodeID>1) + st->dtx_enabled=0; + + /*Loop on subframes */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *exc; + spx_word16_t *sp; + spx_word16_t *innov_save = NULL; + spx_word16_t tmp; + + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Excitation */ + exc=st->exc+offset; + /* Original signal */ + sp=out+offset; + if (st->innov_save) + innov_save = st->innov_save+offset; + + + /* Reset excitation */ + SPEEX_MEMSET(exc, 0, st->subframeSize); + + /*Adaptive codebook contribution*/ + speex_assert (SUBMODE(ltp_unquant)); + { + int pit_min, pit_max; + /* Handle pitch constraints if any */ + if (SUBMODE(lbr_pitch) != -1) + { + int margin; + margin = SUBMODE(lbr_pitch); + if (margin) + { +/* GT - need optimization? + if (ol_pitch < st->min_pitch+margin-1) + ol_pitch=st->min_pitch+margin-1; + if (ol_pitch > st->max_pitch-margin) + ol_pitch=st->max_pitch-margin; + pit_min = ol_pitch-margin+1; + pit_max = ol_pitch+margin; +*/ + pit_min = ol_pitch-margin+1; + if (pit_min < st->min_pitch) + pit_min = st->min_pitch; + pit_max = ol_pitch+margin; + if (pit_max > st->max_pitch) + pit_max = st->max_pitch; + } else { + pit_min = pit_max = ol_pitch; + } + } else { + pit_min = st->min_pitch; + pit_max = st->max_pitch; + } + + + + SUBMODE(ltp_unquant)(exc, exc32, pit_min, pit_max, ol_pitch_coef, SUBMODE(ltp_params), + st->subframeSize, &pitch, &pitch_gain[0], bits, stack, + st->count_lost, offset, st->last_pitch_gain, 0); + + /* Ensuring that things aren't blowing up as would happen if e.g. an encoder is + crafting packets to make us produce NaNs and slow down the decoder (vague DoS threat). + We can probably be even more aggressive and limit to 15000 or so. */ + sanitize_values32(exc32, NEG32(QCONST32(32000,SIG_SHIFT-1)), QCONST32(32000,SIG_SHIFT-1), st->subframeSize); + + tmp = gain_3tap_to_1tap(pitch_gain); + + pitch_average += tmp; + if ((tmp>best_pitch_gain&&ABS(2*best_pitch-pitch)>=3&&ABS(3*best_pitch-pitch)>=4&&ABS(4*best_pitch-pitch)>=5) + || (tmp>MULT16_16_Q15(QCONST16(.6,15),best_pitch_gain)&&(ABS(best_pitch-2*pitch)<3||ABS(best_pitch-3*pitch)<4||ABS(best_pitch-4*pitch)<5)) + || (MULT16_16_Q15(QCONST16(.67,15),tmp)>best_pitch_gain&&(ABS(2*best_pitch-pitch)<3||ABS(3*best_pitch-pitch)<4||ABS(4*best_pitch-pitch)<5)) ) + { + best_pitch = pitch; + if (tmp > best_pitch_gain) + best_pitch_gain = tmp; + } + } + + /* Unquantize the innovation */ + { + int q_energy; + spx_word32_t ener; + + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /* Decode sub-frame gain correction */ + if (SUBMODE(have_subframe_gain)==3) + { + q_energy = speex_bits_unpack_unsigned(bits, 3); + ener = MULT16_32_Q14(exc_gain_quant_scal3[q_energy],ol_gain); + } else if (SUBMODE(have_subframe_gain)==1) + { + q_energy = speex_bits_unpack_unsigned(bits, 1); + ener = MULT16_32_Q14(exc_gain_quant_scal1[q_energy],ol_gain); + } else { + ener = ol_gain; + } + + speex_assert (SUBMODE(innovation_unquant)); + { + /*Fixed codebook contribution*/ + SUBMODE(innovation_unquant)(innov, SUBMODE(innovation_params), st->subframeSize, bits, stack, &st->seed); + /* De-normalize innovation and update excitation */ + + signal_mul(innov, innov, ener, st->subframeSize); + + /* Decode second codebook (only for some modes) */ + if (SUBMODE(double_codebook)) + { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + SUBMODE(innovation_unquant)(innov2, SUBMODE(innovation_params), st->subframeSize, bits, stack, &st->seed); + signal_mul(innov2, innov2, MULT16_32_Q15(QCONST16(0.454545f,15),ener), st->subframeSize); + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i], innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i]=EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + /*print_vec(exc, 40, "innov");*/ + if (innov_save) + { + for (i=0;isubframeSize;i++) + innov_save[i] = EXTRACT16(PSHR32(innov[i], SIG_SHIFT)); + } + } + + /*Vocoder mode*/ + if (st->submodeID==1) + { + spx_word16_t g=ol_pitch_coef; + g=MULT16_16_P14(QCONST16(1.5f,14),(g-QCONST16(.2f,6))); + if (g<0) + g=0; + if (g>GAIN_SCALING) + g=GAIN_SCALING; + + SPEEX_MEMSET(exc, 0, st->subframeSize); + while (st->voc_offsetsubframeSize) + { + /* exc[st->voc_offset]= g*sqrt(2*ol_pitch)*ol_gain; + Not quite sure why we need the factor of two in the sqrt */ + if (st->voc_offset>=0) + exc[st->voc_offset]=MULT16_16(spx_sqrt(MULT16_16_16(2,ol_pitch)),EXTRACT16(PSHR32(MULT16_16(g,PSHR32(ol_gain,SIG_SHIFT)),6))); + st->voc_offset+=ol_pitch; + } + st->voc_offset -= st->subframeSize; + + for (i=0;isubframeSize;i++) + { + spx_word16_t exci=exc[i]; + exc[i]= ADD16(ADD16(MULT16_16_Q15(QCONST16(.7f,15),exc[i]) , MULT16_16_Q15(QCONST16(.3f,15),st->voc_m1)), + SUB16(MULT16_16_Q15(Q15_ONE-MULT16_16_16(QCONST16(.85f,9),g),EXTRACT16(PSHR32(innov[i],SIG_SHIFT))), + MULT16_16_Q15(MULT16_16_16(QCONST16(.15f,9),g),EXTRACT16(PSHR32(st->voc_m2,SIG_SHIFT))) + )); + st->voc_m1 = exci; + st->voc_m2=innov[i]; + st->voc_mean = EXTRACT16(PSHR32(ADD32(MULT16_16(QCONST16(.8f,15),st->voc_mean), MULT16_16(QCONST16(.2f,15),exc[i])), 15)); + exc[i]-=st->voc_mean; + } + } + + } + } + + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + + if (st->lpc_enh_enabled && SUBMODE(comb_gain)>0 && !st->count_lost) + { + multicomb(st->exc-st->subframeSize, out, st->interp_qlpc, st->lpcSize, 2*st->subframeSize, best_pitch, 40, SUBMODE(comb_gain), stack); + multicomb(st->exc+st->subframeSize, out+2*st->subframeSize, st->interp_qlpc, st->lpcSize, 2*st->subframeSize, best_pitch, 40, SUBMODE(comb_gain), stack); + } else { + SPEEX_COPY(out, &st->exc[-st->subframeSize], st->frameSize); + } + + /* If the last packet was lost, re-scale the excitation to obtain the same energy as encoded in ol_gain */ + if (st->count_lost) + { + spx_word16_t exc_ener; + spx_word32_t gain32; + spx_word16_t gain; + exc_ener = compute_rms16 (st->exc, st->frameSize); + gain32 = PDIV32(ol_gain, ADD16(exc_ener,1)); +#ifdef FIXED_POINT + if (gain32 > 32767) + gain32 = 32767; + gain = EXTRACT16(gain32); +#else + if (gain32 > 2) + gain32=2; + gain = gain32; +#endif + for (i=0;iframeSize;i++) + { + st->exc[i] = MULT16_16_Q14(gain, st->exc[i]); + out[i]=st->exc[i-st->subframeSize]; + } + } + + /*Loop on subframes */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *sp; + spx_word16_t *exc; + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Original signal */ + sp=out+offset; + /* Excitation */ + exc=st->exc+offset; + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + /* Make sure the LSP's are stable */ + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (unquantized) */ + lsp_to_lpc(interp_qlsp, ak, st->lpcSize, stack); + + /* Compute analysis filter at w=pi */ + { + spx_word32_t pi_g=LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + /*pi_g += -st->interp_qlpc[i] + st->interp_qlpc[i+1];*/ + pi_g = ADD32(pi_g, SUB32(EXTEND32(ak[i+1]),EXTEND32(ak[i]))); + } + st->pi_gain[sub] = pi_g; + } + + iir_mem16(sp, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, + st->mem_sp, stack); + + for (i=0;ilpcSize;i++) + st->interp_qlpc[i] = ak[i]; + + } + + if (st->highpass_enabled) + highpass(out, out, st->frameSize, (st->isWideband?HIGHPASS_WIDEBAND:HIGHPASS_NARROWBAND)|HIGHPASS_OUTPUT, st->mem_hp); + /*for (i=0;iframeSize;i++) + printf ("%d\n", (int)st->frame[i]);*/ + + /* Tracking output level */ + st->level = 1+PSHR32(ol_gain,SIG_SHIFT); + st->max_level = MAX16(MULT16_16_Q15(QCONST16(.99f,15), st->max_level), st->level); + st->min_level = MIN16(ADD16(1,MULT16_16_Q14(QCONST16(1.01f,14), st->min_level)), st->level); + if (st->max_level < st->min_level+1) + st->max_level = st->min_level+1; + /*printf ("%f %f %f %d\n", og, st->min_level, st->max_level, update);*/ + + /* Store the LSPs for interpolation in the next frame */ + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + /* The next frame will not be the first (Duh!) */ + st->first = 0; + st->count_lost=0; + st->last_pitch = best_pitch; +#ifdef FIXED_POINT + st->last_pitch_gain = PSHR16(pitch_average,2); +#else + st->last_pitch_gain = .25*pitch_average; +#endif + st->pitch_gain_buf[st->pitch_gain_buf_idx++] = st->last_pitch_gain; + if (st->pitch_gain_buf_idx > 2) /* rollover */ + st->pitch_gain_buf_idx = 0; + + st->last_ol_gain = ol_gain; + + return 0; +} + +int nb_encoder_ctl(void *state, int request, void *ptr) +{ + EncState *st; + st=(EncState*)state; + switch(request) + { + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->frameSize; + break; + case SPEEX_SET_LOW_MODE: + case SPEEX_SET_MODE: + st->submodeSelect = st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_LOW_MODE: + case SPEEX_GET_MODE: + (*(spx_int32_t*)ptr) = st->submodeID; + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR: + st->vbr_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VBR: + (*(spx_int32_t*)ptr) = st->vbr_enabled; + break; + case SPEEX_SET_VAD: + st->vad_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; + case SPEEX_SET_DTX: + st->dtx_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_DTX: + (*(spx_int32_t*)ptr) = st->dtx_enabled; + break; + case SPEEX_SET_ABR: + st->abr_enabled = (*(spx_int32_t*)ptr); + st->vbr_enabled = st->abr_enabled!=0; + if (st->vbr_enabled) + { + spx_int32_t i=10; + spx_int32_t rate, target; + float vbr_qual; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + vbr_qual=i; + if (vbr_qual<0) + vbr_qual=0; + speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_qual); + st->abr_count=0; + st->abr_drift=0; + st->abr_drift2=0; + } + + break; + case SPEEX_GET_ABR: + (*(spx_int32_t*)ptr) = st->abr_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ +#if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) + case SPEEX_SET_VBR_QUALITY: + st->vbr_quality = (*(float*)ptr); + break; + case SPEEX_GET_VBR_QUALITY: + (*(float*)ptr) = st->vbr_quality; + break; +#endif /* !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) */ + case SPEEX_SET_QUALITY: + { + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeSelect = st->submodeID = ((const SpeexNBMode*)(st->mode->mode))->quality_map[quality]; + } + break; + case SPEEX_SET_COMPLEXITY: + st->complexity = (*(spx_int32_t*)ptr); + if (st->complexity<0) + st->complexity=0; + break; + case SPEEX_GET_COMPLEXITY: + (*(spx_int32_t*)ptr) = st->complexity; + break; + case SPEEX_SET_BITRATE: + { + spx_int32_t i=10; + spx_int32_t rate, target; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + } + break; + case SPEEX_GET_BITRATE: + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) = st->sampling_rate*SUBMODE(bits_per_frame)/st->frameSize; + else + (*(spx_int32_t*)ptr) = st->sampling_rate*(NB_SUBMODE_BITS+1)/st->frameSize; + break; + case SPEEX_SET_SAMPLING_RATE: + st->sampling_rate = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_RESET_STATE: + { + int i; + st->bounded_pitch = 1; + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + for (i=0;ilpcSize;i++) + st->mem_sw[i]=st->mem_sw_whole[i]=st->mem_sp[i]=st->mem_exc[i]=0; + for (i=0;iframeSize+st->max_pitch+1;i++) + st->excBuf[i]=st->swBuf[i]=0; + for (i=0;iwindowSize-st->frameSize;i++) + st->winBuf[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + (*(spx_int32_t*)ptr)=(st->windowSize-st->frameSize); + break; + case SPEEX_SET_PLC_TUNING: + st->plc_tuning = (*(spx_int32_t*)ptr); + if (st->plc_tuning>100) + st->plc_tuning=100; + break; + case SPEEX_GET_PLC_TUNING: + (*(spx_int32_t*)ptr)=(st->plc_tuning); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR_MAX_BITRATE: + st->vbr_max = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VBR_MAX_BITRATE: + (*(spx_int32_t*)ptr) = st->vbr_max; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_HIGHPASS: + st->highpass_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_HIGHPASS: + (*(spx_int32_t*)ptr) = st->highpass_enabled; + break; + + /* This is all internal stuff past this point */ + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = compute_rms16(st->exc+i*st->subframeSize, st->subframeSize); + } + break; +#ifndef DISABLE_VBR + case SPEEX_GET_RELATIVE_QUALITY: + (*(float*)ptr)=st->relative_quality; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_INNOVATION_SAVE: + st->innov_rms_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + st->isWideband = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +int nb_decoder_ctl(void *state, int request, void *ptr) +{ + DecState *st; + st=(DecState*)state; + switch(request) + { + case SPEEX_SET_LOW_MODE: + case SPEEX_SET_MODE: + st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_LOW_MODE: + case SPEEX_GET_MODE: + (*(spx_int32_t*)ptr) = st->submodeID; + break; + case SPEEX_SET_ENH: + st->lpc_enh_enabled = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_ENH: + *((spx_int32_t*)ptr) = st->lpc_enh_enabled; + break; + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->frameSize; + break; + case SPEEX_GET_BITRATE: + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) = st->sampling_rate*SUBMODE(bits_per_frame)/st->frameSize; + else + (*(spx_int32_t*)ptr) = st->sampling_rate*(NB_SUBMODE_BITS+1)/st->frameSize; + break; + case SPEEX_SET_SAMPLING_RATE: + st->sampling_rate = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_SET_HANDLER: + { + SpeexCallback *c = (SpeexCallback*)ptr; + st->speex_callbacks[c->callback_id].func=c->func; + st->speex_callbacks[c->callback_id].data=c->data; + st->speex_callbacks[c->callback_id].callback_id=c->callback_id; + } + break; + case SPEEX_SET_USER_HANDLER: + { + SpeexCallback *c = (SpeexCallback*)ptr; + st->user_callback.func=c->func; + st->user_callback.data=c->data; + st->user_callback.callback_id=c->callback_id; + } + break; + case SPEEX_RESET_STATE: + { + int i; + for (i=0;ilpcSize;i++) + st->mem_sp[i]=0; + for (i=0;iframeSize + st->max_pitch + 1;i++) + st->excBuf[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + (*(spx_int32_t*)ptr)=st->subframeSize; + break; + case SPEEX_SET_HIGHPASS: + st->highpass_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_HIGHPASS: + (*(spx_int32_t*)ptr) = st->highpass_enabled; + break; + /* FIXME: Convert to fixed-point and re-enable even when float API is disabled */ +#ifndef DISABLE_FLOAT_API + case SPEEX_GET_ACTIVITY: + { + float ret; + ret = log(st->level/st->min_level)/log(st->max_level/st->min_level); + if (ret>1) + ret = 1; + /* Done in a strange way to catch NaNs as well */ + if (!(ret > 0)) + ret = 0; + /*printf ("%f %f %f %f\n", st->level, st->min_level, st->max_level, ret);*/ + (*(spx_int32_t*)ptr) = (int)(100*ret); + } + break; +#endif + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = compute_rms16(st->exc+i*st->subframeSize, st->subframeSize); + } + break; + case SPEEX_GET_DTX_STATUS: + *((spx_int32_t*)ptr) = st->dtx_enabled; + break; + case SPEEX_SET_INNOVATION_SAVE: + st->innov_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + st->isWideband = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} diff --git a/Libraries/speex/nb_celp.h b/Libraries/speex/nb_celp.h new file mode 100644 index 000000000..14c776ff3 --- /dev/null +++ b/Libraries/speex/nb_celp.h @@ -0,0 +1,203 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file nb_celp.h + @brief Narrowband CELP encoder/decoder +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NB_CELP_H +#define NB_CELP_H + +#include "modes.h" +#include +#include +#include "vbr.h" +#include "filters.h" + +#ifdef VORBIS_PSYCHO +#include "vorbis_psy.h" +#endif + +/**Structure representing the full state of the narrowband encoder*/ +typedef struct EncState { + const SpeexMode *mode; /**< Mode corresponding to the state */ + int first; /**< Is this the first frame? */ + int frameSize; /**< Size of frames */ + int subframeSize; /**< Size of sub-frames */ + int nbSubframes; /**< Number of sub-frames */ + int windowSize; /**< Analysis (LPC) window length */ + int lpcSize; /**< LPC order */ + int min_pitch; /**< Minimum pitch value allowed */ + int max_pitch; /**< Maximum pitch value allowed */ + + spx_word32_t cumul_gain; /**< Product of previously used pitch gains (Q10) */ + int bounded_pitch; /**< Next frame should not rely on previous frames for pitch */ + int ol_pitch; /**< Open-loop pitch */ + int ol_voiced; /**< Open-loop voiced/non-voiced decision */ + int *pitch; + +#ifdef VORBIS_PSYCHO + VorbisPsy *psy; + float *psy_window; + float *curve; + float *old_curve; +#endif + + spx_word16_t gamma1; /**< Perceptual filter: A(z/gamma1) */ + spx_word16_t gamma2; /**< Perceptual filter: A(z/gamma2) */ + spx_word16_t lpc_floor; /**< Noise floor multiplier for A[0] in LPC analysis*/ + char *stack; /**< Pseudo-stack allocation for temporary memory */ + spx_word16_t *winBuf; /**< Input buffer (original signal) */ + spx_word16_t *excBuf; /**< Excitation buffer */ + spx_word16_t *exc; /**< Start of excitation frame */ + spx_word16_t *swBuf; /**< Weighted signal buffer */ + spx_word16_t *sw; /**< Start of weighted signal frame */ + const spx_word16_t *window; /**< Temporary (Hanning) window */ + const spx_word16_t *lagWindow; /**< Window applied to auto-correlation */ + spx_lsp_t *old_lsp; /**< LSPs for previous frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs for previous frame */ + spx_mem_t *mem_sp; /**< Filter memory for signal synthesis */ + spx_mem_t *mem_sw; /**< Filter memory for perceptually-weighted signal */ + spx_mem_t *mem_sw_whole; /**< Filter memory for perceptually-weighted signal (whole frame)*/ + spx_mem_t *mem_exc; /**< Filter memory for excitation (whole frame) */ + spx_mem_t *mem_exc2; /**< Filter memory for excitation (whole frame) */ + spx_mem_t mem_hp[2]; /**< High-pass filter memory */ + spx_word32_t *pi_gain; /**< Gain of LPC filter at theta=pi (fe/2) */ + spx_word16_t *innov_rms_save; /**< If non-NULL, innovation RMS is copied here */ + +#ifndef DISABLE_VBR + VBRState *vbr; /**< State of the VBR data */ + float vbr_quality; /**< Quality setting for VBR encoding */ + float relative_quality; /**< Relative quality that will be needed by VBR */ + spx_int32_t vbr_enabled; /**< 1 for enabling VBR, 0 otherwise */ + spx_int32_t vbr_max; /**< Max bit-rate allowed in VBR mode */ + int vad_enabled; /**< 1 for enabling VAD, 0 otherwise */ + int dtx_enabled; /**< 1 for enabling DTX, 0 otherwise */ + int dtx_count; /**< Number of consecutive DTX frames */ + spx_int32_t abr_enabled; /**< ABR setting (in bps), 0 if off */ + float abr_drift; + float abr_drift2; + float abr_count; +#endif /* #ifndef DISABLE_VBR */ + + int complexity; /**< Complexity setting (0-10 from least complex to most complex) */ + spx_int32_t sampling_rate; + int plc_tuning; + int encode_submode; + const SpeexSubmode * const *submodes; /**< Sub-mode data */ + int submodeID; /**< Activated sub-mode */ + int submodeSelect; /**< Mode chosen by the user (may differ from submodeID if VAD is on) */ + int isWideband; /**< Is this used as part of the embedded wideband codec */ + int highpass_enabled; /**< Is the input filter enabled */ +} EncState; + +/**Structure representing the full state of the narrowband decoder*/ +typedef struct DecState { + const SpeexMode *mode; /**< Mode corresponding to the state */ + int first; /**< Is this the first frame? */ + int count_lost; /**< Was the last frame lost? */ + int frameSize; /**< Size of frames */ + int subframeSize; /**< Size of sub-frames */ + int nbSubframes; /**< Number of sub-frames */ + int lpcSize; /**< LPC order */ + int min_pitch; /**< Minimum pitch value allowed */ + int max_pitch; /**< Maximum pitch value allowed */ + spx_int32_t sampling_rate; + + spx_word16_t last_ol_gain; /**< Open-loop gain for previous frame */ + + char *stack; /**< Pseudo-stack allocation for temporary memory */ + spx_word16_t *excBuf; /**< Excitation buffer */ + spx_word16_t *exc; /**< Start of excitation frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs for previous frame */ + spx_coef_t *interp_qlpc; /**< Interpolated quantized LPCs */ + spx_mem_t *mem_sp; /**< Filter memory for synthesis signal */ + spx_mem_t mem_hp[2]; /**< High-pass filter memory */ + spx_word32_t *pi_gain; /**< Gain of LPC filter at theta=pi (fe/2) */ + spx_word16_t *innov_save; /** If non-NULL, innovation is copied here */ + + spx_word16_t level; + spx_word16_t max_level; + spx_word16_t min_level; + + /* This is used in packet loss concealment */ + int last_pitch; /**< Pitch of last correctly decoded frame */ + spx_word16_t last_pitch_gain; /**< Pitch gain of last correctly decoded frame */ + spx_word16_t pitch_gain_buf[3]; /**< Pitch gain of last decoded frames */ + int pitch_gain_buf_idx; /**< Tail of the buffer */ + spx_int32_t seed; /** Seed used for random number generation */ + + int encode_submode; + const SpeexSubmode * const *submodes; /**< Sub-mode data */ + int submodeID; /**< Activated sub-mode */ + int lpc_enh_enabled; /**< 1 when LPC enhancer is on, 0 otherwise */ + SpeexCallback speex_callbacks[SPEEX_MAX_CALLBACKS]; + + SpeexCallback user_callback; + + /*Vocoder data*/ + spx_word16_t voc_m1; + spx_word32_t voc_m2; + spx_word16_t voc_mean; + int voc_offset; + + int dtx_enabled; + int isWideband; /**< Is this used as part of the embedded wideband codec */ + int highpass_enabled; /**< Is the input filter enabled */ +} DecState; + +/** Initializes encoder state*/ +void *nb_encoder_init(const SpeexMode *m); + +/** De-allocates encoder state resources*/ +void nb_encoder_destroy(void *state); + +/** Encodes one frame*/ +int nb_encode(void *state, void *in, SpeexBits *bits); + + +/** Initializes decoder state*/ +void *nb_decoder_init(const SpeexMode *m); + +/** De-allocates decoder state resources*/ +void nb_decoder_destroy(void *state); + +/** Decodes one frame*/ +int nb_decode(void *state, SpeexBits *bits, void *out); + +/** ioctl-like function for controlling a narrowband encoder */ +int nb_encoder_ctl(void *state, int request, void *ptr); + +/** ioctl-like function for controlling a narrowband decoder */ +int nb_decoder_ctl(void *state, int request, void *ptr); + + +#endif diff --git a/Libraries/speex/os_support.h b/Libraries/speex/os_support.h new file mode 100644 index 000000000..6b74b0c22 --- /dev/null +++ b/Libraries/speex/os_support.h @@ -0,0 +1,169 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: os_support.h + This is the (tiny) OS abstraction layer. Aside from math.h, this is the + only place where system headers are allowed. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef OS_SUPPORT_H +#define OS_SUPPORT_H + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef OS_SUPPORT_CUSTOM +#include "os_support_custom.h" +#endif + +/** Speex wrapper for calloc. To do your own dynamic allocation, all you need to do is replace this function, speex_realloc and speex_free + NOTE: speex_alloc needs to CLEAR THE MEMORY */ +#ifndef OVERRIDE_SPEEX_ALLOC +static inline void *speex_alloc (int size) +{ + /* WARNING: this is not equivalent to malloc(). If you want to use malloc() + or your own allocator, YOU NEED TO CLEAR THE MEMORY ALLOCATED. Otherwise + you will experience strange bugs */ + return calloc(size,1); +} +#endif + +/** Same as speex_alloc, except that the area is only needed inside a Speex call (might cause problem with wideband though) */ +#ifndef OVERRIDE_SPEEX_ALLOC_SCRATCH +static inline void *speex_alloc_scratch (int size) +{ + /* Scratch space doesn't need to be cleared */ + return calloc(size,1); +} +#endif + +/** Speex wrapper for realloc. To do your own dynamic allocation, all you need to do is replace this function, speex_alloc and speex_free */ +#ifndef OVERRIDE_SPEEX_REALLOC +static inline void *speex_realloc (void *ptr, int size) +{ + return realloc(ptr, size); +} +#endif + +/** Speex wrapper for calloc. To do your own dynamic allocation, all you need to do is replace this function, speex_realloc and speex_alloc */ +#ifndef OVERRIDE_SPEEX_FREE +static inline void speex_free (void *ptr) +{ + free(ptr); +} +#endif + +/** Same as speex_free, except that the area is only needed inside a Speex call (might cause problem with wideband though) */ +#ifndef OVERRIDE_SPEEX_FREE_SCRATCH +static inline void speex_free_scratch (void *ptr) +{ + free(ptr); +} +#endif + +/** Copy n bytes of memory from src to dst. The 0* term provides compile-time type checking */ +#ifndef OVERRIDE_SPEEX_COPY +#define SPEEX_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Copy n bytes of memory from src to dst, allowing overlapping regions. The 0* term + provides compile-time type checking */ +#ifndef OVERRIDE_SPEEX_MOVE +#define SPEEX_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Set n bytes of memory to value of c, starting at address s */ +#ifndef OVERRIDE_SPEEX_MEMSET +#define SPEEX_MEMSET(dst, c, n) (memset((dst), (c), (n)*sizeof(*(dst)))) +#endif + + +#ifndef OVERRIDE_SPEEX_FATAL +static inline void _speex_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); + exit(1); +} +#endif + +#ifndef OVERRIDE_SPEEX_WARNING +static inline void speex_warning(const char *str) +{ +#ifndef DISABLE_WARNINGS + fprintf (stderr, "warning: %s\n", str); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_WARNING_INT +static inline void speex_warning_int(const char *str, int val) +{ +#ifndef DISABLE_WARNINGS + fprintf (stderr, "warning: %s %d\n", str, val); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_NOTIFY +static inline void speex_notify(const char *str) +{ +#ifndef DISABLE_NOTIFICATIONS + fprintf (stderr, "notification: %s\n", str); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_PUTC +/** Speex wrapper for putc */ +static inline void _speex_putc(int ch, void *file) +{ + FILE *f = (FILE *)file; + fprintf(f, "%c", ch); +} +#endif + +#define speex_fatal(str) _speex_fatal(str, __FILE__, __LINE__); +#define speex_assert(cond) {if (!(cond)) {speex_fatal("assertion failed: " #cond);}} + +#ifndef RELEASE +static inline void print_vec(float *vec, int len, char *name) +{ + int i; + printf ("%s ", name); + for (i=0;i +#include "speex/speex_preprocess.h" +#include "speex/speex_echo.h" +#include "arch.h" +#include "fftwrap.h" +#include "filterbank.h" +#include "math_approx.h" +#include "os_support.h" + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +#define LOUDNESS_EXP 5.f +#define AMP_SCALE .001f +#define AMP_SCALE_1 1000.f + +#define NB_BANDS 24 + +#define SPEECH_PROB_START_DEFAULT QCONST16(0.35f,15) +#define SPEECH_PROB_CONTINUE_DEFAULT QCONST16(0.20f,15) +#define NOISE_SUPPRESS_DEFAULT -15 +#define ECHO_SUPPRESS_DEFAULT -40 +#define ECHO_SUPPRESS_ACTIVE_DEFAULT -15 + +#ifndef NULL +#define NULL 0 +#endif + +#define SQR(x) ((x)*(x)) +#define SQR16(x) (MULT16_16((x),(x))) +#define SQR16_Q15(x) (MULT16_16_Q15((x),(x))) + +#ifdef FIXED_POINT +static inline spx_word16_t DIV32_16_Q8(spx_word32_t a, spx_word32_t b) +{ + if (SHR32(a,7) >= b) + { + return 32767; + } else { + if (b>=QCONST32(1,23)) + { + a = SHR32(a,8); + b = SHR32(b,8); + } + if (b>=QCONST32(1,19)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + if (b>=QCONST32(1,15)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + a = SHL32(a,8); + return PDIV32_16(a,b); + } + +} +static inline spx_word16_t DIV32_16_Q15(spx_word32_t a, spx_word32_t b) +{ + if (SHR32(a,15) >= b) + { + return 32767; + } else { + if (b>=QCONST32(1,23)) + { + a = SHR32(a,8); + b = SHR32(b,8); + } + if (b>=QCONST32(1,19)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + if (b>=QCONST32(1,15)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + a = SHL32(a,15)-a; + return DIV32_16(a,b); + } +} +#define SNR_SCALING 256.f +#define SNR_SCALING_1 0.0039062f +#define SNR_SHIFT 8 + +#define FRAC_SCALING 32767.f +#define FRAC_SCALING_1 3.0518e-05 +#define FRAC_SHIFT 1 + +#define EXPIN_SCALING 2048.f +#define EXPIN_SCALING_1 0.00048828f +#define EXPIN_SHIFT 11 +#define EXPOUT_SCALING_1 1.5259e-05 + +#define NOISE_SHIFT 7 + +#else + +#define DIV32_16_Q8(a,b) ((a)/(b)) +#define DIV32_16_Q15(a,b) ((a)/(b)) +#define SNR_SCALING 1.f +#define SNR_SCALING_1 1.f +#define SNR_SHIFT 0 +#define FRAC_SCALING 1.f +#define FRAC_SCALING_1 1.f +#define FRAC_SHIFT 0 +#define NOISE_SHIFT 0 + +#define EXPIN_SCALING 1.f +#define EXPIN_SCALING_1 1.f +#define EXPOUT_SCALING_1 1.f + +#endif + +/** Speex pre-processor state. */ +struct SpeexPreprocessState_ { + /* Basic info */ + int frame_size; /**< Number of samples processed each time */ + int ps_size; /**< Number of points in the power spectrum */ + int sampling_rate; /**< Sampling rate of the input/output */ + int nbands; + FilterBank *bank; + + /* Parameters */ + int denoise_enabled; + int vad_enabled; + int dereverb_enabled; + spx_word16_t reverb_decay; + spx_word16_t reverb_level; + spx_word16_t speech_prob_start; + spx_word16_t speech_prob_continue; + int noise_suppress; + int echo_suppress; + int echo_suppress_active; + SpeexEchoState *echo_state; + + spx_word16_t speech_prob; /**< Probability last frame was speech */ + + /* DSP-related arrays */ + spx_word16_t *frame; /**< Processing frame (2*ps_size) */ + spx_word16_t *ft; /**< Processing frame in freq domain (2*ps_size) */ + spx_word32_t *ps; /**< Current power spectrum */ + spx_word16_t *gain2; /**< Adjusted gains */ + spx_word16_t *gain_floor; /**< Minimum gain allowed */ + spx_word16_t *window; /**< Analysis/Synthesis window */ + spx_word32_t *noise; /**< Noise estimate */ + spx_word32_t *reverb_estimate; /**< Estimate of reverb energy */ + spx_word32_t *old_ps; /**< Power spectrum for last frame */ + spx_word16_t *gain; /**< Ephraim Malah gain */ + spx_word16_t *prior; /**< A-priori SNR */ + spx_word16_t *post; /**< A-posteriori SNR */ + + spx_word32_t *S; /**< Smoothed power spectrum */ + spx_word32_t *Smin; /**< See Cohen paper */ + spx_word32_t *Stmp; /**< See Cohen paper */ + int *update_prob; /**< Probability of speech presence for noise update */ + + spx_word16_t *zeta; /**< Smoothed a priori SNR */ + spx_word32_t *echo_noise; + spx_word32_t *residual_echo; + + /* Misc */ + spx_word16_t *inbuf; /**< Input buffer (overlapped analysis) */ + spx_word16_t *outbuf; /**< Output buffer (for overlap and add) */ + + /* AGC stuff, only for floating point for now */ +#ifndef FIXED_POINT + int agc_enabled; + float agc_level; + float loudness_accum; + float *loudness_weight; /**< Perceptual loudness curve */ + float loudness; /**< Loudness estimate */ + float agc_gain; /**< Current AGC gain */ + float max_gain; /**< Maximum gain allowed */ + float max_increase_step; /**< Maximum increase in gain from one frame to another */ + float max_decrease_step; /**< Maximum decrease in gain from one frame to another */ + float prev_loudness; /**< Loudness of previous frame */ + float init_max; /**< Current gain limit during initialisation */ +#endif + int nb_adapt; /**< Number of frames used for adaptation so far */ + int was_speech; + int min_count; /**< Number of frames processed so far */ + void *fft_lookup; /**< Lookup table for the FFT */ +#ifdef FIXED_POINT + int frame_shift; +#endif +}; + + +static void conj_window(spx_word16_t *w, int len) +{ + int i; + for (i=0;i19) + return ADD32(EXTEND32(Q15_ONE),EXTEND32(DIV32_16(QCONST32(.1296,23), SHR32(xx,EXPIN_SHIFT-SNR_SHIFT)))); + frac = SHL32(xx-SHL32(ind,10),5); + return SHL32(DIV32_16(PSHR32(MULT16_16(Q15_ONE-frac,table[ind]) + MULT16_16(frac,table[ind+1]),7),(spx_sqrt(SHL32(xx,15)+6711))),7); +} + +static inline spx_word16_t qcurve(spx_word16_t x) +{ + x = MAX16(x, 1); + return DIV32_16(SHL32(EXTEND32(32767),9),ADD16(512,MULT16_16_Q15(QCONST16(.60f,15),DIV32_16(32767,x)))); +} + +/* Compute the gain floor based on different floors for the background noise and residual echo */ +static void compute_gain_floor(int noise_suppress, int effective_echo_suppress, spx_word32_t *noise, spx_word32_t *echo, spx_word16_t *gain_floor, int len) +{ + int i; + + if (noise_suppress > effective_echo_suppress) + { + spx_word16_t noise_gain, gain_ratio; + noise_gain = EXTRACT16(MIN32(Q15_ONE,SHR32(spx_exp(MULT16_16(QCONST16(0.11513,11),noise_suppress)),1))); + gain_ratio = EXTRACT16(MIN32(Q15_ONE,SHR32(spx_exp(MULT16_16(QCONST16(.2302585f,11),effective_echo_suppress-noise_suppress)),1))); + + /* gain_floor = sqrt [ (noise*noise_floor + echo*echo_floor) / (noise+echo) ] */ + for (i=0;i19) + return FRAC_SCALING*(1+.1296/x); + frac = 2*x-integer; + return FRAC_SCALING*((1-frac)*table[ind] + frac*table[ind+1])/sqrt(x+.0001f); +} + +static inline spx_word16_t qcurve(spx_word16_t x) +{ + return 1.f/(1.f+.15f/(SNR_SCALING_1*x)); +} + +static void compute_gain_floor(int noise_suppress, int effective_echo_suppress, spx_word32_t *noise, spx_word32_t *echo, spx_word16_t *gain_floor, int len) +{ + int i; + float echo_floor; + float noise_floor; + + noise_floor = exp(.2302585f*noise_suppress); + echo_floor = exp(.2302585f*effective_echo_suppress); + + /* Compute the gain floor based on different floors for the background noise and residual echo */ + for (i=0;iframe_size = frame_size; + + /* Round ps_size down to the nearest power of two */ +#if 0 + i=1; + st->ps_size = st->frame_size; + while(1) + { + if (st->ps_size & ~i) + { + st->ps_size &= ~i; + i<<=1; + } else { + break; + } + } + + + if (st->ps_size < 3*st->frame_size/4) + st->ps_size = st->ps_size * 3 / 2; +#else + st->ps_size = st->frame_size; +#endif + + N = st->ps_size; + N3 = 2*N - st->frame_size; + N4 = st->frame_size - N3; + + st->sampling_rate = sampling_rate; + st->denoise_enabled = 1; + st->vad_enabled = 0; + st->dereverb_enabled = 0; + st->reverb_decay = 0; + st->reverb_level = 0; + st->noise_suppress = NOISE_SUPPRESS_DEFAULT; + st->echo_suppress = ECHO_SUPPRESS_DEFAULT; + st->echo_suppress_active = ECHO_SUPPRESS_ACTIVE_DEFAULT; + + st->speech_prob_start = SPEECH_PROB_START_DEFAULT; + st->speech_prob_continue = SPEECH_PROB_CONTINUE_DEFAULT; + + st->echo_state = NULL; + + st->nbands = NB_BANDS; + M = st->nbands; + st->bank = filterbank_new(M, sampling_rate, N, 1); + + st->frame = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + st->window = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + st->ft = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + + st->ps = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->noise = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->echo_noise = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->residual_echo = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->reverb_estimate = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->old_ps = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->prior = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->post = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain2 = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain_floor = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->zeta = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + + st->S = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->Smin = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->Stmp = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->update_prob = (int*)speex_alloc(N*sizeof(int)); + + st->inbuf = (spx_word16_t*)speex_alloc(N3*sizeof(spx_word16_t)); + st->outbuf = (spx_word16_t*)speex_alloc(N3*sizeof(spx_word16_t)); + + conj_window(st->window, 2*N3); + for (i=2*N3;i<2*st->ps_size;i++) + st->window[i]=Q15_ONE; + + if (N4>0) + { + for (i=N3-1;i>=0;i--) + { + st->window[i+N3+N4]=st->window[i+N3]; + st->window[i+N3]=1; + } + } + for (i=0;inoise[i]=QCONST32(1.f,NOISE_SHIFT); + st->reverb_estimate[i]=0; + st->old_ps[i]=1; + st->gain[i]=Q15_ONE; + st->post[i]=SHL16(1, SNR_SHIFT); + st->prior[i]=SHL16(1, SNR_SHIFT); + } + + for (i=0;iupdate_prob[i] = 1; + for (i=0;iinbuf[i]=0; + st->outbuf[i]=0; + } +#ifndef FIXED_POINT + st->agc_enabled = 0; + st->agc_level = 8000; + st->loudness_weight = (float*)speex_alloc(N*sizeof(float)); + for (i=0;iloudness_weight[i] = .5f*(1.f/(1.f+ff/8000.f))+1.f*exp(-.5f*(ff-3800.f)*(ff-3800.f)/9e5f);*/ + st->loudness_weight[i] = .35f-.35f*ff/16000.f+.73f*exp(-.5f*(ff-3800)*(ff-3800)/9e5f); + if (st->loudness_weight[i]<.01f) + st->loudness_weight[i]=.01f; + st->loudness_weight[i] *= st->loudness_weight[i]; + } + /*st->loudness = pow(AMP_SCALE*st->agc_level,LOUDNESS_EXP);*/ + st->loudness = 1e-15; + st->agc_gain = 1; + st->max_gain = 30; + st->max_increase_step = exp(0.11513f * 12.*st->frame_size / st->sampling_rate); + st->max_decrease_step = exp(-0.11513f * 40.*st->frame_size / st->sampling_rate); + st->prev_loudness = 1; + st->init_max = 1; +#endif + st->was_speech = 0; + + st->fft_lookup = spx_fft_init(2*N); + + st->nb_adapt=0; + st->min_count=0; + return st; +} + +EXPORT void speex_preprocess_state_destroy(SpeexPreprocessState *st) +{ + speex_free(st->frame); + speex_free(st->ft); + speex_free(st->ps); + speex_free(st->gain2); + speex_free(st->gain_floor); + speex_free(st->window); + speex_free(st->noise); + speex_free(st->reverb_estimate); + speex_free(st->old_ps); + speex_free(st->gain); + speex_free(st->prior); + speex_free(st->post); +#ifndef FIXED_POINT + speex_free(st->loudness_weight); +#endif + speex_free(st->echo_noise); + speex_free(st->residual_echo); + + speex_free(st->S); + speex_free(st->Smin); + speex_free(st->Stmp); + speex_free(st->update_prob); + speex_free(st->zeta); + + speex_free(st->inbuf); + speex_free(st->outbuf); + + spx_fft_destroy(st->fft_lookup); + filterbank_destroy(st->bank); + speex_free(st); +} + +/* FIXME: The AGC doesn't work yet with fixed-point*/ +#ifndef FIXED_POINT +static void speex_compute_agc(SpeexPreprocessState *st, spx_word16_t Pframe, spx_word16_t *ft) +{ + int i; + int N = st->ps_size; + float target_gain; + float loudness=1.f; + float rate; + + for (i=2;ips[i]* st->loudness_weight[i]; + } + loudness=sqrt(loudness); + /*if (loudness < 2*pow(st->loudness, 1.0/LOUDNESS_EXP) && + loudness*2 > pow(st->loudness, 1.0/LOUDNESS_EXP))*/ + if (Pframe>.3f) + { + /*rate=2.0f*Pframe*Pframe/(1+st->nb_loudness_adapt);*/ + rate = .03*Pframe*Pframe; + st->loudness = (1-rate)*st->loudness + (rate)*pow(AMP_SCALE*loudness, LOUDNESS_EXP); + st->loudness_accum = (1-rate)*st->loudness_accum + rate; + if (st->init_max < st->max_gain && st->nb_adapt > 20) + st->init_max *= 1.f + .1f*Pframe*Pframe; + } + /*printf ("%f %f %f %f\n", Pframe, loudness, pow(st->loudness, 1.0f/LOUDNESS_EXP), st->loudness2);*/ + + target_gain = AMP_SCALE*st->agc_level*pow(st->loudness/(1e-4+st->loudness_accum), -1.0f/LOUDNESS_EXP); + + if ((Pframe>.5 && st->nb_adapt > 20) || target_gain < st->agc_gain) + { + if (target_gain > st->max_increase_step*st->agc_gain) + target_gain = st->max_increase_step*st->agc_gain; + if (target_gain < st->max_decrease_step*st->agc_gain && loudness < 10*st->prev_loudness) + target_gain = st->max_decrease_step*st->agc_gain; + if (target_gain > st->max_gain) + target_gain = st->max_gain; + if (target_gain > st->init_max) + target_gain = st->init_max; + + st->agc_gain = target_gain; + } + /*fprintf (stderr, "%f %f %f\n", loudness, (float)AMP_SCALE_1*pow(st->loudness, 1.0f/LOUDNESS_EXP), st->agc_gain);*/ + + for (i=0;i<2*N;i++) + ft[i] *= st->agc_gain; + st->prev_loudness = loudness; +} +#endif + +static void preprocess_analysis(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int N4 = st->frame_size - N3; + spx_word32_t *ps=st->ps; + + /* 'Build' input frame */ + for (i=0;iframe[i]=st->inbuf[i]; + for (i=0;iframe_size;i++) + st->frame[N3+i]=x[i]; + + /* Update inbuf */ + for (i=0;iinbuf[i]=x[N4+i]; + + /* Windowing */ + for (i=0;i<2*N;i++) + st->frame[i] = MULT16_16_Q15(st->frame[i], st->window[i]); + +#ifdef FIXED_POINT + { + spx_word16_t max_val=0; + for (i=0;i<2*N;i++) + max_val = MAX16(max_val, ABS16(st->frame[i])); + st->frame_shift = 14-spx_ilog2(EXTEND32(max_val)); + for (i=0;i<2*N;i++) + st->frame[i] = SHL16(st->frame[i], st->frame_shift); + } +#endif + + /* Perform FFT */ + spx_fft(st->fft_lookup, st->frame, st->ft); + + /* Power spectrum */ + ps[0]=MULT16_16(st->ft[0],st->ft[0]); + for (i=1;ift[2*i-1],st->ft[2*i-1]) + MULT16_16(st->ft[2*i],st->ft[2*i]); + for (i=0;ips[i] = PSHR32(st->ps[i], 2*st->frame_shift); + + filterbank_compute_bank32(st->bank, ps, ps+N); +} + +static void update_noise_prob(SpeexPreprocessState *st) +{ + int i; + int min_range; + int N = st->ps_size; + + for (i=1;iS[i] = MULT16_32_Q15(QCONST16(.8f,15),st->S[i]) + MULT16_32_Q15(QCONST16(.05f,15),st->ps[i-1]) + + MULT16_32_Q15(QCONST16(.1f,15),st->ps[i]) + MULT16_32_Q15(QCONST16(.05f,15),st->ps[i+1]); + st->S[0] = MULT16_32_Q15(QCONST16(.8f,15),st->S[0]) + MULT16_32_Q15(QCONST16(.2f,15),st->ps[0]); + st->S[N-1] = MULT16_32_Q15(QCONST16(.8f,15),st->S[N-1]) + MULT16_32_Q15(QCONST16(.2f,15),st->ps[N-1]); + + if (st->nb_adapt==1) + { + for (i=0;iSmin[i] = st->Stmp[i] = 0; + } + + if (st->nb_adapt < 100) + min_range = 15; + else if (st->nb_adapt < 1000) + min_range = 50; + else if (st->nb_adapt < 10000) + min_range = 150; + else + min_range = 300; + if (st->min_count > min_range) + { + st->min_count = 0; + for (i=0;iSmin[i] = MIN32(st->Stmp[i], st->S[i]); + st->Stmp[i] = st->S[i]; + } + } else { + for (i=0;iSmin[i] = MIN32(st->Smin[i], st->S[i]); + st->Stmp[i] = MIN32(st->Stmp[i], st->S[i]); + } + } + for (i=0;iS[i]) > st->Smin[i]) + st->update_prob[i] = 1; + else + st->update_prob[i] = 0; + /*fprintf (stderr, "%f ", st->S[i]/st->Smin[i]);*/ + /*fprintf (stderr, "%f ", st->update_prob[i]);*/ + } + +} + +#define NOISE_OVERCOMPENS 1. + +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *Yout, int len); + +EXPORT int speex_preprocess(SpeexPreprocessState *st, spx_int16_t *x, spx_int32_t *echo) +{ + return speex_preprocess_run(st, x); +} + +EXPORT int speex_preprocess_run(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int M; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int N4 = st->frame_size - N3; + spx_word32_t *ps=st->ps; + spx_word32_t Zframe; + spx_word16_t Pframe; + spx_word16_t beta, beta_1; + spx_word16_t effective_echo_suppress; + + st->nb_adapt++; + if (st->nb_adapt>20000) + st->nb_adapt = 20000; + st->min_count++; + + beta = MAX16(QCONST16(.03,15),DIV32_16(Q15_ONE,st->nb_adapt)); + beta_1 = Q15_ONE-beta; + M = st->nbands; + /* Deal with residual echo if provided */ + if (st->echo_state) + { + speex_echo_get_residual(st->echo_state, st->residual_echo, N); +#ifndef FIXED_POINT + /* If there are NaNs or ridiculous values, it'll show up in the DC and we just reset everything to zero */ + if (!(st->residual_echo[0] >=0 && st->residual_echo[0]residual_echo[i] = 0; + } +#endif + for (i=0;iecho_noise[i] = MAX32(MULT16_32_Q15(QCONST16(.6f,15),st->echo_noise[i]), st->residual_echo[i]); + filterbank_compute_bank32(st->bank, st->echo_noise, st->echo_noise+N); + } else { + for (i=0;iecho_noise[i] = 0; + } + preprocess_analysis(st, x); + + update_noise_prob(st); + + /* Noise estimation always updated for the 10 first frames */ + /*if (st->nb_adapt<10) + { + for (i=1;iupdate_prob[i] = 0; + } + */ + + /* Update the noise estimate for the frequencies where it can be */ + for (i=0;iupdate_prob[i] || st->ps[i] < PSHR32(st->noise[i], NOISE_SHIFT)) + st->noise[i] = MAX32(EXTEND32(0),MULT16_32_Q15(beta_1,st->noise[i]) + MULT16_32_Q15(beta,SHL32(st->ps[i],NOISE_SHIFT))); + } + filterbank_compute_bank32(st->bank, st->noise, st->noise+N); + + /* Special case for first frame */ + if (st->nb_adapt==1) + for (i=0;iold_ps[i] = ps[i]; + + /* Compute a posteriori SNR */ + for (i=0;inoise[i],NOISE_SHIFT)) , st->echo_noise[i]) , st->reverb_estimate[i]); + + /* A posteriori SNR = ps/noise - 1*/ + st->post[i] = SUB16(DIV32_16_Q8(ps[i],tot_noise), QCONST16(1.f,SNR_SHIFT)); + st->post[i]=MIN16(st->post[i], QCONST16(100.f,SNR_SHIFT)); + + /* Computing update gamma = .1 + .9*(old/(old+noise))^2 */ + gamma = QCONST16(.1f,15)+MULT16_16_Q15(QCONST16(.89f,15),SQR16_Q15(DIV32_16_Q15(st->old_ps[i],ADD32(st->old_ps[i],tot_noise)))); + + /* A priori SNR update = gamma*max(0,post) + (1-gamma)*old/noise */ + st->prior[i] = EXTRACT16(PSHR32(ADD32(MULT16_16(gamma,MAX16(0,st->post[i])), MULT16_16(Q15_ONE-gamma,DIV32_16_Q8(st->old_ps[i],tot_noise))), 15)); + st->prior[i]=MIN16(st->prior[i], QCONST16(100.f,SNR_SHIFT)); + } + + /*print_vec(st->post, N+M, "");*/ + + /* Recursive average of the a priori SNR. A bit smoothed for the psd components */ + st->zeta[0] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[0]), MULT16_16(QCONST16(.3f,15),st->prior[0])),15); + for (i=1;izeta[i] = PSHR32(ADD32(ADD32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.15f,15),st->prior[i])), + MULT16_16(QCONST16(.075f,15),st->prior[i-1])), MULT16_16(QCONST16(.075f,15),st->prior[i+1])),15); + for (i=N-1;izeta[i] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.3f,15),st->prior[i])),15); + + /* Speech probability of presence for the entire frame is based on the average filterbank a priori SNR */ + Zframe = 0; + for (i=N;izeta[i])); + Pframe = QCONST16(.1f,15)+MULT16_16_Q15(QCONST16(.899f,15),qcurve(DIV32_16(Zframe,st->nbands))); + + effective_echo_suppress = EXTRACT16(PSHR32(ADD32(MULT16_16(SUB16(Q15_ONE,Pframe), st->echo_suppress), MULT16_16(Pframe, st->echo_suppress_active)),15)); + + compute_gain_floor(st->noise_suppress, effective_echo_suppress, st->noise+N, st->echo_noise+N, st->gain_floor+N, M); + + /* Compute Ephraim & Malah gain speech probability of presence for each critical band (Bark scale) + Technically this is actually wrong because the EM gaim assumes a slightly different probability + distribution */ + for (i=N;iprior[i]), 15), ADD16(st->prior[i], SHL32(1,SNR_SHIFT))); + theta = MULT16_32_P15(prior_ratio, QCONST32(1.f,EXPIN_SHIFT)+SHL32(EXTEND32(st->post[i]),EXPIN_SHIFT-SNR_SHIFT)); + + MM = hypergeom_gain(theta); + /* Gain with bound */ + st->gain[i] = EXTRACT16(MIN32(Q15_ONE, MULT16_32_Q15(prior_ratio, MM))); + /* Save old Bark power spectrum */ + st->old_ps[i] = MULT16_32_P15(QCONST16(.2f,15),st->old_ps[i]) + MULT16_32_P15(MULT16_16_P15(QCONST16(.8f,15),SQR16_Q15(st->gain[i])),ps[i]); + + P1 = QCONST16(.199f,15)+MULT16_16_Q15(QCONST16(.8f,15),qcurve (st->zeta[i])); + q = Q15_ONE-MULT16_16_Q15(Pframe,P1); +#ifdef FIXED_POINT + theta = MIN32(theta, EXTEND32(32767)); +/*Q8*/tmp = MULT16_16_Q15((SHL32(1,SNR_SHIFT)+st->prior[i]),EXTRACT16(MIN32(Q15ONE,SHR32(spx_exp(-EXTRACT16(theta)),1)))); + tmp = MIN16(QCONST16(3.,SNR_SHIFT), tmp); /* Prevent overflows in the next line*/ +/*Q8*/tmp = EXTRACT16(PSHR32(MULT16_16(PDIV32_16(SHL32(EXTEND32(q),8),(Q15_ONE-q)),tmp),8)); + st->gain2[i]=DIV32_16(SHL32(EXTEND32(32767),SNR_SHIFT), ADD16(256,tmp)); +#else + st->gain2[i]=1/(1.f + (q/(1.f-q))*(1+st->prior[i])*exp(-theta)); +#endif + } + /* Convert the EM gains and speech prob to linear frequency */ + filterbank_compute_psd16(st->bank,st->gain2+N, st->gain2); + filterbank_compute_psd16(st->bank,st->gain+N, st->gain); + + /* Use 1 for linear gain resolution (best) or 0 for Bark gain resolution (faster) */ + if (1) + { + filterbank_compute_psd16(st->bank,st->gain_floor+N, st->gain_floor); + + /* Compute gain according to the Ephraim-Malah algorithm -- linear frequency */ + for (i=0;iprior[i]), 15), ADD16(st->prior[i], SHL32(1,SNR_SHIFT))); + theta = MULT16_32_P15(prior_ratio, QCONST32(1.f,EXPIN_SHIFT)+SHL32(EXTEND32(st->post[i]),EXPIN_SHIFT-SNR_SHIFT)); + + /* Optimal estimator for loudness domain */ + MM = hypergeom_gain(theta); + /* EM gain with bound */ + g = EXTRACT16(MIN32(Q15_ONE, MULT16_32_Q15(prior_ratio, MM))); + /* Interpolated speech probability of presence */ + p = st->gain2[i]; + + /* Constrain the gain to be close to the Bark scale gain */ + if (MULT16_16_Q15(QCONST16(.333f,15),g) > st->gain[i]) + g = MULT16_16(3,st->gain[i]); + st->gain[i] = g; + + /* Save old power spectrum */ + st->old_ps[i] = MULT16_32_P15(QCONST16(.2f,15),st->old_ps[i]) + MULT16_32_P15(MULT16_16_P15(QCONST16(.8f,15),SQR16_Q15(st->gain[i])),ps[i]); + + /* Apply gain floor */ + if (st->gain[i] < st->gain_floor[i]) + st->gain[i] = st->gain_floor[i]; + + /* Exponential decay model for reverberation (unused) */ + /*st->reverb_estimate[i] = st->reverb_decay*st->reverb_estimate[i] + st->reverb_decay*st->reverb_level*st->gain[i]*st->gain[i]*st->ps[i];*/ + + /* Take into account speech probability of presence (loudness domain MMSE estimator) */ + /* gain2 = [p*sqrt(gain)+(1-p)*sqrt(gain _floor) ]^2 */ + tmp = MULT16_16_P15(p,spx_sqrt(SHL32(EXTEND32(st->gain[i]),15))) + MULT16_16_P15(SUB16(Q15_ONE,p),spx_sqrt(SHL32(EXTEND32(st->gain_floor[i]),15))); + st->gain2[i]=SQR16_Q15(tmp); + + /* Use this if you want a log-domain MMSE estimator instead */ + /*st->gain2[i] = pow(st->gain[i], p) * pow(st->gain_floor[i],1.f-p);*/ + } + } else { + for (i=N;igain2[i]; + st->gain[i] = MAX16(st->gain[i], st->gain_floor[i]); + tmp = MULT16_16_P15(p,spx_sqrt(SHL32(EXTEND32(st->gain[i]),15))) + MULT16_16_P15(SUB16(Q15_ONE,p),spx_sqrt(SHL32(EXTEND32(st->gain_floor[i]),15))); + st->gain2[i]=SQR16_Q15(tmp); + } + filterbank_compute_psd16(st->bank,st->gain2+N, st->gain2); + } + + /* If noise suppression is off, don't apply the gain (but then why call this in the first place!) */ + if (!st->denoise_enabled) + { + for (i=0;igain2[i]=Q15_ONE; + } + + /* Apply computed gain */ + for (i=1;ift[2*i-1] = MULT16_16_P15(st->gain2[i],st->ft[2*i-1]); + st->ft[2*i] = MULT16_16_P15(st->gain2[i],st->ft[2*i]); + } + st->ft[0] = MULT16_16_P15(st->gain2[0],st->ft[0]); + st->ft[2*N-1] = MULT16_16_P15(st->gain2[N-1],st->ft[2*N-1]); + + /*FIXME: This *will* not work for fixed-point */ +#ifndef FIXED_POINT + if (st->agc_enabled) + speex_compute_agc(st, Pframe, st->ft); +#endif + + /* Inverse FFT with 1/N scaling */ + spx_ifft(st->fft_lookup, st->ft, st->frame); + /* Scale back to original (lower) amplitude */ + for (i=0;i<2*N;i++) + st->frame[i] = PSHR16(st->frame[i], st->frame_shift); + + /*FIXME: This *will* not work for fixed-point */ +#ifndef FIXED_POINT + if (st->agc_enabled) + { + float max_sample=0; + for (i=0;i<2*N;i++) + if (fabs(st->frame[i])>max_sample) + max_sample = fabs(st->frame[i]); + if (max_sample>28000.f) + { + float damp = 28000.f/max_sample; + for (i=0;i<2*N;i++) + st->frame[i] *= damp; + } + } +#endif + + /* Synthesis window (for WOLA) */ + for (i=0;i<2*N;i++) + st->frame[i] = MULT16_16_Q15(st->frame[i], st->window[i]); + + /* Perform overlap and add */ + for (i=0;ioutbuf[i] + st->frame[i]; + for (i=0;iframe[N3+i]; + + /* Update outbuf */ + for (i=0;ioutbuf[i] = st->frame[st->frame_size+i]; + + /* FIXME: This VAD is a kludge */ + st->speech_prob = Pframe; + if (st->vad_enabled) + { + if (st->speech_prob > st->speech_prob_start || (st->was_speech && st->speech_prob > st->speech_prob_continue)) + { + st->was_speech=1; + return 1; + } else + { + st->was_speech=0; + return 0; + } + } else { + return 1; + } +} + +EXPORT void speex_preprocess_estimate_update(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int M; + spx_word32_t *ps=st->ps; + + M = st->nbands; + st->min_count++; + + preprocess_analysis(st, x); + + update_noise_prob(st); + + for (i=1;iupdate_prob[i] || st->ps[i] < PSHR32(st->noise[i],NOISE_SHIFT)) + { + st->noise[i] = MULT16_32_Q15(QCONST16(.95f,15),st->noise[i]) + MULT16_32_Q15(QCONST16(.05f,15),SHL32(st->ps[i],NOISE_SHIFT)); + } + } + + for (i=0;ioutbuf[i] = MULT16_16_Q15(x[st->frame_size-N3+i],st->window[st->frame_size+i]); + + /* Save old power spectrum */ + for (i=0;iold_ps[i] = ps[i]; + + for (i=0;ireverb_estimate[i] = MULT16_32_Q15(st->reverb_decay, st->reverb_estimate[i]); +} + + +EXPORT int speex_preprocess_ctl(SpeexPreprocessState *state, int request, void *ptr) +{ + int i; + SpeexPreprocessState *st; + st=(SpeexPreprocessState*)state; + switch(request) + { + case SPEEX_PREPROCESS_SET_DENOISE: + st->denoise_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_DENOISE: + (*(spx_int32_t*)ptr) = st->denoise_enabled; + break; +#ifndef FIXED_POINT + case SPEEX_PREPROCESS_SET_AGC: + st->agc_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_AGC: + (*(spx_int32_t*)ptr) = st->agc_enabled; + break; +#ifndef DISABLE_FLOAT_API + case SPEEX_PREPROCESS_SET_AGC_LEVEL: + st->agc_level = (*(float*)ptr); + if (st->agc_level<1) + st->agc_level=1; + if (st->agc_level>32768) + st->agc_level=32768; + break; + case SPEEX_PREPROCESS_GET_AGC_LEVEL: + (*(float*)ptr) = st->agc_level; + break; +#endif /* #ifndef DISABLE_FLOAT_API */ + case SPEEX_PREPROCESS_SET_AGC_INCREMENT: + st->max_increase_step = exp(0.11513f * (*(spx_int32_t*)ptr)*st->frame_size / st->sampling_rate); + break; + case SPEEX_PREPROCESS_GET_AGC_INCREMENT: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_increase_step)*st->sampling_rate/st->frame_size); + break; + case SPEEX_PREPROCESS_SET_AGC_DECREMENT: + st->max_decrease_step = exp(0.11513f * (*(spx_int32_t*)ptr)*st->frame_size / st->sampling_rate); + break; + case SPEEX_PREPROCESS_GET_AGC_DECREMENT: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_decrease_step)*st->sampling_rate/st->frame_size); + break; + case SPEEX_PREPROCESS_SET_AGC_MAX_GAIN: + st->max_gain = exp(0.11513f * (*(spx_int32_t*)ptr)); + break; + case SPEEX_PREPROCESS_GET_AGC_MAX_GAIN: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_gain)); + break; +#endif + case SPEEX_PREPROCESS_SET_VAD: + speex_warning("The VAD has been replaced by a hack pending a complete rewrite"); + st->vad_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; + + case SPEEX_PREPROCESS_SET_DEREVERB: + st->dereverb_enabled = (*(spx_int32_t*)ptr); + for (i=0;ips_size;i++) + st->reverb_estimate[i]=0; + break; + case SPEEX_PREPROCESS_GET_DEREVERB: + (*(spx_int32_t*)ptr) = st->dereverb_enabled; + break; + + case SPEEX_PREPROCESS_SET_DEREVERB_LEVEL: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*st->reverb_level = (*(float*)ptr);*/ + break; + case SPEEX_PREPROCESS_GET_DEREVERB_LEVEL: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*(*(float*)ptr) = st->reverb_level;*/ + break; + + case SPEEX_PREPROCESS_SET_DEREVERB_DECAY: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*st->reverb_decay = (*(float*)ptr);*/ + break; + case SPEEX_PREPROCESS_GET_DEREVERB_DECAY: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*(*(float*)ptr) = st->reverb_decay;*/ + break; + + case SPEEX_PREPROCESS_SET_PROB_START: + *(spx_int32_t*)ptr = MIN32(100,MAX32(0, *(spx_int32_t*)ptr)); + st->speech_prob_start = DIV32_16(MULT16_16(Q15ONE,*(spx_int32_t*)ptr), 100); + break; + case SPEEX_PREPROCESS_GET_PROB_START: + (*(spx_int32_t*)ptr) = MULT16_16_Q15(st->speech_prob_start, 100); + break; + + case SPEEX_PREPROCESS_SET_PROB_CONTINUE: + *(spx_int32_t*)ptr = MIN32(100,MAX32(0, *(spx_int32_t*)ptr)); + st->speech_prob_continue = DIV32_16(MULT16_16(Q15ONE,*(spx_int32_t*)ptr), 100); + break; + case SPEEX_PREPROCESS_GET_PROB_CONTINUE: + (*(spx_int32_t*)ptr) = MULT16_16_Q15(st->speech_prob_continue, 100); + break; + + case SPEEX_PREPROCESS_SET_NOISE_SUPPRESS: + st->noise_suppress = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_NOISE_SUPPRESS: + (*(spx_int32_t*)ptr) = st->noise_suppress; + break; + case SPEEX_PREPROCESS_SET_ECHO_SUPPRESS: + st->echo_suppress = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_ECHO_SUPPRESS: + (*(spx_int32_t*)ptr) = st->echo_suppress; + break; + case SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE: + st->echo_suppress_active = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_ECHO_SUPPRESS_ACTIVE: + (*(spx_int32_t*)ptr) = st->echo_suppress_active; + break; + case SPEEX_PREPROCESS_SET_ECHO_STATE: + st->echo_state = (SpeexEchoState*)ptr; + break; + case SPEEX_PREPROCESS_GET_ECHO_STATE: + (*(SpeexEchoState**)ptr) = (SpeexEchoState*)st->echo_state; + break; +#ifndef FIXED_POINT + case SPEEX_PREPROCESS_GET_AGC_LOUDNESS: + (*(spx_int32_t*)ptr) = pow(st->loudness, 1.0/LOUDNESS_EXP); + break; + case SPEEX_PREPROCESS_GET_AGC_GAIN: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->agc_gain)); + break; +#endif + case SPEEX_PREPROCESS_GET_PSD_SIZE: + case SPEEX_PREPROCESS_GET_NOISE_PSD_SIZE: + (*(spx_int32_t*)ptr) = st->ps_size; + break; + case SPEEX_PREPROCESS_GET_PSD: + for(i=0;ips_size;i++) + ((spx_int32_t *)ptr)[i] = (spx_int32_t) st->ps[i]; + break; + case SPEEX_PREPROCESS_GET_NOISE_PSD: + for(i=0;ips_size;i++) + ((spx_int32_t *)ptr)[i] = (spx_int32_t) PSHR32(st->noise[i], NOISE_SHIFT); + break; + case SPEEX_PREPROCESS_GET_PROB: + (*(spx_int32_t*)ptr) = MULT16_16_Q15(st->speech_prob, 100); + break; +#ifndef FIXED_POINT + case SPEEX_PREPROCESS_SET_AGC_TARGET: + st->agc_level = (*(spx_int32_t*)ptr); + if (st->agc_level<1) + st->agc_level=1; + if (st->agc_level>32768) + st->agc_level=32768; + break; + case SPEEX_PREPROCESS_GET_AGC_TARGET: + (*(spx_int32_t*)ptr) = st->agc_level; + break; +#endif + default: + speex_warning_int("Unknown speex_preprocess_ctl request: ", request); + return -1; + } + return 0; +} + +#ifdef FIXED_DEBUG +long long spx_mips=0; +#endif + diff --git a/Libraries/speex/pseudofloat.h b/Libraries/speex/pseudofloat.h new file mode 100644 index 000000000..fa841a010 --- /dev/null +++ b/Libraries/speex/pseudofloat.h @@ -0,0 +1,379 @@ +/* Copyright (C) 2005 Jean-Marc Valin */ +/** + @file pseudofloat.h + @brief Pseudo-floating point + * This header file provides a lightweight floating point type for + * use on fixed-point platforms when a large dynamic range is + * required. The new type is not compatible with the 32-bit IEEE format, + * it is not even remotely as accurate as 32-bit floats, and is not + * even guaranteed to produce even remotely correct results for code + * other than Speex. It makes all kinds of shortcuts that are acceptable + * for Speex, but may not be acceptable for your application. You're + * quite welcome to reuse this code and improve it, but don't assume + * it works out of the box. Most likely, it doesn't. + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef PSEUDOFLOAT_H +#define PSEUDOFLOAT_H + +#include "arch.h" +#include "os_support.h" +#include "math_approx.h" +#include + +#ifdef FIXED_POINT + +typedef struct { + spx_int16_t m; + spx_int16_t e; +} spx_float_t; + +static const spx_float_t FLOAT_ZERO = {0,0}; +static const spx_float_t FLOAT_ONE = {16384,-14}; +static const spx_float_t FLOAT_HALF = {16384,-15}; + +#define MIN(a,b) ((a)<(b)?(a):(b)) +static inline spx_float_t PSEUDOFLOAT(spx_int32_t x) +{ + int e=0; + int sign=0; + if (x<0) + { + sign = 1; + x = -x; + } + if (x==0) + { + spx_float_t r = {0,0}; + return r; + } + e = spx_ilog2(ABS32(x))-14; + x = VSHR32(x, e); + if (sign) + { + spx_float_t r; + r.m = -x; + r.e = e; + return r; + } + else + { + spx_float_t r; + r.m = x; + r.e = e; + return r; + } +} + + +static inline spx_float_t FLOAT_ADD(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + if (a.m==0) + return b; + else if (b.m==0) + return a; + if ((a).e > (b).e) + { + r.m = ((a).m>>1) + ((b).m>>MIN(15,(a).e-(b).e+1)); + r.e = (a).e+1; + } + else + { + r.m = ((b).m>>1) + ((a).m>>MIN(15,(b).e-(a).e+1)); + r.e = (b).e+1; + } + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f + %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline spx_float_t FLOAT_SUB(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + if (a.m==0) + return b; + else if (b.m==0) + return a; + if ((a).e > (b).e) + { + r.m = ((a).m>>1) - ((b).m>>MIN(15,(a).e-(b).e+1)); + r.e = (a).e+1; + } + else + { + r.m = ((a).m>>MIN(15,(b).e-(a).e+1)) - ((b).m>>1); + r.e = (b).e+1; + } + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f + %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline int FLOAT_LT(spx_float_t a, spx_float_t b) +{ + if (a.m==0) + return b.m>0; + else if (b.m==0) + return a.m<0; + if ((a).e > (b).e) + return ((a).m>>1) < ((b).m>>MIN(15,(a).e-(b).e+1)); + else + return ((b).m>>1) > ((a).m>>MIN(15,(b).e-(a).e+1)); + +} + +static inline int FLOAT_GT(spx_float_t a, spx_float_t b) +{ + return FLOAT_LT(b,a); +} + +static inline spx_float_t FLOAT_MULT(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + r.m = (spx_int16_t)((spx_int32_t)(a).m*(b).m>>15); + r.e = (a).e+(b).e+15; + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f * %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline spx_float_t FLOAT_AMULT(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + r.m = (spx_int16_t)((spx_int32_t)(a).m*(b).m>>15); + r.e = (a).e+(b).e+15; + return r; +} + + +static inline spx_float_t FLOAT_SHL(spx_float_t a, int b) +{ + spx_float_t r; + r.m = a.m; + r.e = a.e+b; + return r; +} + +static inline spx_int16_t FLOAT_EXTRACT16(spx_float_t a) +{ + if (a.e<0) + return EXTRACT16((EXTEND32(a.m)+(EXTEND32(1)<<(-a.e-1)))>>-a.e); + else + return a.m<>-a.e; + else + return EXTEND32(a.m)<=SHL32(EXTEND32(b.m-1),15)) + { + a >>= 1; + e++; + } + r.m = DIV32_16(a,b.m); + r.e = e-b.e; + return r; +} + + +/* Do NOT attempt to divide by a negative number */ +static inline spx_float_t FLOAT_DIV32(spx_word32_t a, spx_word32_t b) +{ + int e0=0,e=0; + spx_float_t r; + if (a==0) + { + return FLOAT_ZERO; + } + if (b>32767) + { + e0 = spx_ilog2(b)-14; + b = VSHR32(b, e0); + e0 = -e0; + } + e = spx_ilog2(ABS32(a))-spx_ilog2(b-1)-15; + a = VSHR32(a, e); + if (ABS32(a)>=SHL32(EXTEND32(b-1),15)) + { + a >>= 1; + e++; + } + e += e0; + r.m = DIV32_16(a,b); + r.e = e; + return r; +} + +/* Do NOT attempt to divide by a negative number */ +static inline spx_float_t FLOAT_DIVU(spx_float_t a, spx_float_t b) +{ + int e=0; + spx_int32_t num; + spx_float_t r; + if (b.m<=0) + { + speex_warning_int("Attempted to divide by", b.m); + return FLOAT_ONE; + } + num = a.m; + a.m = ABS16(a.m); + while (a.m >= b.m) + { + e++; + a.m >>= 1; + } + num = num << (15-e); + r.m = DIV32_16(num,b.m); + r.e = a.e-b.e-15+e; + return r; +} + +static inline spx_float_t FLOAT_SQRT(spx_float_t a) +{ + spx_float_t r; + spx_int32_t m; + m = SHL32(EXTEND32(a.m), 14); + r.e = a.e - 14; + if (r.e & 1) + { + r.e -= 1; + m <<= 1; + } + r.e >>= 1; + r.m = spx_sqrt(m); + return r; +} + +#else + +#define spx_float_t float +#define FLOAT_ZERO 0.f +#define FLOAT_ONE 1.f +#define FLOAT_HALF 0.5f +#define PSEUDOFLOAT(x) (x) +#define FLOAT_MULT(a,b) ((a)*(b)) +#define FLOAT_AMULT(a,b) ((a)*(b)) +#define FLOAT_MUL32(a,b) ((a)*(b)) +#define FLOAT_DIV32(a,b) ((a)/(b)) +#define FLOAT_EXTRACT16(a) (a) +#define FLOAT_EXTRACT32(a) (a) +#define FLOAT_ADD(a,b) ((a)+(b)) +#define FLOAT_SUB(a,b) ((a)-(b)) +#define REALFLOAT(x) (x) +#define FLOAT_DIV32_FLOAT(a,b) ((a)/(b)) +#define FLOAT_MUL32U(a,b) ((a)*(b)) +#define FLOAT_SHL(a,b) (a) +#define FLOAT_LT(a,b) ((a)<(b)) +#define FLOAT_GT(a,b) ((a)>(b)) +#define FLOAT_DIVU(a,b) ((a)/(b)) +#define FLOAT_SQRT(a) (spx_sqrt(a)) + +#endif + +#endif diff --git a/Libraries/speex/quant_lsp.c b/Libraries/speex/quant_lsp.c new file mode 100644 index 000000000..e624d1a28 --- /dev/null +++ b/Libraries/speex/quant_lsp.c @@ -0,0 +1,385 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: quant_lsp.c + LSP vector quantization + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "quant_lsp.h" +#include "os_support.h" +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#include "arch.h" + +#ifdef BFIN_ASM +#include "quant_lsp_bfin.h" +#endif + +#ifdef FIXED_POINT + +#define LSP_LINEAR(i) (SHL16(i+1,11)) +#define LSP_LINEAR_HIGH(i) (ADD16(MULT16_16_16(i,2560),6144)) +#define LSP_DIV_256(x) (SHL16((spx_word16_t)x, 5)) +#define LSP_DIV_512(x) (SHL16((spx_word16_t)x, 4)) +#define LSP_DIV_1024(x) (SHL16((spx_word16_t)x, 3)) +#define LSP_PI 25736 + +#else + +#define LSP_LINEAR(i) (.25*(i)+.25) +#define LSP_LINEAR_HIGH(i) (.3125*(i)+.75) +#define LSP_SCALE 256. +#define LSP_DIV_256(x) (0.0039062*(x)) +#define LSP_DIV_512(x) (0.0019531*(x)) +#define LSP_DIV_1024(x) (0.00097656*(x)) +#define LSP_PI M_PI + +#endif + +static void compute_quant_weights(spx_lsp_t *qlsp, spx_word16_t *quant_weight, int order) +{ + int i; + spx_word16_t tmp1, tmp2; + for (i=0;i tmp2 ? tmp1 : tmp2; + }*/ + + for (i=0;i +#include "arch.h" + +#define MAX_LSP_SIZE 20 + +#define NB_CDBK_SIZE 64 +#define NB_CDBK_SIZE_LOW1 64 +#define NB_CDBK_SIZE_LOW2 64 +#define NB_CDBK_SIZE_HIGH1 64 +#define NB_CDBK_SIZE_HIGH2 64 + +/*Narrowband codebooks*/ +extern const signed char cdbk_nb[]; +extern const signed char cdbk_nb_low1[]; +extern const signed char cdbk_nb_low2[]; +extern const signed char cdbk_nb_high1[]; +extern const signed char cdbk_nb_high2[]; + +/* Quantizes narrowband LSPs with 30 bits */ +void lsp_quant_nb(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes quantized narrowband LSPs */ +void lsp_unquant_nb(spx_lsp_t *lsp, int order, SpeexBits *bits); + +/* Quantizes low bit-rate narrowband LSPs with 18 bits */ +void lsp_quant_lbr(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes quantized low bit-rate narrowband LSPs */ +void lsp_unquant_lbr(spx_lsp_t *lsp, int order, SpeexBits *bits); + +/* Quantizes high-band LSPs with 12 bits */ +void lsp_quant_high(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes high-band LSPs */ +void lsp_unquant_high(spx_lsp_t *lsp, int order, SpeexBits *bits); + +#endif diff --git a/Libraries/speex/quant_lsp_bfin.h b/Libraries/speex/quant_lsp_bfin.h new file mode 100644 index 000000000..087b466b7 --- /dev/null +++ b/Libraries/speex/quant_lsp_bfin.h @@ -0,0 +1,165 @@ +/* Copyright (C) 2006 David Rowe */ +/** + @file quant_lsp_bfin.h + @author David Rowe + @brief Various compatibility routines for Speex (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_LSP_QUANT +#ifdef OVERRIDE_LSP_QUANT + +/* + Note http://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html + well tell you all the magic resgister constraints used below + for gcc in-line asm. +*/ + +static int lsp_quant( + spx_word16_t *x, + const signed char *cdbk, + int nbVec, + int nbDim +) +{ + int j; + spx_word32_t best_dist=1<<30; + int best_id=0; + + __asm__ __volatile__ + ( +" %0 = 1 (X);\n\t" /* %0: best_dist */ +" %0 <<= 30;\n\t" +" %1 = 0 (X);\n\t" /* %1: best_i */ +" P2 = %3\n\t" /* P2: ptr to cdbk */ +" R5 = 0;\n\t" /* R5: best cb entry */ + +" R0 = %5;\n\t" /* set up circ addr */ +" R0 <<= 1;\n\t" +" L0 = R0;\n\t" +" I0 = %2;\n\t" /* %2: &x[0] */ +" B0 = %2;\n\t" + +" R2.L = W [I0++];\n\t" +" LSETUP (1f, 2f) LC0 = %4;\n\t" +"1: R3 = 0;\n\t" /* R3: dist */ +" LSETUP (3f, 4f) LC1 = %5;\n\t" +"3: R1 = B [P2++] (X);\n\t" +" R1 <<= 5;\n\t" +" R0.L = R2.L - R1.L || R2.L = W [I0++];\n\t" +" R0 = R0.L*R0.L;\n\t" +"4: R3 = R3 + R0;\n\t" + +" cc =R3<%0;\n\t" +" if cc %0=R3;\n\t" +" if cc %1=R5;\n\t" +"2: R5 += 1;\n\t" +" L0 = 0;\n\t" + : "=&d" (best_dist), "=&d" (best_id) + : "a" (x), "b" (cdbk), "a" (nbVec), "a" (nbDim) + : "I0", "P2", "R0", "R1", "R2", "R3", "R5", "L0", "B0", "A0" + ); + + for (j=0;j>> 16;\n\t" +" R1 = (A1 += R2.L*R0.H) (IS);\n\t" +"4: R3 = R3 + R1;\n\t" + +" cc =R3<%0;\n\t" +" if cc %0=R3;\n\t" +" if cc %1=R5;\n\t" +"2: R5 += 1;\n\t" +" L0 = 0;\n\t" +" L1 = 0;\n\t" + : "=&d" (best_dist), "=&d" (best_id) + : "a" (x), "a" (weight), "b" (cdbk), "a" (nbVec), "a" (nbDim) + : "I0", "I1", "P2", "R0", "R1", "R2", "R3", "R5", "A1", + "L0", "L1", "B0", "B1" + ); + + for (j=0;j +static void *speex_alloc (int size) {return calloc(size,1);} +static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);} +static void speex_free (void *ptr) {free(ptr);} +#include "speex_resampler.h" +#include "arch.h" +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_resampler.h" +#include "arch.h" +#include "os_support.h" +#endif /* OUTSIDE_SPEEX */ + +#include "stack_alloc.h" +#include + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +#ifdef FIXED_POINT +#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) +#else +#define WORD2INT(x) ((x) < -32767.5f ? -32768 : ((x) > 32766.5f ? 32767 : floor(.5+(x)))) +#endif + +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef _USE_SSE +#include "resample_sse.h" +#endif + +/* Numer of elements to allocate on the stack */ +#ifdef VAR_ARRAYS +#define FIXED_STACK_ALLOC 8192 +#else +#define FIXED_STACK_ALLOC 1024 +#endif + +typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); + +struct SpeexResamplerState_ { + spx_uint32_t in_rate; + spx_uint32_t out_rate; + spx_uint32_t num_rate; + spx_uint32_t den_rate; + + int quality; + spx_uint32_t nb_channels; + spx_uint32_t filt_len; + spx_uint32_t mem_alloc_size; + spx_uint32_t buffer_size; + int int_advance; + int frac_advance; + float cutoff; + spx_uint32_t oversample; + int initialised; + int started; + + /* These are per-channel */ + spx_int32_t *last_sample; + spx_uint32_t *samp_frac_num; + spx_uint32_t *magic_samples; + + spx_word16_t *mem; + spx_word16_t *sinc_table; + spx_uint32_t sinc_table_length; + resampler_basic_func resampler_ptr; + + int in_stride; + int out_stride; +} ; + +static double kaiser12_table[68] = { + 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, + 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, + 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, + 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, + 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, + 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, + 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, + 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, + 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, + 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, + 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, + 0.00001000, 0.00000000}; +/* +static double kaiser12_table[36] = { + 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741, + 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762, + 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274, + 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466, + 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291, + 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000}; +*/ +static double kaiser10_table[36] = { + 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, + 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, + 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, + 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, + 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, + 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000}; + +static double kaiser8_table[36] = { + 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, + 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, + 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, + 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, + 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, + 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000}; + +static double kaiser6_table[36] = { + 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, + 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, + 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, + 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, + 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, + 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000}; + +struct FuncDef { + double *table; + int oversample; +}; + +static struct FuncDef _KAISER12 = {kaiser12_table, 64}; +#define KAISER12 (&_KAISER12) +/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; +#define KAISER12 (&_KAISER12)*/ +static struct FuncDef _KAISER10 = {kaiser10_table, 32}; +#define KAISER10 (&_KAISER10) +static struct FuncDef _KAISER8 = {kaiser8_table, 32}; +#define KAISER8 (&_KAISER8) +static struct FuncDef _KAISER6 = {kaiser6_table, 32}; +#define KAISER6 (&_KAISER6) + +struct QualityMapping { + int base_length; + int oversample; + float downsample_bandwidth; + float upsample_bandwidth; + struct FuncDef *window_func; +}; + + +/* This table maps conversion quality to internal parameters. There are two + reasons that explain why the up-sampling bandwidth is larger than the + down-sampling bandwidth: + 1) When up-sampling, we can assume that the spectrum is already attenuated + close to the Nyquist rate (from an A/D or a previous resampling filter) + 2) Any aliasing that occurs very close to the Nyquist rate will be masked + by the sinusoids/noise just below the Nyquist rate (guaranteed only for + up-sampling). +*/ +static const struct QualityMapping quality_map[11] = { + { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */ + { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */ + { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ + { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ + { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ + { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ + { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ + {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ + {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ + {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ + {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ +}; +/*8,24,40,56,80,104,128,160,200,256,320*/ +static double compute_func(float x, struct FuncDef *func) +{ + float y, frac; + double interp[4]; + int ind; + y = x*func->oversample; + ind = (int)floor(y); + frac = (y-ind); + /* CSE with handle the repeated powers */ + interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac); + interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac); + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac); + /* Just to make sure we don't have rounding problems */ + interp[1] = 1.f-interp[3]-interp[2]-interp[0]; + + /*sum = frac*accum[1] + (1-frac)*accum[2];*/ + return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3]; +} + +#if 0 +#include +int main(int argc, char **argv) +{ + int i; + for (i=0;i<256;i++) + { + printf ("%f\n", compute_func(i/256., KAISER12)); + } + return 0; +} +#endif + +#ifdef FIXED_POINT +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6f) + return WORD2INT(32768.*cutoff); + else if (fabs(x) > .5f*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func)); +} +#else +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6) + return cutoff; + else if (fabs(x) > .5*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func); +} +#endif + +#ifdef FIXED_POINT +static void cubic_coef(spx_word16_t x, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + spx_word16_t x2, x3; + x2 = MULT16_16_P15(x, x); + x3 = MULT16_16_P15(x, x2); + interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15); + interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1)); + interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15); + /* Just to make sure we don't have rounding problems */ + interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3]; + if (interp[2]<32767) + interp[2]+=1; +} +#else +static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac; + interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac; + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac; + /* Just to make sure we don't have rounding problems */ + interp[2] = 1.-interp[0]-interp[1]-interp[3]; +} +#endif + +static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const spx_word16_t *sinc_table = st->sinc_table; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + spx_word32_t sum; + int j; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *sinc = & sinc_table[samp_frac_num*N]; + const spx_word16_t *iptr = & in[last_sample]; + +#ifndef OVERRIDE_INNER_PRODUCT_SINGLE + float accum[4] = {0,0,0,0}; + + for(j=0;j= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const spx_word16_t *sinc_table = st->sinc_table; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + double sum; + int j; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *sinc = & sinc_table[samp_frac_num*N]; + const spx_word16_t *iptr = & in[last_sample]; + +#ifndef OVERRIDE_INNER_PRODUCT_DOUBLE + double accum[4] = {0,0,0,0}; + + for(j=0;j= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + int j; + spx_word32_t sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *iptr = & in[last_sample]; + + const int offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + spx_word16_t interp[4]; + + +#ifndef OVERRIDE_INTERPOLATE_PRODUCT_SINGLE + spx_word32_t accum[4] = {0,0,0,0}; + + for(j=0;jsinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); +#else + cubic_coef(frac, interp); + sum = interpolate_product_single(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); +#endif + + out[out_stride * out_sample++] = PSHR32(sum,15); + last_sample += int_advance; + samp_frac_num += frac_advance; + if (samp_frac_num >= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + int j; + spx_word32_t sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *iptr = & in[last_sample]; + + const int offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + spx_word16_t interp[4]; + + +#ifndef OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE + double accum[4] = {0,0,0,0}; + + for(j=0;jsinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); +#else + cubic_coef(frac, interp); + sum = interpolate_product_double(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); +#endif + + out[out_stride * out_sample++] = PSHR32(sum,15); + last_sample += int_advance; + samp_frac_num += frac_advance; + if (samp_frac_num >= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static void update_filter(SpeexResamplerState *st) +{ + spx_uint32_t old_length; + + old_length = st->filt_len; + st->oversample = quality_map[st->quality].oversample; + st->filt_len = quality_map[st->quality].base_length; + + if (st->num_rate > st->den_rate) + { + /* down-sampling */ + st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; + /* FIXME: divide the numerator and denominator by a certain amount if they're too large */ + st->filt_len = st->filt_len*st->num_rate / st->den_rate; + /* Round down to make sure we have a multiple of 4 */ + st->filt_len &= (~0x3); + if (2*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (4*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (8*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (16*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (st->oversample < 1) + st->oversample = 1; + } else { + /* up-sampling */ + st->cutoff = quality_map[st->quality].upsample_bandwidth; + } + + /* Choose the resampling type that requires the least amount of memory */ + if (st->den_rate <= st->oversample) + { + spx_uint32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc(st->filt_len*st->den_rate*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->den_rate) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,st->filt_len*st->den_rate*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->den_rate; + } + for (i=0;iden_rate;i++) + { + spx_int32_t j; + for (j=0;jfilt_len;j++) + { + st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func); + } + } +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_direct_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_direct_double; + else + st->resampler_ptr = resampler_basic_direct_single; +#endif + /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/ + } else { + spx_int32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc((st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->oversample+8) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,(st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->oversample+8; + } + for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++) + st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func); +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_interpolate_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_interpolate_double; + else + st->resampler_ptr = resampler_basic_interpolate_single; +#endif + /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/ + } + st->int_advance = st->num_rate/st->den_rate; + st->frac_advance = st->num_rate%st->den_rate; + + + /* Here's the place where we update the filter memory to take into account + the change in filter length. It's probably the messiest part of the code + due to handling of lots of corner cases. */ + if (!st->mem) + { + spx_uint32_t i; + st->mem_alloc_size = st->filt_len-1 + st->buffer_size; + st->mem = (spx_word16_t*)speex_alloc(st->nb_channels*st->mem_alloc_size * sizeof(spx_word16_t)); + for (i=0;inb_channels*st->mem_alloc_size;i++) + st->mem[i] = 0; + /*speex_warning("init filter");*/ + } else if (!st->started) + { + spx_uint32_t i; + st->mem_alloc_size = st->filt_len-1 + st->buffer_size; + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*st->mem_alloc_size * sizeof(spx_word16_t)); + for (i=0;inb_channels*st->mem_alloc_size;i++) + st->mem[i] = 0; + /*speex_warning("reinit filter");*/ + } else if (st->filt_len > old_length) + { + spx_int32_t i; + /* Increase the filter length */ + /*speex_warning("increase filter size");*/ + int old_alloc_size = st->mem_alloc_size; + if ((st->filt_len-1 + st->buffer_size) > st->mem_alloc_size) + { + st->mem_alloc_size = st->filt_len-1 + st->buffer_size; + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*st->mem_alloc_size * sizeof(spx_word16_t)); + } + for (i=st->nb_channels-1;i>=0;i--) + { + spx_int32_t j; + spx_uint32_t olen = old_length; + /*if (st->magic_samples[i])*/ + { + /* Try and remove the magic samples as if nothing had happened */ + + /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ + olen = old_length + 2*st->magic_samples[i]; + for (j=old_length-2+st->magic_samples[i];j>=0;j--) + st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; + for (j=0;jmagic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = 0; + st->magic_samples[i] = 0; + } + if (st->filt_len > olen) + { + /* If the new filter length is still bigger than the "augmented" length */ + /* Copy data going backward */ + for (j=0;jmem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + /* Then put zeros for lack of anything better */ + for (;jfilt_len-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + /* Adjust last_sample */ + st->last_sample[i] += (st->filt_len - olen)/2; + } else { + /* Put back some of the magic! */ + st->magic_samples[i] = (olen - st->filt_len)/2; + for (j=0;jfilt_len-1+st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + } + } + } else if (st->filt_len < old_length) + { + spx_uint32_t i; + /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic" + samples so they can be used directly as input the next time(s) */ + for (i=0;inb_channels;i++) + { + spx_uint32_t j; + spx_uint32_t old_magic = st->magic_samples[i]; + st->magic_samples[i] = (old_length - st->filt_len)/2; + /* We must copy some of the memory that's no longer used */ + /* Copy data going backward */ + for (j=0;jfilt_len-1+st->magic_samples[i]+old_magic;j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + st->magic_samples[i] += old_magic; + } + } + +} + +EXPORT SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err); +} + +EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + spx_uint32_t i; + SpeexResamplerState *st; + if (quality > 10 || quality < 0) + { + if (err) + *err = RESAMPLER_ERR_INVALID_ARG; + return NULL; + } + st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState)); + st->initialised = 0; + st->started = 0; + st->in_rate = 0; + st->out_rate = 0; + st->num_rate = 0; + st->den_rate = 0; + st->quality = -1; + st->sinc_table_length = 0; + st->mem_alloc_size = 0; + st->filt_len = 0; + st->mem = 0; + st->resampler_ptr = 0; + + st->cutoff = 1.f; + st->nb_channels = nb_channels; + st->in_stride = 1; + st->out_stride = 1; + +#ifdef FIXED_POINT + st->buffer_size = 160; +#else + st->buffer_size = 160; +#endif + + /* Per channel data */ + st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(int)); + st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + for (i=0;ilast_sample[i] = 0; + st->magic_samples[i] = 0; + st->samp_frac_num[i] = 0; + } + + speex_resampler_set_quality(st, quality); + speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate); + + + update_filter(st); + + st->initialised = 1; + if (err) + *err = RESAMPLER_ERR_SUCCESS; + + return st; +} + +EXPORT void speex_resampler_destroy(SpeexResamplerState *st) +{ + speex_free(st->mem); + speex_free(st->sinc_table); + speex_free(st->last_sample); + speex_free(st->magic_samples); + speex_free(st->samp_frac_num); + speex_free(st); +} + +static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int j=0; + const int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + spx_uint32_t ilen; + + st->started = 1; + + /* Call the right resampler through the function ptr */ + out_sample = st->resampler_ptr(st, channel_index, mem, in_len, out, out_len); + + if (st->last_sample[channel_index] < (spx_int32_t)*in_len) + *in_len = st->last_sample[channel_index]; + *out_len = out_sample; + st->last_sample[channel_index] -= *in_len; + + ilen = *in_len; + + for(j=0;jmagic_samples[channel_index]; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + const int N = st->filt_len; + + speex_resampler_process_native(st, channel_index, &tmp_in_len, *out, &out_len); + + st->magic_samples[channel_index] -= tmp_in_len; + + /* If we couldn't process all "magic" input samples, save the rest for next time */ + if (st->magic_samples[channel_index]) + { + spx_uint32_t i; + for (i=0;imagic_samples[channel_index];i++) + mem[N-1+i]=mem[N-1+i+tmp_in_len]; + } + *out += out_len*st->out_stride; + return out_len; +} + +#ifdef FIXED_POINT +EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +#else +EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +#endif +{ + int j; + spx_uint32_t ilen = *in_len; + spx_uint32_t olen = *out_len; + spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size; + const int filt_offs = st->filt_len - 1; + const spx_uint32_t xlen = st->mem_alloc_size - filt_offs; + const int istride = st->in_stride; + + if (st->magic_samples[channel_index]) + olen -= speex_resampler_magic(st, channel_index, &out, olen); + if (! st->magic_samples[channel_index]) { + while (ilen && olen) { + spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen; + spx_uint32_t ochunk = olen; + + if (in) { + for(j=0;jout_stride; + if (in) + in += ichunk * istride; + } + } + *in_len -= ilen; + *out_len -= olen; + return RESAMPLER_ERR_SUCCESS; +} + +#ifdef FIXED_POINT +EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +#else +EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +#endif +{ + int j; + const int istride_save = st->in_stride; + const int ostride_save = st->out_stride; + spx_uint32_t ilen = *in_len; + spx_uint32_t olen = *out_len; + spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size; + const spx_uint32_t xlen = st->mem_alloc_size - (st->filt_len - 1); +#ifdef VAR_ARRAYS + const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC; + VARDECL(spx_word16_t *ystack); + ALLOC(ystack, ylen, spx_word16_t); +#else + const unsigned int ylen = FIXED_STACK_ALLOC; + spx_word16_t ystack[FIXED_STACK_ALLOC]; +#endif + + st->out_stride = 1; + + while (ilen && olen) { + spx_word16_t *y = ystack; + spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen; + spx_uint32_t ochunk = (olen > ylen) ? ylen : olen; + spx_uint32_t omagic = 0; + + if (st->magic_samples[channel_index]) { + omagic = speex_resampler_magic(st, channel_index, &y, ochunk); + ochunk -= omagic; + olen -= omagic; + } + if (! st->magic_samples[channel_index]) { + if (in) { + for(j=0;jfilt_len-1]=WORD2INT(in[j*istride_save]); +#else + x[j+st->filt_len-1]=in[j*istride_save]; +#endif + } else { + for(j=0;jfilt_len-1]=0; + } + + speex_resampler_process_native(st, channel_index, &ichunk, y, &ochunk); + } else { + ichunk = 0; + ochunk = 0; + } + + for (j=0;jout_stride = ostride_save; + *in_len -= ilen; + *out_len -= olen; + + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_len; + if (in != NULL) + speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_float(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_len; + if (in != NULL) + speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_int(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate); +} + +EXPORT void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate) +{ + *in_rate = st->in_rate; + *out_rate = st->out_rate; +} + +EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + spx_uint32_t fact; + spx_uint32_t old_den; + spx_uint32_t i; + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) + return RESAMPLER_ERR_SUCCESS; + + old_den = st->den_rate; + st->in_rate = in_rate; + st->out_rate = out_rate; + st->num_rate = ratio_num; + st->den_rate = ratio_den; + /* FIXME: This is terribly inefficient, but who cares (at least for now)? */ + for (fact=2;fact<=IMIN(st->num_rate, st->den_rate);fact++) + { + while ((st->num_rate % fact == 0) && (st->den_rate % fact == 0)) + { + st->num_rate /= fact; + st->den_rate /= fact; + } + } + + if (old_den > 0) + { + for (i=0;inb_channels;i++) + { + st->samp_frac_num[i]=st->samp_frac_num[i]*st->den_rate/old_den; + /* Safety net */ + if (st->samp_frac_num[i] >= st->den_rate) + st->samp_frac_num[i] = st->den_rate-1; + } + } + + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den) +{ + *ratio_num = st->num_rate; + *ratio_den = st->den_rate; +} + +EXPORT int speex_resampler_set_quality(SpeexResamplerState *st, int quality) +{ + if (quality > 10 || quality < 0) + return RESAMPLER_ERR_INVALID_ARG; + if (st->quality == quality) + return RESAMPLER_ERR_SUCCESS; + st->quality = quality; + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT void speex_resampler_get_quality(SpeexResamplerState *st, int *quality) +{ + *quality = st->quality; +} + +EXPORT void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->in_stride = stride; +} + +EXPORT void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->in_stride; +} + +EXPORT void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->out_stride = stride; +} + +EXPORT void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->out_stride; +} + +EXPORT int speex_resampler_get_input_latency(SpeexResamplerState *st) +{ + return st->filt_len / 2; +} + +EXPORT int speex_resampler_get_output_latency(SpeexResamplerState *st) +{ + return ((st->filt_len / 2) * st->den_rate + (st->num_rate >> 1)) / st->num_rate; +} + +EXPORT int speex_resampler_skip_zeros(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + st->last_sample[i] = st->filt_len/2; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_reset_mem(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT const char *speex_resampler_strerror(int err) +{ + switch (err) + { + case RESAMPLER_ERR_SUCCESS: + return "Success."; + case RESAMPLER_ERR_ALLOC_FAILED: + return "Memory allocation failed."; + case RESAMPLER_ERR_BAD_STATE: + return "Bad resampler state."; + case RESAMPLER_ERR_INVALID_ARG: + return "Invalid argument."; + case RESAMPLER_ERR_PTR_OVERLAP: + return "Input and output buffers overlap."; + default: + return "Unknown error. Bad error code or strange version mismatch."; + } +} diff --git a/Libraries/speex/resample_sse.h b/Libraries/speex/resample_sse.h new file mode 100644 index 000000000..4bd35a2d0 --- /dev/null +++ b/Libraries/speex/resample_sse.h @@ -0,0 +1,128 @@ +/* Copyright (C) 2007-2008 Jean-Marc Valin + * Copyright (C) 2008 Thorvald Natvig + */ +/** + @file resample_sse.h + @brief Resampler functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#define OVERRIDE_INNER_PRODUCT_SINGLE +static inline float inner_product_single(const float *a, const float *b, unsigned int len) +{ + int i; + float ret; + __m128 sum = _mm_setzero_ps(); + for (i=0;i +#define OVERRIDE_INNER_PRODUCT_DOUBLE + +static inline double inner_product_double(const float *a, const float *b, unsigned int len) +{ + int i; + double ret; + __m128d sum = _mm_setzero_pd(); + __m128 t; + for (i=0;i +#include "sb_celp.h" +#include "filters.h" +#include "lpc.h" +#include "lsp.h" +#include "stack_alloc.h" +#include "cb_search.h" +#include "quant_lsp.h" +#include "vq.h" +#include "ltp.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +/* Default size for the encoder and decoder stack (can be changed at compile time). + This does not apply when using variable-size arrays or alloca. */ +#ifndef SB_ENC_STACK +#define SB_ENC_STACK (10000*sizeof(spx_sig_t)) +#endif + +#ifndef SB_DEC_STACK +#define SB_DEC_STACK (6000*sizeof(spx_sig_t)) +#endif + + +#ifdef DISABLE_WIDEBAND +void *sb_encoder_init(const SpeexMode *m) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return NULL; +} +void sb_encoder_destroy(void *state) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); +} +int sb_encode(void *state, void *vin, SpeexBits *bits) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +void *sb_decoder_init(const SpeexMode *m) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return NULL; +} +void sb_decoder_destroy(void *state) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); +} +int sb_decode(void *state, SpeexBits *bits, void *vout) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +int sb_encoder_ctl(void *state, int request, void *ptr) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +int sb_decoder_ctl(void *state, int request, void *ptr) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +#else + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#define sqr(x) ((x)*(x)) + +#define SUBMODE(x) st->submodes[st->submodeID]->x + +#ifdef FIXED_POINT +static const spx_word16_t gc_quant_bound[16] = {125, 164, 215, 282, 370, 484, 635, 832, 1090, 1428, 1871, 2452, 3213, 4210, 5516, 7228}; +static const spx_word16_t fold_quant_bound[32] = { + 39, 44, 50, 57, 64, 73, 83, 94, + 106, 120, 136, 154, 175, 198, 225, 255, + 288, 327, 370, 420, 476, 539, 611, 692, + 784, 889, 1007, 1141, 1293, 1465, 1660, 1881}; +#define LSP_MARGIN 410 +#define LSP_DELTA1 6553 +#define LSP_DELTA2 1638 + +#else + +static const spx_word16_t gc_quant_bound[16] = { + 0.97979, 1.28384, 1.68223, 2.20426, 2.88829, 3.78458, 4.95900, 6.49787, + 8.51428, 11.15642, 14.61846, 19.15484, 25.09895, 32.88761, 43.09325, 56.46588}; +static const spx_word16_t fold_quant_bound[32] = { + 0.30498, 0.34559, 0.39161, 0.44375, 0.50283, 0.56979, 0.64565, 0.73162, + 0.82903, 0.93942, 1.06450, 1.20624, 1.36685, 1.54884, 1.75506, 1.98875, + 2.25355, 2.55360, 2.89361, 3.27889, 3.71547, 4.21018, 4.77076, 5.40598, + 6.12577, 6.94141, 7.86565, 8.91295, 10.09969, 11.44445, 12.96826, 14.69497}; + +#define LSP_MARGIN .05 +#define LSP_DELTA1 .2 +#define LSP_DELTA2 .05 + +#endif + +#define QMF_ORDER 64 + +#ifdef FIXED_POINT +static const spx_word16_t h0[64] = {2, -7, -7, 18, 15, -39, -25, 75, 35, -130, -41, 212, 38, -327, -17, 483, -32, -689, 124, 956, -283, -1307, 543, 1780, -973, -2467, 1733, 3633, -3339, -6409, 9059, 30153, 30153, 9059, -6409, -3339, 3633, 1733, -2467, -973, 1780, 543, -1307, -283, 956, 124, -689, -32, 483, -17, -327, 38, 212, -41, -130, 35, 75, -25, -39, 15, 18, -7, -7, 2}; + +#else +static const float h0[64] = { + 3.596189e-05f, -0.0001123515f, + -0.0001104587f, 0.0002790277f, + 0.0002298438f, -0.0005953563f, + -0.0003823631f, 0.00113826f, + 0.0005308539f, -0.001986177f, + -0.0006243724f, 0.003235877f, + 0.0005743159f, -0.004989147f, + -0.0002584767f, 0.007367171f, + -0.0004857935f, -0.01050689f, + 0.001894714f, 0.01459396f, + -0.004313674f, -0.01994365f, + 0.00828756f, 0.02716055f, + -0.01485397f, -0.03764973f, + 0.026447f, 0.05543245f, + -0.05095487f, -0.09779096f, + 0.1382363f, 0.4600981f, + 0.4600981f, 0.1382363f, + -0.09779096f, -0.05095487f, + 0.05543245f, 0.026447f, + -0.03764973f, -0.01485397f, + 0.02716055f, 0.00828756f, + -0.01994365f, -0.004313674f, + 0.01459396f, 0.001894714f, + -0.01050689f, -0.0004857935f, + 0.007367171f, -0.0002584767f, + -0.004989147f, 0.0005743159f, + 0.003235877f, -0.0006243724f, + -0.001986177f, 0.0005308539f, + 0.00113826f, -0.0003823631f, + -0.0005953563f, 0.0002298438f, + 0.0002790277f, -0.0001104587f, + -0.0001123515f, 3.596189e-05f +}; + +#endif + +extern const spx_word16_t lag_window[]; +extern const spx_word16_t lpc_window[]; + + +void *sb_encoder_init(const SpeexMode *m) +{ + int i; + spx_int32_t tmp; + SBEncState *st; + const SpeexSBMode *mode; + + st = (SBEncState*)speex_alloc(sizeof(SBEncState)); + if (!st) + return NULL; + st->mode = m; + mode = (const SpeexSBMode*)m->mode; + + + st->st_low = speex_encoder_init(mode->nb_mode); +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + /*st->stack = (char*)speex_alloc_scratch(SB_ENC_STACK);*/ + speex_encoder_ctl(st->st_low, SPEEX_GET_STACK, &st->stack); +#endif + + st->full_frame_size = 2*mode->frameSize; + st->frame_size = mode->frameSize; + st->subframeSize = mode->subframeSize; + st->nbSubframes = mode->frameSize/mode->subframeSize; + st->windowSize = st->frame_size+st->subframeSize; + st->lpcSize=mode->lpcSize; + + st->encode_submode = 1; + st->submodes=mode->submodes; + st->submodeSelect = st->submodeID=mode->defaultSubmode; + + tmp=9; + speex_encoder_ctl(st->st_low, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, &tmp); + + st->lpc_floor = mode->lpc_floor; + st->gamma1=mode->gamma1; + st->gamma2=mode->gamma2; + st->first=1; + + st->high=(spx_word16_t*)speex_alloc((st->windowSize-st->frame_size)*sizeof(spx_word16_t)); + + st->h0_mem=(spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + st->h1_mem=(spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + + st->window= lpc_window; + + st->lagWindow = lag_window; + + st->old_lsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->exc_rms = (spx_word16_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word16_t)); + st->innov_rms_save = NULL; + + st->mem_sp = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sp2 = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + +#ifndef DISABLE_VBR + st->vbr_quality = 8; + st->vbr_enabled = 0; + st->vbr_max = 0; + st->vbr_max_high = 20000; /* We just need a big value here */ + st->vad_enabled = 0; + st->abr_enabled = 0; + st->relative_quality=0; +#endif /* #ifndef DISABLE_VBR */ + + st->complexity=2; + speex_encoder_ctl(st->st_low, SPEEX_GET_SAMPLING_RATE, &st->sampling_rate); + st->sampling_rate*=2; +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, (st->stack-(char*)st)); +#endif + return st; +} + +void sb_encoder_destroy(void *state) +{ + SBEncState *st=(SBEncState*)state; + + speex_encoder_destroy(st->st_low); +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + /*speex_free_scratch(st->stack);*/ +#endif + + speex_free(st->high); + + speex_free(st->h0_mem); + speex_free(st->h1_mem); + + speex_free(st->old_lsp); + speex_free(st->old_qlsp); + speex_free(st->interp_qlpc); + speex_free(st->pi_gain); + speex_free(st->exc_rms); + + speex_free(st->mem_sp); + speex_free(st->mem_sp2); + speex_free(st->mem_sw); + + + speex_free(st); +} + + +int sb_encode(void *state, void *vin, SpeexBits *bits) +{ + SBEncState *st; + int i, roots, sub; + char *stack; + VARDECL(spx_mem_t *mem); + VARDECL(spx_sig_t *innov); + VARDECL(spx_word16_t *target); + VARDECL(spx_word16_t *syn_resp); + VARDECL(spx_word32_t *low_pi_gain); + spx_word16_t *low; + spx_word16_t *high; + VARDECL(spx_word16_t *low_exc_rms); + VARDECL(spx_word16_t *low_innov_rms); + const SpeexSBMode *mode; + spx_int32_t dtx; + spx_word16_t *in = (spx_word16_t*)vin; + spx_word16_t e_low=0, e_high=0; + VARDECL(spx_coef_t *lpc); + VARDECL(spx_coef_t *interp_lpc); + VARDECL(spx_coef_t *bw_lpc1); + VARDECL(spx_coef_t *bw_lpc2); + VARDECL(spx_lsp_t *lsp); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_lsp); + VARDECL(spx_lsp_t *interp_qlsp); + + st = (SBEncState*)state; + stack=st->stack; + mode = (const SpeexSBMode*)(st->mode->mode); + low = in; + high = in+st->frame_size; + + /* High-band buffering / sync with low band */ + /* Compute the two sub-bands by filtering with QMF h0*/ + qmf_decomp(in, h0, low, high, st->full_frame_size, QMF_ORDER, st->h0_mem, stack); + +#ifndef DISABLE_VBR + if (st->vbr_enabled || st->vad_enabled) + { + /* Need to compute things here before the signal is trashed by the encoder */ + /*FIXME: Are the two signals (low, high) in sync? */ + e_low = compute_rms16(low, st->frame_size); + e_high = compute_rms16(high, st->frame_size); + } +#endif /* #ifndef DISABLE_VBR */ + + ALLOC(low_innov_rms, st->nbSubframes, spx_word16_t); + speex_encoder_ctl(st->st_low, SPEEX_SET_INNOVATION_SAVE, low_innov_rms); + /* Encode the narrowband part*/ + speex_encode_native(st->st_low, low, bits); + + high = high - (st->windowSize-st->frame_size); + SPEEX_COPY(high, st->high, st->windowSize-st->frame_size); + SPEEX_COPY(st->high, &high[st->frame_size], st->windowSize-st->frame_size); + + + ALLOC(low_pi_gain, st->nbSubframes, spx_word32_t); + ALLOC(low_exc_rms, st->nbSubframes, spx_word16_t); + speex_encoder_ctl(st->st_low, SPEEX_GET_PI_GAIN, low_pi_gain); + speex_encoder_ctl(st->st_low, SPEEX_GET_EXC, low_exc_rms); + + speex_encoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, &dtx); + + if (dtx==0) + dtx=1; + else + dtx=0; + + ALLOC(lpc, st->lpcSize, spx_coef_t); + ALLOC(interp_lpc, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc1, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc2, st->lpcSize, spx_coef_t); + + ALLOC(lsp, st->lpcSize, spx_lsp_t); + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + + { + VARDECL(spx_word16_t *autocorr); + VARDECL(spx_word16_t *w_sig); + ALLOC(autocorr, st->lpcSize+1, spx_word16_t); + ALLOC(w_sig, st->windowSize, spx_word16_t); + /* Window for analysis */ + /* FIXME: This is a kludge */ + if (st->subframeSize==80) + { + for (i=0;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(high[i],st->window[i>>1]),SIG_SHIFT)); + } else { + for (i=0;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(high[i],st->window[i]),SIG_SHIFT)); + } + /* Compute auto-correlation */ + _spx_autocorr(w_sig, autocorr, st->lpcSize+1, st->windowSize); + autocorr[0] = ADD16(autocorr[0],MULT16_16_Q15(autocorr[0],st->lpc_floor)); /* Noise floor in auto-correlation domain */ + + /* Lag windowing: equivalent to filtering in the power-spectrum domain */ + for (i=0;ilpcSize+1;i++) + autocorr[i] = MULT16_16_Q14(autocorr[i],st->lagWindow[i]); + + /* Levinson-Durbin */ + _spx_lpc(lpc, autocorr, st->lpcSize); + } + + /* LPC to LSPs (x-domain) transform */ + roots=lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA1, stack); + if (roots!=st->lpcSize) + { + roots = lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA2, stack); + if (roots!=st->lpcSize) { + /*If we can't find all LSP's, do some damage control and use a flat filter*/ + for (i=0;ilpcSize;i++) + { + lsp[i]=st->old_lsp[i]; + } + } + } + +#ifndef DISABLE_VBR + /* VBR code */ + if ((st->vbr_enabled || st->vad_enabled) && !dtx) + { + float ratio; + if (st->abr_enabled) + { + float qual_change=0; + if (st->abr_drift2 * st->abr_drift > 0) + { + /* Only adapt if long-term and short-term drift are the same sign */ + qual_change = -.00001*st->abr_drift/(1+st->abr_count); + if (qual_change>.1) + qual_change=.1; + if (qual_change<-.1) + qual_change=-.1; + } + st->vbr_quality += qual_change; + if (st->vbr_quality>10) + st->vbr_quality=10; + if (st->vbr_quality<0) + st->vbr_quality=0; + } + + + ratio = 2*log((1.f+e_high)/(1.f+e_low)); + + speex_encoder_ctl(st->st_low, SPEEX_GET_RELATIVE_QUALITY, &st->relative_quality); + if (ratio<-4) + ratio=-4; + if (ratio>2) + ratio=2; + /*if (ratio>-2)*/ + if (st->vbr_enabled) + { + spx_int32_t modeid; + modeid = mode->nb_modes-1; + st->relative_quality+=1.0*(ratio+2); + if (st->relative_quality<-1) + st->relative_quality=-1; + while (modeid) + { + int v1; + float thresh; + v1=(int)floor(st->vbr_quality); + if (v1==10) + thresh = mode->vbr_thresh[modeid][v1]; + else + thresh = (st->vbr_quality-v1) * mode->vbr_thresh[modeid][v1+1] + + (1+v1-st->vbr_quality) * mode->vbr_thresh[modeid][v1]; + if (st->relative_quality >= thresh && st->sampling_rate*st->submodes[modeid]->bits_per_frame/st->full_frame_size <= st->vbr_max_high) + break; + modeid--; + } + speex_encoder_ctl(state, SPEEX_SET_HIGH_MODE, &modeid); + if (st->abr_enabled) + { + spx_int32_t bitrate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &bitrate); + st->abr_drift+=(bitrate-st->abr_enabled); + st->abr_drift2 = .95*st->abr_drift2 + .05*(bitrate-st->abr_enabled); + st->abr_count += 1.0; + } + + } else { + /* VAD only */ + int modeid; + if (st->relative_quality<2.0) + modeid=1; + else + modeid=st->submodeSelect; + /*speex_encoder_ctl(state, SPEEX_SET_MODE, &mode);*/ + st->submodeID=modeid; + + } + /*fprintf (stderr, "%f %f\n", ratio, low_qual);*/ + } +#endif /* #ifndef DISABLE_VBR */ + + if (st->encode_submode) + { + speex_bits_pack(bits, 1, 1); + if (dtx) + speex_bits_pack(bits, 0, SB_SUBMODE_BITS); + else + speex_bits_pack(bits, st->submodeID, SB_SUBMODE_BITS); + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (dtx || st->submodes[st->submodeID] == NULL) + { + for (i=0;iframe_size;i++) + high[i]=VERY_SMALL; + + for (i=0;ilpcSize;i++) + st->mem_sw[i]=0; + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(high, st->interp_qlpc, high, st->frame_size, st->lpcSize, st->mem_sp, stack); + + if (dtx) + return 0; + else + return 1; + } + + + /* LSP quantization */ + SUBMODE(lsp_quant)(lsp, qlsp, st->lpcSize, bits); + + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + ALLOC(mem, st->lpcSize, spx_mem_t); + ALLOC(syn_resp, st->subframeSize, spx_word16_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(target, st->subframeSize, spx_word16_t); + + for (sub=0;subnbSubframes;sub++) + { + VARDECL(spx_word16_t *exc); + VARDECL(spx_word16_t *res); + VARDECL(spx_word16_t *sw); + spx_word16_t *sp; + spx_word16_t filter_ratio; /*Q7*/ + int offset; + spx_word32_t rl, rh; /*Q13*/ + spx_word16_t eh=0; + + offset = st->subframeSize*sub; + sp=high+offset; + ALLOC(exc, st->subframeSize, spx_word16_t); + ALLOC(res, st->subframeSize, spx_word16_t); + ALLOC(sw, st->subframeSize, spx_word16_t); + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, sub, st->nbSubframes); + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + lsp_to_lpc(interp_qlsp, st->interp_qlpc, st->lpcSize, stack); + + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + + /* Compute mid-band (4000 Hz for wideband) response of low-band and high-band + filters */ + st->pi_gain[sub]=LPC_SCALING; + rh = LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + rh += st->interp_qlpc[i+1] - st->interp_qlpc[i]; + st->pi_gain[sub] += st->interp_qlpc[i] + st->interp_qlpc[i+1]; + } + + rl = low_pi_gain[sub]; +#ifdef FIXED_POINT + filter_ratio=EXTRACT16(SATURATE(PDIV32(SHL32(ADD32(rl,82),7),ADD32(82,rh)),32767)); +#else + filter_ratio=(rl+.01)/(rh+.01); +#endif + + /* Compute "real excitation" */ + fir_mem16(sp, st->interp_qlpc, exc, st->subframeSize, st->lpcSize, st->mem_sp2, stack); + /* Compute energy of low-band and high-band excitation */ + + eh = compute_rms16(exc, st->subframeSize); + + if (!SUBMODE(innovation_quant)) {/* 1 for spectral folding excitation, 0 for stochastic */ + spx_word32_t g; /*Q7*/ + spx_word16_t el; /*Q0*/ + el = low_innov_rms[sub]; + + /* Gain to use if we want to use the low-band excitation for high-band */ + g=PDIV32(MULT16_16(filter_ratio,eh),EXTEND32(ADD16(1,el))); + +#if 0 + { + char *tmp_stack=stack; + float *tmp_sig; + float g2; + ALLOC(tmp_sig, st->subframeSize, spx_sig_t); + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + iir_mem2(st->low_innov+offset, st->interp_qlpc, tmp_sig, st->subframeSize, st->lpcSize, mem); + g2 = compute_rms(sp, st->subframeSize)/(.01+compute_rms(tmp_sig, st->subframeSize)); + /*fprintf (stderr, "gains: %f %f\n", g, g2);*/ + g = g2; + stack = tmp_stack; + } +#endif + + /*print_vec(&g, 1, "gain factor");*/ + /* Gain quantization */ + { + int quant = scal_quant(g, fold_quant_bound, 32); + /*speex_warning_int("tata", quant);*/ + if (quant<0) + quant=0; + if (quant>31) + quant=31; + speex_bits_pack(bits, quant, 5); + } + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = eh; + } + st->exc_rms[sub] = eh; + } else { + spx_word16_t gc; /*Q7*/ + spx_word32_t scale; /*Q14*/ + spx_word16_t el; /*Q0*/ + el = low_exc_rms[sub]; /*Q0*/ + + gc = PDIV32_16(MULT16_16(filter_ratio,1+eh),1+el); + + /* This is a kludge that cleans up a historical bug */ + if (st->subframeSize==80) + gc = MULT16_16_P15(QCONST16(0.70711f,15),gc); + /*printf ("%f %f %f %f\n", el, eh, filter_ratio, gc);*/ + { + int qgc = scal_quant(gc, gc_quant_bound, 16); + speex_bits_pack(bits, qgc, 4); + gc = MULT16_16_Q15(QCONST16(0.87360,15),gc_quant_bound[qgc]); + } + if (st->subframeSize==80) + gc = MULT16_16_P14(QCONST16(1.4142f,14), gc); + + scale = SHL32(MULT16_16(PDIV32_16(SHL32(EXTEND32(gc),SIG_SHIFT-6),filter_ratio),(1+el)),6); + + compute_impulse_response(st->interp_qlpc, bw_lpc1, bw_lpc2, syn_resp, st->subframeSize, st->lpcSize, stack); + + + /* Reset excitation */ + for (i=0;isubframeSize;i++) + res[i]=VERY_SMALL; + + /* Compute zero response (ringing) of A(z/g1) / ( A(z/g2) * Aq(z) ) */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + iir_mem16(res, st->interp_qlpc, res, st->subframeSize, st->lpcSize, mem, stack); + + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(res, bw_lpc1, bw_lpc2, res, st->subframeSize, st->lpcSize, mem, stack); + + /* Compute weighted signal */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(sp, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, mem, stack); + + /* Compute target signal */ + for (i=0;isubframeSize;i++) + target[i]=SUB16(sw[i],res[i]); + + signal_div(target, target, scale, st->subframeSize); + + /* Reset excitation */ + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /*print_vec(target, st->subframeSize, "\ntarget");*/ + SUBMODE(innovation_quant)(target, st->interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov, syn_resp, bits, stack, st->complexity, SUBMODE(double_codebook)); + /*print_vec(target, st->subframeSize, "after");*/ + + signal_mul(innov, innov, scale, st->subframeSize); + + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + for (i=0;isubframeSize;i++) + target[i]=MULT16_16_P13(QCONST16(2.5f,13), target[i]); + + SUBMODE(innovation_quant)(target, st->interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov2, syn_resp, bits, stack, st->complexity, 0); + signal_mul(innov2, innov2, MULT16_32_P15(QCONST16(0.4f,15),scale), st->subframeSize); + + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i],innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i] = PSHR32(innov[i],SIG_SHIFT); + + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = MULT16_16_Q15(QCONST16(.70711f, 15), compute_rms(innov, st->subframeSize)); + } + st->exc_rms[sub] = compute_rms16(exc, st->subframeSize); + + + } + + + /*Keep the previous memory*/ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + /* Final signal synthesis from excitation */ + iir_mem16(exc, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, st->mem_sp, stack); + + /* Compute weighted signal again, from synthesized speech (not sure it's the right thing) */ + filter_mem16(sp, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, st->mem_sw, stack); + } + + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + st->first=0; + + return 1; +} + + + + + +void *sb_decoder_init(const SpeexMode *m) +{ + spx_int32_t tmp; + SBDecState *st; + const SpeexSBMode *mode; + st = (SBDecState*)speex_alloc(sizeof(SBDecState)); + if (!st) + return NULL; + st->mode = m; + mode=(const SpeexSBMode*)m->mode; + st->encode_submode = 1; + + st->st_low = speex_decoder_init(mode->nb_mode); +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + /*st->stack = (char*)speex_alloc_scratch(SB_DEC_STACK);*/ + speex_decoder_ctl(st->st_low, SPEEX_GET_STACK, &st->stack); +#endif + + st->full_frame_size = 2*mode->frameSize; + st->frame_size = mode->frameSize; + st->subframeSize = mode->subframeSize; + st->nbSubframes = mode->frameSize/mode->subframeSize; + st->lpcSize=mode->lpcSize; + speex_decoder_ctl(st->st_low, SPEEX_GET_SAMPLING_RATE, &st->sampling_rate); + st->sampling_rate*=2; + tmp=1; + speex_decoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, &tmp); + + st->submodes=mode->submodes; + st->submodeID=mode->defaultSubmode; + + st->first=1; + + st->g0_mem = (spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + st->g1_mem = (spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + + st->excBuf = (spx_word16_t*)speex_alloc((st->subframeSize)*sizeof(spx_word16_t)); + + st->old_qlsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->exc_rms = (spx_word16_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word16_t)); + st->mem_sp = (spx_mem_t*)speex_alloc((2*st->lpcSize)*sizeof(spx_mem_t)); + + st->innov_save = NULL; + + + st->lpc_enh_enabled=0; + st->seed = 1000; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, (st->stack-(char*)st)); +#endif + return st; +} + +void sb_decoder_destroy(void *state) +{ + SBDecState *st; + st = (SBDecState*)state; + speex_decoder_destroy(st->st_low); +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + /*speex_free_scratch(st->stack);*/ +#endif + + speex_free(st->g0_mem); + speex_free(st->g1_mem); + speex_free(st->excBuf); + speex_free(st->old_qlsp); + speex_free(st->interp_qlpc); + speex_free(st->pi_gain); + speex_free(st->exc_rms); + speex_free(st->mem_sp); + + speex_free(state); +} + +static void sb_decode_lost(SBDecState *st, spx_word16_t *out, int dtx, char *stack) +{ + int i; + int saved_modeid=0; + + if (dtx) + { + saved_modeid=st->submodeID; + st->submodeID=1; + } else { + bw_lpc(QCONST16(0.99f,15), st->interp_qlpc, st->interp_qlpc, st->lpcSize); + } + + st->first=1; + + + /* Final signal synthesis from excitation */ + if (!dtx) + { + st->last_ener = MULT16_16_Q15(QCONST16(.9f,15),st->last_ener); + } + for (i=0;iframe_size;i++) + out[i+st->frame_size] = speex_rand(st->last_ener, &st->seed); + + iir_mem16(out+st->frame_size, st->interp_qlpc, out+st->frame_size, st->frame_size, st->lpcSize, + st->mem_sp, stack); + + + /* Reconstruct the original */ + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + if (dtx) + { + st->submodeID=saved_modeid; + } + + return; +} + +int sb_decode(void *state, SpeexBits *bits, void *vout) +{ + int i, sub; + SBDecState *st; + int wideband; + int ret; + char *stack; + VARDECL(spx_word32_t *low_pi_gain); + VARDECL(spx_word16_t *low_exc_rms); + VARDECL(spx_coef_t *ak); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_qlsp); + spx_int32_t dtx; + const SpeexSBMode *mode; + spx_word16_t *out = (spx_word16_t*)vout; + spx_word16_t *low_innov_alias; + spx_word32_t exc_ener_sum = 0; + + st = (SBDecState*)state; + stack=st->stack; + mode = (const SpeexSBMode*)(st->mode->mode); + + low_innov_alias = out+st->frame_size; + speex_decoder_ctl(st->st_low, SPEEX_SET_INNOVATION_SAVE, low_innov_alias); + /* Decode the low-band */ + ret = speex_decode_native(st->st_low, bits, out); + + speex_decoder_ctl(st->st_low, SPEEX_GET_DTX_STATUS, &dtx); + + /* If error decoding the narrowband part, propagate error */ + if (ret!=0) + { + return ret; + } + + if (!bits) + { + sb_decode_lost(st, out, dtx, stack); + return 0; + } + + if (st->encode_submode) + { + + /*Check "wideband bit"*/ + if (speex_bits_remaining(bits)>0) + wideband = speex_bits_peek(bits); + else + wideband = 0; + if (wideband) + { + /*Regular wideband frame, read the submode*/ + wideband = speex_bits_unpack_unsigned(bits, 1); + st->submodeID = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + } else + { + /*Was a narrowband frame, set "null submode"*/ + st->submodeID = 0; + } + if (st->submodeID != 0 && st->submodes[st->submodeID] == NULL) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + if (dtx) + { + sb_decode_lost(st, out, 1, stack); + return 0; + } + + for (i=0;iframe_size;i++) + out[st->frame_size+i]=VERY_SMALL; + + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(out+st->frame_size, st->interp_qlpc, out+st->frame_size, st->frame_size, st->lpcSize, st->mem_sp, stack); + + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + + return 0; + + } + + ALLOC(low_pi_gain, st->nbSubframes, spx_word32_t); + ALLOC(low_exc_rms, st->nbSubframes, spx_word16_t); + speex_decoder_ctl(st->st_low, SPEEX_GET_PI_GAIN, low_pi_gain); + speex_decoder_ctl(st->st_low, SPEEX_GET_EXC, low_exc_rms); + + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + SUBMODE(lsp_unquant)(qlsp, st->lpcSize, bits); + + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + ALLOC(ak, st->lpcSize, spx_coef_t); + + for (sub=0;subnbSubframes;sub++) + { + VARDECL(spx_word32_t *exc); + spx_word16_t *innov_save=NULL; + spx_word16_t *sp; + spx_word16_t filter_ratio; + spx_word16_t el=0; + int offset; + spx_word32_t rl=0,rh=0; + + offset = st->subframeSize*sub; + sp=out+st->frame_size+offset; + ALLOC(exc, st->subframeSize, spx_word32_t); + /* Pointer for saving innovation */ + if (st->innov_save) + { + innov_save = st->innov_save+2*offset; + SPEEX_MEMSET(innov_save, 0, 2*st->subframeSize); + } + + /* LSP interpolation */ + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* LSP to LPC */ + lsp_to_lpc(interp_qlsp, ak, st->lpcSize, stack); + + /* Calculate reponse ratio between the low and high filter in the middle + of the band (4000 Hz) */ + + st->pi_gain[sub]=LPC_SCALING; + rh = LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + rh += ak[i+1] - ak[i]; + st->pi_gain[sub] += ak[i] + ak[i+1]; + } + + rl = low_pi_gain[sub]; +#ifdef FIXED_POINT + filter_ratio=EXTRACT16(SATURATE(PDIV32(SHL32(ADD32(rl,82),7),ADD32(82,rh)),32767)); +#else + filter_ratio=(rl+.01)/(rh+.01); +#endif + + SPEEX_MEMSET(exc, 0, st->subframeSize); + if (!SUBMODE(innovation_unquant)) + { + spx_word32_t g; + int quant; + + quant = speex_bits_unpack_unsigned(bits, 5); + g= spx_exp(MULT16_16(QCONST16(.125f,11),(quant-10))); + + g = PDIV32(g, filter_ratio); + + for (i=0;isubframeSize;i+=2) + { + exc[i]=SHL32(MULT16_32_P15(MULT16_16_Q15(mode->folding_gain,low_innov_alias[offset+i]),SHL32(g,6)),SIG_SHIFT); + exc[i+1]=NEG32(SHL32(MULT16_32_P15(MULT16_16_Q15(mode->folding_gain,low_innov_alias[offset+i+1]),SHL32(g,6)),SIG_SHIFT)); + } + + } else { + spx_word16_t gc; + spx_word32_t scale; + int qgc = speex_bits_unpack_unsigned(bits, 4); + + el = low_exc_rms[sub]; + gc = MULT16_16_Q15(QCONST16(0.87360,15),gc_quant_bound[qgc]); + + if (st->subframeSize==80) + gc = MULT16_16_P14(QCONST16(1.4142f,14),gc); + + scale = SHL32(PDIV32(SHL32(MULT16_16(gc, el),3), filter_ratio),SIG_SHIFT-3); + SUBMODE(innovation_unquant)(exc, SUBMODE(innovation_params), st->subframeSize, + bits, stack, &st->seed); + + signal_mul(exc,exc,scale,st->subframeSize); + + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + SUBMODE(innovation_unquant)(innov2, SUBMODE(innovation_params), st->subframeSize, + bits, stack, &st->seed); + signal_mul(innov2, innov2, MULT16_32_P15(QCONST16(0.4f,15),scale), st->subframeSize); + for (i=0;isubframeSize;i++) + exc[i] = ADD32(exc[i],innov2[i]); + stack = tmp_stack; + } + + } + + if (st->innov_save) + { + for (i=0;isubframeSize;i++) + innov_save[2*i]=EXTRACT16(PSHR32(exc[i],SIG_SHIFT)); + } + + iir_mem16(st->excBuf, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, + st->mem_sp, stack); + for (i=0;isubframeSize;i++) + st->excBuf[i]=EXTRACT16(PSHR32(exc[i],SIG_SHIFT)); + for (i=0;ilpcSize;i++) + st->interp_qlpc[i] = ak[i]; + st->exc_rms[sub] = compute_rms16(st->excBuf, st->subframeSize); + exc_ener_sum = ADD32(exc_ener_sum, DIV32(MULT16_16(st->exc_rms[sub],st->exc_rms[sub]), st->nbSubframes)); + } + st->last_ener = spx_sqrt(exc_ener_sum); + + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + st->first=0; + + return 0; +} + + +int sb_encoder_ctl(void *state, int request, void *ptr) +{ + SBEncState *st; + st=(SBEncState*)state; + switch(request) + { + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->full_frame_size; + break; + case SPEEX_SET_HIGH_MODE: + st->submodeSelect = st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_SET_LOW_MODE: + speex_encoder_ctl(st->st_low, SPEEX_SET_LOW_MODE, ptr); + break; + case SPEEX_SET_DTX: + speex_encoder_ctl(st->st_low, SPEEX_SET_DTX, ptr); + break; + case SPEEX_GET_DTX: + speex_encoder_ctl(st->st_low, SPEEX_GET_DTX, ptr); + break; + case SPEEX_GET_LOW_MODE: + speex_encoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, ptr); + break; + case SPEEX_SET_MODE: + speex_encoder_ctl(st, SPEEX_SET_QUALITY, ptr); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR: + st->vbr_enabled = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR, ptr); + break; + case SPEEX_GET_VBR: + (*(spx_int32_t*)ptr) = st->vbr_enabled; + break; + case SPEEX_SET_VAD: + st->vad_enabled = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_VAD, ptr); + break; + case SPEEX_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ +#if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) + case SPEEX_SET_VBR_QUALITY: + { + spx_int32_t q; + float qual = (*(float*)ptr)+.6; + st->vbr_quality = (*(float*)ptr); + if (qual>10) + qual=10; + q=(int)floor(.5+*(float*)ptr); + if (q>10) + q=10; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_QUALITY, &qual); + speex_encoder_ctl(state, SPEEX_SET_QUALITY, &q); + break; + } + case SPEEX_GET_VBR_QUALITY: + (*(float*)ptr) = st->vbr_quality; + break; +#endif /* #if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) */ +#ifndef DISABLE_VBR + case SPEEX_SET_ABR: + st->abr_enabled = (*(spx_int32_t*)ptr); + st->vbr_enabled = st->abr_enabled!=0; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR, &st->vbr_enabled); + if (st->vbr_enabled) + { + spx_int32_t i=10, rate, target; + float vbr_qual; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + vbr_qual=i; + if (vbr_qual<0) + vbr_qual=0; + speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_qual); + st->abr_count=0; + st->abr_drift=0; + st->abr_drift2=0; + } + + break; + case SPEEX_GET_ABR: + (*(spx_int32_t*)ptr) = st->abr_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ + + case SPEEX_SET_QUALITY: + { + spx_int32_t nb_qual; + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeSelect = st->submodeID = ((const SpeexSBMode*)(st->mode->mode))->quality_map[quality]; + nb_qual = ((const SpeexSBMode*)(st->mode->mode))->low_quality_map[quality]; + speex_encoder_ctl(st->st_low, SPEEX_SET_MODE, &nb_qual); + } + break; + case SPEEX_SET_COMPLEXITY: + speex_encoder_ctl(st->st_low, SPEEX_SET_COMPLEXITY, ptr); + st->complexity = (*(spx_int32_t*)ptr); + if (st->complexity<1) + st->complexity=1; + break; + case SPEEX_GET_COMPLEXITY: + (*(spx_int32_t*)ptr) = st->complexity; + break; + case SPEEX_SET_BITRATE: + { + spx_int32_t i=10; + spx_int32_t rate, target; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + } + break; + case SPEEX_GET_BITRATE: + speex_encoder_ctl(st->st_low, request, ptr); + /*fprintf (stderr, "before: %d\n", (*(int*)ptr));*/ + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) += st->sampling_rate*SUBMODE(bits_per_frame)/st->full_frame_size; + else + (*(spx_int32_t*)ptr) += st->sampling_rate*(SB_SUBMODE_BITS+1)/st->full_frame_size; + /*fprintf (stderr, "after: %d\n", (*(int*)ptr));*/ + break; + case SPEEX_SET_SAMPLING_RATE: + { + spx_int32_t tmp=(*(spx_int32_t*)ptr); + st->sampling_rate = tmp; + tmp>>=1; + speex_encoder_ctl(st->st_low, SPEEX_SET_SAMPLING_RATE, &tmp); + } + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_RESET_STATE: + { + int i; + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + for (i=0;ilpcSize;i++) + st->mem_sw[i]=st->mem_sp[i]=st->mem_sp2[i]=0; + for (i=0;ih0_mem[i]=st->h1_mem[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_SUBMODE_ENCODING, ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + speex_encoder_ctl(st->st_low, SPEEX_GET_LOOKAHEAD, ptr); + (*(spx_int32_t*)ptr) = 2*(*(spx_int32_t*)ptr) + QMF_ORDER - 1; + break; + case SPEEX_SET_PLC_TUNING: + speex_encoder_ctl(st->st_low, SPEEX_SET_PLC_TUNING, ptr); + break; + case SPEEX_GET_PLC_TUNING: + speex_encoder_ctl(st->st_low, SPEEX_GET_PLC_TUNING, ptr); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR_MAX_BITRATE: + { + st->vbr_max = (*(spx_int32_t*)ptr); + if (SPEEX_SET_VBR_MAX_BITRATE<1) + { + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_MAX_BITRATE, &st->vbr_max); + st->vbr_max_high = 17600; + } else { + spx_int32_t low_rate; + if (st->vbr_max >= 42200) + { + st->vbr_max_high = 17600; + } else if (st->vbr_max >= 27800) + { + st->vbr_max_high = 9600; + } else if (st->vbr_max > 20600) + { + st->vbr_max_high = 5600; + } else { + st->vbr_max_high = 1800; + } + if (st->subframeSize==80) + st->vbr_max_high = 1800; + low_rate = st->vbr_max - st->vbr_max_high; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_MAX_BITRATE, &low_rate); + } + } + break; + case SPEEX_GET_VBR_MAX_BITRATE: + (*(spx_int32_t*)ptr) = st->vbr_max; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_HIGHPASS: + speex_encoder_ctl(st->st_low, SPEEX_SET_HIGHPASS, ptr); + break; + case SPEEX_GET_HIGHPASS: + speex_encoder_ctl(st->st_low, SPEEX_GET_HIGHPASS, ptr); + break; + + + /* This is all internal stuff past this point */ + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = st->exc_rms[i]; + } + break; +#ifndef DISABLE_VBR + case SPEEX_GET_RELATIVE_QUALITY: + (*(float*)ptr)=st->relative_quality; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_INNOVATION_SAVE: + st->innov_rms_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + speex_encoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +int sb_decoder_ctl(void *state, int request, void *ptr) +{ + SBDecState *st; + st=(SBDecState*)state; + switch(request) + { + case SPEEX_SET_HIGH_MODE: + st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_SET_LOW_MODE: + speex_decoder_ctl(st->st_low, SPEEX_SET_LOW_MODE, ptr); + break; + case SPEEX_GET_LOW_MODE: + speex_decoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, ptr); + break; + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->full_frame_size; + break; + case SPEEX_SET_ENH: + speex_decoder_ctl(st->st_low, request, ptr); + st->lpc_enh_enabled = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_ENH: + *((spx_int32_t*)ptr) = st->lpc_enh_enabled; + break; + case SPEEX_SET_MODE: + case SPEEX_SET_QUALITY: + { + spx_int32_t nb_qual; + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeID = ((const SpeexSBMode*)(st->mode->mode))->quality_map[quality]; + nb_qual = ((const SpeexSBMode*)(st->mode->mode))->low_quality_map[quality]; + speex_decoder_ctl(st->st_low, SPEEX_SET_MODE, &nb_qual); + } + break; + case SPEEX_GET_BITRATE: + speex_decoder_ctl(st->st_low, request, ptr); + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) += st->sampling_rate*SUBMODE(bits_per_frame)/st->full_frame_size; + else + (*(spx_int32_t*)ptr) += st->sampling_rate*(SB_SUBMODE_BITS+1)/st->full_frame_size; + break; + case SPEEX_SET_SAMPLING_RATE: + { + spx_int32_t tmp=(*(spx_int32_t*)ptr); + st->sampling_rate = tmp; + tmp>>=1; + speex_decoder_ctl(st->st_low, SPEEX_SET_SAMPLING_RATE, &tmp); + } + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_SET_HANDLER: + speex_decoder_ctl(st->st_low, SPEEX_SET_HANDLER, ptr); + break; + case SPEEX_SET_USER_HANDLER: + speex_decoder_ctl(st->st_low, SPEEX_SET_USER_HANDLER, ptr); + break; + case SPEEX_RESET_STATE: + { + int i; + for (i=0;i<2*st->lpcSize;i++) + st->mem_sp[i]=0; + for (i=0;ig0_mem[i]=st->g1_mem[i]=0; + st->last_ener=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + speex_decoder_ctl(st->st_low, SPEEX_SET_SUBMODE_ENCODING, ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + speex_decoder_ctl(st->st_low, SPEEX_GET_LOOKAHEAD, ptr); + (*(spx_int32_t*)ptr) = 2*(*(spx_int32_t*)ptr); + break; + case SPEEX_SET_HIGHPASS: + speex_decoder_ctl(st->st_low, SPEEX_SET_HIGHPASS, ptr); + break; + case SPEEX_GET_HIGHPASS: + speex_decoder_ctl(st->st_low, SPEEX_GET_HIGHPASS, ptr); + break; + case SPEEX_GET_ACTIVITY: + speex_decoder_ctl(st->st_low, SPEEX_GET_ACTIVITY, ptr); + break; + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = st->exc_rms[i]; + } + break; + case SPEEX_GET_DTX_STATUS: + speex_decoder_ctl(st->st_low, SPEEX_GET_DTX_STATUS, ptr); + break; + case SPEEX_SET_INNOVATION_SAVE: + st->innov_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + speex_decoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +#endif + diff --git a/Libraries/speex/sb_celp.h b/Libraries/speex/sb_celp.h new file mode 100644 index 000000000..e8c376123 --- /dev/null +++ b/Libraries/speex/sb_celp.h @@ -0,0 +1,155 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file sb_celp.h + @brief Sub-band CELP mode used for wideband encoding +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SB_CELP_H +#define SB_CELP_H + +#include "modes.h" +#include +#include "nb_celp.h" + +/**Structure representing the full state of the sub-band encoder*/ +typedef struct SBEncState { + const SpeexMode *mode; /**< Pointer to the mode (containing for vtable info) */ + void *st_low; /**< State of the low-band (narrowband) encoder */ + int full_frame_size; /**< Length of full-band frames*/ + int frame_size; /**< Length of high-band frames*/ + int subframeSize; /**< Length of high-band sub-frames*/ + int nbSubframes; /**< Number of high-band sub-frames*/ + int windowSize; /**< Length of high-band LPC window*/ + int lpcSize; /**< Order of high-band LPC analysis */ + int first; /**< First frame? */ + spx_word16_t lpc_floor; /**< Controls LPC analysis noise floor */ + spx_word16_t gamma1; /**< Perceptual weighting coef 1 */ + spx_word16_t gamma2; /**< Perceptual weighting coef 2 */ + + char *stack; /**< Temporary allocation stack */ + spx_word16_t *high; /**< High-band signal (buffer) */ + spx_word16_t *h0_mem, *h1_mem; + + const spx_word16_t *window; /**< LPC analysis window */ + const spx_word16_t *lagWindow; /**< Auto-correlation window */ + spx_lsp_t *old_lsp; /**< LSPs of previous frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs of previous frame */ + spx_coef_t *interp_qlpc; /**< Interpolated quantized LPCs for current sub-frame */ + + spx_mem_t *mem_sp; /**< Synthesis signal memory */ + spx_mem_t *mem_sp2; + spx_mem_t *mem_sw; /**< Perceptual signal memory */ + spx_word32_t *pi_gain; + spx_word16_t *exc_rms; + spx_word16_t *innov_rms_save; /**< If non-NULL, innovation is copied here */ + +#ifndef DISABLE_VBR + float vbr_quality; /**< Quality setting for VBR encoding */ + int vbr_enabled; /**< 1 for enabling VBR, 0 otherwise */ + spx_int32_t vbr_max; /**< Max bit-rate allowed in VBR mode (total) */ + spx_int32_t vbr_max_high; /**< Max bit-rate allowed in VBR mode for the high-band */ + spx_int32_t abr_enabled; /**< ABR setting (in bps), 0 if off */ + float abr_drift; + float abr_drift2; + float abr_count; + int vad_enabled; /**< 1 for enabling VAD, 0 otherwise */ + float relative_quality; +#endif /* #ifndef DISABLE_VBR */ + + int encode_submode; + const SpeexSubmode * const *submodes; + int submodeID; + int submodeSelect; + int complexity; + spx_int32_t sampling_rate; + +} SBEncState; + + +/**Structure representing the full state of the sub-band decoder*/ +typedef struct SBDecState { + const SpeexMode *mode; /**< Pointer to the mode (containing for vtable info) */ + void *st_low; /**< State of the low-band (narrowband) encoder */ + int full_frame_size; + int frame_size; + int subframeSize; + int nbSubframes; + int lpcSize; + int first; + spx_int32_t sampling_rate; + int lpc_enh_enabled; + + char *stack; + spx_word16_t *g0_mem, *g1_mem; + + spx_word16_t *excBuf; + spx_lsp_t *old_qlsp; + spx_coef_t *interp_qlpc; + + spx_mem_t *mem_sp; + spx_word32_t *pi_gain; + spx_word16_t *exc_rms; + spx_word16_t *innov_save; /** If non-NULL, innovation is copied here */ + + spx_word16_t last_ener; + spx_int32_t seed; + + int encode_submode; + const SpeexSubmode * const *submodes; + int submodeID; +} SBDecState; + + +/**Initializes encoder state*/ +void *sb_encoder_init(const SpeexMode *m); + +/**De-allocates encoder state resources*/ +void sb_encoder_destroy(void *state); + +/**Encodes one frame*/ +int sb_encode(void *state, void *in, SpeexBits *bits); + + +/**Initializes decoder state*/ +void *sb_decoder_init(const SpeexMode *m); + +/**De-allocates decoder state resources*/ +void sb_decoder_destroy(void *state); + +/**Decodes one frame*/ +int sb_decode(void *state, SpeexBits *bits, void *out); + +int sb_encoder_ctl(void *state, int request, void *ptr); + +int sb_decoder_ctl(void *state, int request, void *ptr); + +#endif diff --git a/Libraries/speex/scal.c b/Libraries/speex/scal.c new file mode 100644 index 000000000..54fd3f6dd --- /dev/null +++ b/Libraries/speex/scal.c @@ -0,0 +1,289 @@ +/* Copyright (C) 2006-2008 CSIRO, Jean-Marc Valin, Xiph.Org Foundation + + File: scal.c + Shaped comb-allpass filter for channel decorrelation + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +The algorithm implemented here is described in: + +* J.-M. Valin, Perceptually-Motivated Nonlinear Channel Decorrelation For + Stereo Acoustic Echo Cancellation, Accepted for Joint Workshop on + Hands­free Speech Communication and Microphone Arrays (HSCMA), 2008. + http://people.xiph.org/~jm/papers/valin_hscma2008.pdf + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "speex/speex_echo.h" +#include "vorbis_psy.h" +#include "arch.h" +#include "os_support.h" +#include "smallft.h" +#include +#include + +#define ALLPASS_ORDER 20 + +struct SpeexDecorrState_ { + int rate; + int channels; + int frame_size; +#ifdef VORBIS_PSYCHO + VorbisPsy *psy; + struct drft_lookup lookup; + float *wola_mem; + float *curve; +#endif + float *vorbis_win; + int seed; + float *y; + + /* Per-channel stuff */ + float *buff; + float (*ring)[ALLPASS_ORDER]; + int *ringID; + int *order; + float *alpha; +}; + + + +EXPORT SpeexDecorrState *speex_decorrelate_new(int rate, int channels, int frame_size) +{ + int i, ch; + SpeexDecorrState *st = speex_alloc(sizeof(SpeexDecorrState)); + st->rate = rate; + st->channels = channels; + st->frame_size = frame_size; +#ifdef VORBIS_PSYCHO + st->psy = vorbis_psy_init(rate, 2*frame_size); + spx_drft_init(&st->lookup, 2*frame_size); + st->wola_mem = speex_alloc(frame_size*sizeof(float)); + st->curve = speex_alloc(frame_size*sizeof(float)); +#endif + st->y = speex_alloc(frame_size*sizeof(float)); + + st->buff = speex_alloc(channels*2*frame_size*sizeof(float)); + st->ringID = speex_alloc(channels*sizeof(int)); + st->order = speex_alloc(channels*sizeof(int)); + st->alpha = speex_alloc(channels*sizeof(float)); + st->ring = speex_alloc(channels*ALLPASS_ORDER*sizeof(float)); + + /*FIXME: The +20 is there only as a kludge for ALL_PASS_OLA*/ + st->vorbis_win = speex_alloc((2*frame_size+20)*sizeof(float)); + for (i=0;i<2*frame_size;i++) + st->vorbis_win[i] = sin(.5*M_PI* sin(M_PI*i/(2*frame_size))*sin(M_PI*i/(2*frame_size)) ); + st->seed = rand(); + + for (ch=0;chring[ch][i] = 0; + st->ringID[ch] = 0; + st->alpha[ch] = 0; + st->order[ch] = 10; + } + return st; +} + +static float uni_rand(int *seed) +{ + const unsigned int jflone = 0x3f800000; + const unsigned int jflmsk = 0x007fffff; + union {int i; float f;} ran; + *seed = 1664525 * *seed + 1013904223; + ran.i = jflone | (jflmsk & *seed); + ran.f -= 1.5; + return 2*ran.f; +} + +static unsigned int irand(int *seed) +{ + *seed = 1664525 * *seed + 1013904223; + return ((unsigned int)*seed)>>16; +} + + +EXPORT void speex_decorrelate(SpeexDecorrState *st, const spx_int16_t *in, spx_int16_t *out, int strength) +{ + int ch; + float amount; + + if (strength<0) + strength = 0; + if (strength>100) + strength = 100; + + amount = .01*strength; + for (ch=0;chchannels;ch++) + { + int i; + //int N=2*st->frame_size; + float beta, beta2; + float *x; + float max_alpha = 0; + + float *buff; + float *ring; + int ringID; + int order; + float alpha; + + buff = st->buff+ch*2*st->frame_size; + ring = st->ring[ch]; + ringID = st->ringID[ch]; + order = st->order[ch]; + alpha = st->alpha[ch]; + + for (i=0;iframe_size;i++) + buff[i] = buff[i+st->frame_size]; + for (i=0;iframe_size;i++) + buff[i+st->frame_size] = in[i*st->channels+ch]; + + x = buff+st->frame_size; + beta = 1.-.3*amount*amount; + if (amount>1) + beta = 1-sqrt(.4*amount); + else + beta = 1-0.63246*amount; + if (beta<0) + beta = 0; + + beta2 = beta; + for (i=0;iframe_size;i++) + { + st->y[i] = alpha*(x[i-ALLPASS_ORDER+order]-beta*x[i-ALLPASS_ORDER+order-1])*st->vorbis_win[st->frame_size+i+order] + + x[i-ALLPASS_ORDER]*st->vorbis_win[st->frame_size+i] + - alpha*(ring[ringID] + - beta*ring[ringID+1>=order?0:ringID+1]); + ring[ringID++]=st->y[i]; + st->y[i] *= st->vorbis_win[st->frame_size+i]; + if (ringID>=order) + ringID=0; + } + order = order+(irand(&st->seed)%3)-1; + if (order < 5) + order = 5; + if (order > 10) + order = 10; + /*order = 5+(irand(&st->seed)%6);*/ + max_alpha = pow(.96+.04*(amount-1),order); + if (max_alpha > .98/(1.+beta2)) + max_alpha = .98/(1.+beta2); + + alpha = alpha + .4*uni_rand(&st->seed); + if (alpha > max_alpha) + alpha = max_alpha; + if (alpha < -max_alpha) + alpha = -max_alpha; + for (i=0;iframe_size;i++) + { + float tmp = alpha*(x[i-ALLPASS_ORDER+order]-beta*x[i-ALLPASS_ORDER+order-1])*st->vorbis_win[i+order] + + x[i-ALLPASS_ORDER]*st->vorbis_win[i] + - alpha*(ring[ringID] + - beta*ring[ringID+1>=order?0:ringID+1]); + ring[ringID++]=tmp; + tmp *= st->vorbis_win[i]; + if (ringID>=order) + ringID=0; + st->y[i] += tmp; + } + +#ifdef VORBIS_PSYCHO + float frame[N]; + float scale = 1./N; + for (i=0;i<2*st->frame_size;i++) + frame[i] = buff[i]; + //float coef = .5*0.78130; + float coef = M_PI*0.075063 * 0.93763 * amount * .8 * 0.707; + compute_curve(st->psy, buff, st->curve); + for (i=1;iframe_size;i++) + { + float x1,x2; + float gain; + do { + x1 = uni_rand(&st->seed); + x2 = uni_rand(&st->seed); + } while (x1*x1+x2*x2 > 1.); + gain = coef*sqrt(.1+st->curve[i]); + frame[2*i-1] = gain*x1; + frame[2*i] = gain*x2; + } + frame[0] = coef*uni_rand(&st->seed)*sqrt(.1+st->curve[0]); + frame[2*st->frame_size-1] = coef*uni_rand(&st->seed)*sqrt(.1+st->curve[st->frame_size-1]); + spx_drft_backward(&st->lookup,frame); + for (i=0;i<2*st->frame_size;i++) + frame[i] *= st->vorbis_win[i]; +#endif + + for (i=0;iframe_size;i++) + { +#ifdef VORBIS_PSYCHO + float tmp = st->y[i] + frame[i] + st->wola_mem[i]; + st->wola_mem[i] = frame[i+st->frame_size]; +#else + float tmp = st->y[i]; +#endif + if (tmp>32767) + tmp = 32767; + if (tmp < -32767) + tmp = -32767; + out[i*st->channels+ch] = tmp; + } + + st->ringID[ch] = ringID; + st->order[ch] = order; + st->alpha[ch] = alpha; + + } +} + +EXPORT void speex_decorrelate_destroy(SpeexDecorrState *st) +{ +#ifdef VORBIS_PSYCHO + vorbis_psy_destroy(st->psy); + speex_free(st->wola_mem); + speex_free(st->curve); +#endif + speex_free(st->buff); + speex_free(st->ring); + speex_free(st->ringID); + speex_free(st->alpha); + speex_free(st->vorbis_win); + speex_free(st->order); + speex_free(st->y); + speex_free(st); +} diff --git a/Libraries/speex/smallft.c b/Libraries/speex/smallft.c new file mode 100644 index 000000000..5c26d016f --- /dev/null +++ b/Libraries/speex/smallft.c @@ -0,0 +1,1261 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: *unnormalized* fft transform + last mod: $Id: smallft.c,v 1.19 2003/10/08 05:12:37 jm Exp $ + + ********************************************************************/ + +/* FFT implementation from OggSquish, minus cosine transforms, + * minus all but radix 2/4 case. In Vorbis we only need this + * cut-down version. + * + * To do more than just power-of-two sized vectors, see the full + * version I wrote for NetLib. + * + * Note that the packing is a little strange; rather than the FFT r/i + * packing following R_0, I_n, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, + * it follows R_0, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, I_n like the + * FORTRAN version + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "smallft.h" +#include "arch.h" +#include "os_support.h" + +static void drfti1(int n, float *wa, int *ifac){ + static int ntryh[4] = { 4,2,3,5 }; + static float tpi = 6.28318530717958648f; + float arg,argh,argld,fi; + int ntry=0,i,j=-1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl=n; + int nf=0; + + L101: + j++; + if (j < 4) + ntry=ntryh[j]; + else + ntry+=2; + + L104: + nq=nl/ntry; + nr=nl-ntry*nq; + if (nr!=0) goto L101; + + nf++; + ifac[nf+1]=ntry; + nl=nq; + if(ntry!=2)goto L107; + if(nf==1)goto L107; + + for (i=1;i>1; + ipp2=ip; + idp2=ido; + nbd=(ido-1)>>1; + t0=l1*ido; + t10=ip*ido; + + if(ido==1)goto L119; + for(ik=0;ikl1){ + for(j=1;j>1; + ipp2=ip; + ipph=(ip+1)>>1; + if(idol1)goto L139; + + is= -ido-1; + t1=0; + for(j=1;jn==1)return; + drftf1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void spx_drft_backward(struct drft_lookup *l,float *data){ + if (l->n==1)return; + drftb1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void spx_drft_init(struct drft_lookup *l,int n) +{ + l->n=n; + l->trigcache=(float*)speex_alloc(3*n*sizeof(*l->trigcache)); + l->splitcache=(int*)speex_alloc(32*sizeof(*l->splitcache)); + fdrffti(n, l->trigcache, l->splitcache); +} + +void spx_drft_clear(struct drft_lookup *l) +{ + if(l) + { + if(l->trigcache) + speex_free(l->trigcache); + if(l->splitcache) + speex_free(l->splitcache); + } +} diff --git a/Libraries/speex/smallft.h b/Libraries/speex/smallft.h new file mode 100644 index 000000000..446e2f65b --- /dev/null +++ b/Libraries/speex/smallft.h @@ -0,0 +1,46 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: fft transform + last mod: $Id: smallft.h,v 1.3 2003/09/16 18:35:45 jm Exp $ + + ********************************************************************/ +/** + @file smallft.h + @brief Discrete Rotational Fourier Transform (DRFT) +*/ + +#ifndef _V_SMFT_H_ +#define _V_SMFT_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** Discrete Rotational Fourier Transform lookup */ +struct drft_lookup{ + int n; + float *trigcache; + int *splitcache; +}; + +extern void spx_drft_forward(struct drft_lookup *l,float *data); +extern void spx_drft_backward(struct drft_lookup *l,float *data); +extern void spx_drft_init(struct drft_lookup *l,int n); +extern void spx_drft_clear(struct drft_lookup *l); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Libraries/speex/speex.c b/Libraries/speex/speex.c new file mode 100644 index 000000000..b425155c2 --- /dev/null +++ b/Libraries/speex/speex.c @@ -0,0 +1,250 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex.c + + Basic Speex functions + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +#define MAX_IN_SAMPLES 640 + + + +EXPORT void *speex_encoder_init(const SpeexMode *mode) +{ + return mode->enc_init(mode); +} + +EXPORT void *speex_decoder_init(const SpeexMode *mode) +{ + return mode->dec_init(mode); +} + +EXPORT void speex_encoder_destroy(void *state) +{ + (*((SpeexMode**)state))->enc_destroy(state); +} + +EXPORT void speex_decoder_destroy(void *state) +{ + (*((SpeexMode**)state))->dec_destroy(state); +} + + + +int speex_encode_native(void *state, spx_word16_t *in, SpeexBits *bits) +{ + return (*((SpeexMode**)state))->enc(state, in, bits); +} + +int speex_decode_native(void *state, SpeexBits *bits, spx_word16_t *out) +{ + return (*((SpeexMode**)state))->dec(state, bits, out); +} + + + +#ifdef FIXED_POINT + +#ifndef DISABLE_FLOAT_API +EXPORT int speex_encode(void *state, float *in, SpeexBits *bits) +{ + int i; + spx_int32_t N; + spx_int16_t short_in[MAX_IN_SAMPLES]; + speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + for (i=0;i32767.f) + short_in[i] = 32767; + else if (in[i]<-32768.f) + short_in[i] = -32768; + else + short_in[i] = (spx_int16_t)floor(.5+in[i]); + } + return (*((SpeexMode**)state))->enc(state, short_in, bits); +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +EXPORT int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits) +{ + SpeexMode *mode; + mode = *(SpeexMode**)state; + return (mode)->enc(state, in, bits); +} + +#ifndef DISABLE_FLOAT_API +EXPORT int speex_decode(void *state, SpeexBits *bits, float *out) +{ + int i, ret; + spx_int32_t N; + spx_int16_t short_out[MAX_IN_SAMPLES]; + speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + ret = (*((SpeexMode**)state))->dec(state, bits, short_out); + for (i=0;idec(state, bits, out); +} + +#else + +EXPORT int speex_encode(void *state, float *in, SpeexBits *bits) +{ + return (*((SpeexMode**)state))->enc(state, in, bits); +} + +EXPORT int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits) +{ + int i; + spx_int32_t N; + float float_in[MAX_IN_SAMPLES]; + speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + for (i=0;ienc(state, float_in, bits); +} + +EXPORT int speex_decode(void *state, SpeexBits *bits, float *out) +{ + return (*((SpeexMode**)state))->dec(state, bits, out); +} + +EXPORT int speex_decode_int(void *state, SpeexBits *bits, spx_int16_t *out) +{ + int i; + spx_int32_t N; + float float_out[MAX_IN_SAMPLES]; + int ret; + speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + ret = (*((SpeexMode**)state))->dec(state, bits, float_out); + for (i=0;i32767.f) + out[i] = 32767; + else if (float_out[i]<-32768.f) + out[i] = -32768; + else + out[i] = (spx_int16_t)floor(.5+float_out[i]); + } + return ret; +} +#endif + + + +EXPORT int speex_encoder_ctl(void *state, int request, void *ptr) +{ + return (*((SpeexMode**)state))->enc_ctl(state, request, ptr); +} + +EXPORT int speex_decoder_ctl(void *state, int request, void *ptr) +{ + return (*((SpeexMode**)state))->dec_ctl(state, request, ptr); +} + + + +int nb_mode_query(const void *mode, int request, void *ptr) +{ + const SpeexNBMode *m = (const SpeexNBMode*)mode; + + switch (request) + { + case SPEEX_MODE_FRAME_SIZE: + *((int*)ptr)=m->frameSize; + break; + case SPEEX_SUBMODE_BITS_PER_FRAME: + if (*((int*)ptr)==0) + *((int*)ptr) = NB_SUBMODE_BITS+1; + else if (m->submodes[*((int*)ptr)]==NULL) + *((int*)ptr) = -1; + else + *((int*)ptr) = m->submodes[*((int*)ptr)]->bits_per_frame; + break; + default: + speex_warning_int("Unknown nb_mode_query request: ", request); + return -1; + } + return 0; +} + + + +EXPORT int speex_lib_ctl(int request, void *ptr) +{ + switch (request) + { + case SPEEX_LIB_GET_MAJOR_VERSION: + *((int*)ptr) = SPEEX_MAJOR_VERSION; + break; + case SPEEX_LIB_GET_MINOR_VERSION: + *((int*)ptr) = SPEEX_MINOR_VERSION; + break; + case SPEEX_LIB_GET_MICRO_VERSION: + *((int*)ptr) = SPEEX_MICRO_VERSION; + break; + case SPEEX_LIB_GET_EXTRA_VERSION: + *((const char**)ptr) = SPEEX_EXTRA_VERSION; + break; + case SPEEX_LIB_GET_VERSION_STRING: + *((const char**)ptr) = SPEEX_VERSION; + break; + /*case SPEEX_LIB_SET_ALLOC_FUNC: + break; + case SPEEX_LIB_GET_ALLOC_FUNC: + break; + case SPEEX_LIB_SET_FREE_FUNC: + break; + case SPEEX_LIB_GET_FREE_FUNC: + break;*/ + default: + speex_warning_int("Unknown wb_mode_query request: ", request); + return -1; + } + return 0; +} diff --git a/Libraries/speex/speex.xcodeproj/project.pbxproj b/Libraries/speex/speex.xcodeproj/project.pbxproj new file mode 100644 index 000000000..e6d101bb4 --- /dev/null +++ b/Libraries/speex/speex.xcodeproj/project.pbxproj @@ -0,0 +1,670 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A1FDCDE616DBCA0500868894 /* _kiss_fft_guts.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD8916DBCA0400868894 /* _kiss_fft_guts.h */; }; + A1FDCDE716DBCA0500868894 /* arch.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD8A16DBCA0400868894 /* arch.h */; }; + A1FDCDE816DBCA0500868894 /* bits.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD8B16DBCA0400868894 /* bits.c */; }; + A1FDCDE916DBCA0500868894 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD8C16DBCA0400868894 /* buffer.c */; }; + A1FDCDEA16DBCA0500868894 /* cb_search_arm4.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD8D16DBCA0400868894 /* cb_search_arm4.h */; }; + A1FDCDEB16DBCA0500868894 /* cb_search_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD8E16DBCA0400868894 /* cb_search_bfin.h */; }; + A1FDCDEC16DBCA0500868894 /* cb_search_sse.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD8F16DBCA0400868894 /* cb_search_sse.h */; }; + A1FDCDED16DBCA0500868894 /* cb_search.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9016DBCA0400868894 /* cb_search.c */; }; + A1FDCDEE16DBCA0500868894 /* cb_search.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9116DBCA0400868894 /* cb_search.h */; }; + A1FDCDF016DBCA0500868894 /* exc_5_64_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9316DBCA0400868894 /* exc_5_64_table.c */; }; + A1FDCDF116DBCA0500868894 /* exc_5_256_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9416DBCA0400868894 /* exc_5_256_table.c */; }; + A1FDCDF216DBCA0500868894 /* exc_8_128_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9516DBCA0400868894 /* exc_8_128_table.c */; }; + A1FDCDF316DBCA0500868894 /* exc_10_16_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9616DBCA0400868894 /* exc_10_16_table.c */; }; + A1FDCDF416DBCA0500868894 /* exc_10_32_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9716DBCA0400868894 /* exc_10_32_table.c */; }; + A1FDCDF516DBCA0500868894 /* exc_20_32_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9816DBCA0400868894 /* exc_20_32_table.c */; }; + A1FDCDF616DBCA0500868894 /* fftwrap.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9916DBCA0400868894 /* fftwrap.c */; }; + A1FDCDF716DBCA0500868894 /* fftwrap.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9A16DBCA0400868894 /* fftwrap.h */; }; + A1FDCDF816DBCA0500868894 /* filterbank.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCD9B16DBCA0400868894 /* filterbank.c */; }; + A1FDCDF916DBCA0500868894 /* filterbank.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9C16DBCA0400868894 /* filterbank.h */; }; + A1FDCDFA16DBCA0500868894 /* filters_arm4.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9D16DBCA0400868894 /* filters_arm4.h */; }; + A1FDCDFB16DBCA0500868894 /* filters_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9E16DBCA0400868894 /* filters_bfin.h */; }; + A1FDCDFC16DBCA0500868894 /* filters_sse.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCD9F16DBCA0400868894 /* filters_sse.h */; }; + A1FDCDFD16DBCA0500868894 /* filters.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDA016DBCA0400868894 /* filters.c */; }; + A1FDCDFE16DBCA0500868894 /* filters.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA116DBCA0400868894 /* filters.h */; }; + A1FDCDFF16DBCA0500868894 /* fixed_arm4.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA216DBCA0400868894 /* fixed_arm4.h */; }; + A1FDCE0016DBCA0500868894 /* fixed_arm5e.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA316DBCA0400868894 /* fixed_arm5e.h */; }; + A1FDCE0116DBCA0500868894 /* fixed_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA416DBCA0400868894 /* fixed_bfin.h */; }; + A1FDCE0216DBCA0500868894 /* fixed_debug.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA516DBCA0400868894 /* fixed_debug.h */; }; + A1FDCE0316DBCA0500868894 /* fixed_generic.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDA616DBCA0400868894 /* fixed_generic.h */; }; + A1FDCE0416DBCA0500868894 /* gain_table_lbr.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDA716DBCA0400868894 /* gain_table_lbr.c */; }; + A1FDCE0516DBCA0500868894 /* gain_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDA816DBCA0400868894 /* gain_table.c */; }; + A1FDCE0616DBCA0500868894 /* hexc_10_32_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDA916DBCA0400868894 /* hexc_10_32_table.c */; }; + A1FDCE0716DBCA0500868894 /* hexc_table.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDAA16DBCA0400868894 /* hexc_table.c */; }; + A1FDCE0816DBCA0500868894 /* high_lsp_tables.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDAB16DBCA0400868894 /* high_lsp_tables.c */; }; + A1FDCE0916DBCA0500868894 /* jitter.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDAC16DBCA0400868894 /* jitter.c */; }; + A1FDCE0A16DBCA0500868894 /* kiss_fft.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDAD16DBCA0400868894 /* kiss_fft.c */; }; + A1FDCE0B16DBCA0500868894 /* kiss_fft.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDAE16DBCA0400868894 /* kiss_fft.h */; }; + A1FDCE0C16DBCA0500868894 /* kiss_fftr.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDAF16DBCA0400868894 /* kiss_fftr.c */; }; + A1FDCE0D16DBCA0500868894 /* kiss_fftr.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB016DBCA0400868894 /* kiss_fftr.h */; }; + A1FDCE0E16DBCA0500868894 /* lpc_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB116DBCA0400868894 /* lpc_bfin.h */; }; + A1FDCE0F16DBCA0500868894 /* lpc.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDB216DBCA0400868894 /* lpc.c */; }; + A1FDCE1016DBCA0500868894 /* lpc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB316DBCA0400868894 /* lpc.h */; }; + A1FDCE1116DBCA0500868894 /* lsp_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB416DBCA0400868894 /* lsp_bfin.h */; }; + A1FDCE1216DBCA0500868894 /* lsp_tables_nb.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDB516DBCA0400868894 /* lsp_tables_nb.c */; }; + A1FDCE1316DBCA0500868894 /* lsp.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDB616DBCA0400868894 /* lsp.c */; }; + A1FDCE1416DBCA0500868894 /* lsp.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB716DBCA0400868894 /* lsp.h */; }; + A1FDCE1516DBCA0500868894 /* ltp_arm4.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB816DBCA0400868894 /* ltp_arm4.h */; }; + A1FDCE1616DBCA0500868894 /* ltp_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDB916DBCA0400868894 /* ltp_bfin.h */; }; + A1FDCE1716DBCA0500868894 /* ltp_sse.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDBA16DBCA0400868894 /* ltp_sse.h */; }; + A1FDCE1816DBCA0500868894 /* ltp.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDBB16DBCA0400868894 /* ltp.c */; }; + A1FDCE1916DBCA0500868894 /* ltp.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDBC16DBCA0400868894 /* ltp.h */; }; + A1FDCE1A16DBCA0500868894 /* math_approx.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDBD16DBCA0400868894 /* math_approx.h */; }; + A1FDCE1B16DBCA0500868894 /* mdf.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDBE16DBCA0400868894 /* mdf.c */; }; + A1FDCE1C16DBCA0500868894 /* misc_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDBF16DBCA0400868894 /* misc_bfin.h */; }; + A1FDCE1D16DBCA0500868894 /* modes_wb.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDC016DBCA0400868894 /* modes_wb.c */; }; + A1FDCE1E16DBCA0500868894 /* modes.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDC116DBCA0400868894 /* modes.c */; }; + A1FDCE1F16DBCA0500868894 /* modes.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDC216DBCA0400868894 /* modes.h */; }; + A1FDCE2016DBCA0500868894 /* nb_celp.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDC316DBCA0400868894 /* nb_celp.c */; }; + A1FDCE2116DBCA0500868894 /* nb_celp.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDC416DBCA0400868894 /* nb_celp.h */; }; + A1FDCE2216DBCA0500868894 /* os_support.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDC516DBCA0400868894 /* os_support.h */; }; + A1FDCE2316DBCA0500868894 /* preprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDC616DBCA0400868894 /* preprocess.c */; }; + A1FDCE2416DBCA0500868894 /* pseudofloat.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDC716DBCA0400868894 /* pseudofloat.h */; }; + A1FDCE2516DBCA0500868894 /* quant_lsp_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDC816DBCA0400868894 /* quant_lsp_bfin.h */; }; + A1FDCE2616DBCA0500868894 /* quant_lsp.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDC916DBCA0400868894 /* quant_lsp.c */; }; + A1FDCE2716DBCA0500868894 /* quant_lsp.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDCA16DBCA0400868894 /* quant_lsp.h */; }; + A1FDCE2816DBCA0500868894 /* resample_sse.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDCB16DBCA0400868894 /* resample_sse.h */; }; + A1FDCE2916DBCA0500868894 /* resample.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDCC16DBCA0400868894 /* resample.c */; }; + A1FDCE2A16DBCA0500868894 /* sb_celp.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDCD16DBCA0400868894 /* sb_celp.c */; }; + A1FDCE2B16DBCA0500868894 /* sb_celp.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDCE16DBCA0400868894 /* sb_celp.h */; }; + A1FDCE2C16DBCA0500868894 /* scal.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDCF16DBCA0400868894 /* scal.c */; }; + A1FDCE2D16DBCA0500868894 /* smallft.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD016DBCA0400868894 /* smallft.c */; }; + A1FDCE2E16DBCA0500868894 /* smallft.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDD116DBCA0400868894 /* smallft.h */; }; + A1FDCE2F16DBCA0500868894 /* speex_callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD216DBCA0400868894 /* speex_callbacks.c */; }; + A1FDCE3016DBCA0500868894 /* speex_header.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD316DBCA0400868894 /* speex_header.c */; }; + A1FDCE3116DBCA0500868894 /* speex.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD416DBCA0400868894 /* speex.c */; }; + A1FDCE3216DBCA0500868894 /* stack_alloc.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDD516DBCA0400868894 /* stack_alloc.h */; }; + A1FDCE3316DBCA0500868894 /* stereo.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD616DBCA0400868894 /* stereo.c */; }; + A1FDCE3416DBCA0500868894 /* testdenoise.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD716DBCA0400868894 /* testdenoise.c */; }; + A1FDCE3516DBCA0500868894 /* testecho.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD816DBCA0400868894 /* testecho.c */; }; + A1FDCE3616DBCA0500868894 /* testenc_uwb.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDD916DBCA0400868894 /* testenc_uwb.c */; }; + A1FDCE3716DBCA0500868894 /* testenc_wb.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDDA16DBCA0400868894 /* testenc_wb.c */; }; + A1FDCE3816DBCA0500868894 /* testenc.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDDB16DBCA0400868894 /* testenc.c */; }; + A1FDCE3916DBCA0500868894 /* testjitter.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDDC16DBCA0400868894 /* testjitter.c */; }; + A1FDCE3A16DBCA0500868894 /* vbr.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDDD16DBCA0400868894 /* vbr.c */; }; + A1FDCE3B16DBCA0500868894 /* vbr.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDDE16DBCA0400868894 /* vbr.h */; }; + A1FDCE3C16DBCA0500868894 /* vorbis_psy.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDDF16DBCA0400868894 /* vorbis_psy.h */; }; + A1FDCE3D16DBCA0500868894 /* vq_arm4.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDE016DBCA0400868894 /* vq_arm4.h */; }; + A1FDCE3E16DBCA0500868894 /* vq_bfin.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDE116DBCA0400868894 /* vq_bfin.h */; }; + A1FDCE3F16DBCA0500868894 /* vq_sse.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDE216DBCA0400868894 /* vq_sse.h */; }; + A1FDCE4016DBCA0500868894 /* vq.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDE316DBCA0400868894 /* vq.c */; }; + A1FDCE4116DBCA0500868894 /* vq.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FDCDE416DBCA0400868894 /* vq.h */; }; + A1FDCE4216DBCA0500868894 /* window.c in Sources */ = {isa = PBXBuildFile; fileRef = A1FDCDE516DBCA0500868894 /* window.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A1FDCBFA16DBC57D00868894 /* libspeex.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libspeex.a; sourceTree = BUILT_PRODUCTS_DIR; }; + A1FDCD8916DBCA0400868894 /* _kiss_fft_guts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _kiss_fft_guts.h; sourceTree = ""; }; + A1FDCD8A16DBCA0400868894 /* arch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arch.h; sourceTree = ""; }; + A1FDCD8B16DBCA0400868894 /* bits.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bits.c; sourceTree = ""; }; + A1FDCD8C16DBCA0400868894 /* buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = buffer.c; sourceTree = ""; }; + A1FDCD8D16DBCA0400868894 /* cb_search_arm4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cb_search_arm4.h; sourceTree = ""; }; + A1FDCD8E16DBCA0400868894 /* cb_search_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cb_search_bfin.h; sourceTree = ""; }; + A1FDCD8F16DBCA0400868894 /* cb_search_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cb_search_sse.h; sourceTree = ""; }; + A1FDCD9016DBCA0400868894 /* cb_search.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cb_search.c; sourceTree = ""; }; + A1FDCD9116DBCA0400868894 /* cb_search.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cb_search.h; sourceTree = ""; }; + A1FDCD9316DBCA0400868894 /* exc_5_64_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_5_64_table.c; sourceTree = ""; }; + A1FDCD9416DBCA0400868894 /* exc_5_256_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_5_256_table.c; sourceTree = ""; }; + A1FDCD9516DBCA0400868894 /* exc_8_128_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_8_128_table.c; sourceTree = ""; }; + A1FDCD9616DBCA0400868894 /* exc_10_16_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_10_16_table.c; sourceTree = ""; }; + A1FDCD9716DBCA0400868894 /* exc_10_32_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_10_32_table.c; sourceTree = ""; }; + A1FDCD9816DBCA0400868894 /* exc_20_32_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exc_20_32_table.c; sourceTree = ""; }; + A1FDCD9916DBCA0400868894 /* fftwrap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fftwrap.c; sourceTree = ""; }; + A1FDCD9A16DBCA0400868894 /* fftwrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fftwrap.h; sourceTree = ""; }; + A1FDCD9B16DBCA0400868894 /* filterbank.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = filterbank.c; sourceTree = ""; }; + A1FDCD9C16DBCA0400868894 /* filterbank.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filterbank.h; sourceTree = ""; }; + A1FDCD9D16DBCA0400868894 /* filters_arm4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filters_arm4.h; sourceTree = ""; }; + A1FDCD9E16DBCA0400868894 /* filters_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filters_bfin.h; sourceTree = ""; }; + A1FDCD9F16DBCA0400868894 /* filters_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filters_sse.h; sourceTree = ""; }; + A1FDCDA016DBCA0400868894 /* filters.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = filters.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; + A1FDCDA116DBCA0400868894 /* filters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filters.h; sourceTree = ""; }; + A1FDCDA216DBCA0400868894 /* fixed_arm4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_arm4.h; sourceTree = ""; }; + A1FDCDA316DBCA0400868894 /* fixed_arm5e.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_arm5e.h; sourceTree = ""; }; + A1FDCDA416DBCA0400868894 /* fixed_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_bfin.h; sourceTree = ""; }; + A1FDCDA516DBCA0400868894 /* fixed_debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_debug.h; sourceTree = ""; }; + A1FDCDA616DBCA0400868894 /* fixed_generic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_generic.h; sourceTree = ""; }; + A1FDCDA716DBCA0400868894 /* gain_table_lbr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gain_table_lbr.c; sourceTree = ""; }; + A1FDCDA816DBCA0400868894 /* gain_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gain_table.c; sourceTree = ""; }; + A1FDCDA916DBCA0400868894 /* hexc_10_32_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hexc_10_32_table.c; sourceTree = ""; }; + A1FDCDAA16DBCA0400868894 /* hexc_table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hexc_table.c; sourceTree = ""; }; + A1FDCDAB16DBCA0400868894 /* high_lsp_tables.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = high_lsp_tables.c; sourceTree = ""; }; + A1FDCDAC16DBCA0400868894 /* jitter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = jitter.c; sourceTree = ""; }; + A1FDCDAD16DBCA0400868894 /* kiss_fft.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kiss_fft.c; sourceTree = ""; }; + A1FDCDAE16DBCA0400868894 /* kiss_fft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = kiss_fft.h; sourceTree = ""; }; + A1FDCDAF16DBCA0400868894 /* kiss_fftr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kiss_fftr.c; sourceTree = ""; }; + A1FDCDB016DBCA0400868894 /* kiss_fftr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = kiss_fftr.h; sourceTree = ""; }; + A1FDCDB116DBCA0400868894 /* lpc_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc_bfin.h; sourceTree = ""; }; + A1FDCDB216DBCA0400868894 /* lpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lpc.c; sourceTree = ""; }; + A1FDCDB316DBCA0400868894 /* lpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc.h; sourceTree = ""; }; + A1FDCDB416DBCA0400868894 /* lsp_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lsp_bfin.h; sourceTree = ""; }; + A1FDCDB516DBCA0400868894 /* lsp_tables_nb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lsp_tables_nb.c; sourceTree = ""; }; + A1FDCDB616DBCA0400868894 /* lsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lsp.c; sourceTree = ""; }; + A1FDCDB716DBCA0400868894 /* lsp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lsp.h; sourceTree = ""; }; + A1FDCDB816DBCA0400868894 /* ltp_arm4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ltp_arm4.h; sourceTree = ""; }; + A1FDCDB916DBCA0400868894 /* ltp_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ltp_bfin.h; sourceTree = ""; }; + A1FDCDBA16DBCA0400868894 /* ltp_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ltp_sse.h; sourceTree = ""; }; + A1FDCDBB16DBCA0400868894 /* ltp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = ltp.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; + A1FDCDBC16DBCA0400868894 /* ltp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ltp.h; sourceTree = ""; }; + A1FDCDBD16DBCA0400868894 /* math_approx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = math_approx.h; sourceTree = ""; }; + A1FDCDBE16DBCA0400868894 /* mdf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdf.c; sourceTree = ""; }; + A1FDCDBF16DBCA0400868894 /* misc_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc_bfin.h; sourceTree = ""; }; + A1FDCDC016DBCA0400868894 /* modes_wb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = modes_wb.c; sourceTree = ""; }; + A1FDCDC116DBCA0400868894 /* modes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = modes.c; sourceTree = ""; }; + A1FDCDC216DBCA0400868894 /* modes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modes.h; sourceTree = ""; }; + A1FDCDC316DBCA0400868894 /* nb_celp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nb_celp.c; sourceTree = ""; }; + A1FDCDC416DBCA0400868894 /* nb_celp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nb_celp.h; sourceTree = ""; }; + A1FDCDC516DBCA0400868894 /* os_support.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = os_support.h; sourceTree = ""; }; + A1FDCDC616DBCA0400868894 /* preprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = preprocess.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; + A1FDCDC716DBCA0400868894 /* pseudofloat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pseudofloat.h; sourceTree = ""; }; + A1FDCDC816DBCA0400868894 /* quant_lsp_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quant_lsp_bfin.h; sourceTree = ""; }; + A1FDCDC916DBCA0400868894 /* quant_lsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = quant_lsp.c; sourceTree = ""; }; + A1FDCDCA16DBCA0400868894 /* quant_lsp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quant_lsp.h; sourceTree = ""; }; + A1FDCDCB16DBCA0400868894 /* resample_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resample_sse.h; sourceTree = ""; }; + A1FDCDCC16DBCA0400868894 /* resample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = resample.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; + A1FDCDCD16DBCA0400868894 /* sb_celp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sb_celp.c; sourceTree = ""; }; + A1FDCDCE16DBCA0400868894 /* sb_celp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sb_celp.h; sourceTree = ""; }; + A1FDCDCF16DBCA0400868894 /* scal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scal.c; sourceTree = ""; }; + A1FDCDD016DBCA0400868894 /* smallft.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = smallft.c; sourceTree = ""; }; + A1FDCDD116DBCA0400868894 /* smallft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = smallft.h; sourceTree = ""; }; + A1FDCDD216DBCA0400868894 /* speex_callbacks.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = speex_callbacks.c; sourceTree = ""; }; + A1FDCDD316DBCA0400868894 /* speex_header.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = speex_header.c; sourceTree = ""; }; + A1FDCDD416DBCA0400868894 /* speex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = speex.c; sourceTree = ""; }; + A1FDCDD516DBCA0400868894 /* stack_alloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack_alloc.h; sourceTree = ""; }; + A1FDCDD616DBCA0400868894 /* stereo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stereo.c; sourceTree = ""; }; + A1FDCDD716DBCA0400868894 /* testdenoise.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testdenoise.c; sourceTree = ""; }; + A1FDCDD816DBCA0400868894 /* testecho.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testecho.c; sourceTree = ""; }; + A1FDCDD916DBCA0400868894 /* testenc_uwb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testenc_uwb.c; sourceTree = ""; }; + A1FDCDDA16DBCA0400868894 /* testenc_wb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testenc_wb.c; sourceTree = ""; }; + A1FDCDDB16DBCA0400868894 /* testenc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testenc.c; sourceTree = ""; }; + A1FDCDDC16DBCA0400868894 /* testjitter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testjitter.c; sourceTree = ""; }; + A1FDCDDD16DBCA0400868894 /* vbr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vbr.c; sourceTree = ""; }; + A1FDCDDE16DBCA0400868894 /* vbr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vbr.h; sourceTree = ""; }; + A1FDCDDF16DBCA0400868894 /* vorbis_psy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vorbis_psy.h; sourceTree = ""; }; + A1FDCDE016DBCA0400868894 /* vq_arm4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vq_arm4.h; sourceTree = ""; }; + A1FDCDE116DBCA0400868894 /* vq_bfin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vq_bfin.h; sourceTree = ""; }; + A1FDCDE216DBCA0400868894 /* vq_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vq_sse.h; sourceTree = ""; }; + A1FDCDE316DBCA0400868894 /* vq.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vq.c; sourceTree = ""; }; + A1FDCDE416DBCA0400868894 /* vq.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vq.h; sourceTree = ""; }; + A1FDCDE516DBCA0500868894 /* window.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = window.c; sourceTree = ""; }; + A1FDCE6816DBCFCE00868894 /* bitwise.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bitwise.c; sourceTree = ""; }; + A1FDCE6916DBCFCE00868894 /* config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; + A1FDCE6A16DBCFCE00868894 /* framing.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = framing.c; sourceTree = ""; }; + A1FDCE6C16DBCFCE00868894 /* config_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = config_types.h; sourceTree = ""; }; + A1FDCE6D16DBCFCE00868894 /* ogg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ogg.h; sourceTree = ""; }; + A1FDCE6E16DBCFCE00868894 /* os_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = os_types.h; sourceTree = ""; }; + A1FDCE7016DBCFCE00868894 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + A1FDCE7116DBCFCE00868894 /* Makefile.am */ = {isa = PBXFileReference; lastKnownFileType = text; path = Makefile.am; sourceTree = ""; }; + A1FDCE7216DBCFCE00868894 /* Makefile.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = Makefile.in; sourceTree = ""; }; + A1FDCE7316DBCFCE00868894 /* speex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex.h; sourceTree = ""; }; + A1FDCE7416DBCFCE00868894 /* speex_bits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_bits.h; sourceTree = ""; }; + A1FDCE7516DBCFCE00868894 /* speex_buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_buffer.h; sourceTree = ""; }; + A1FDCE7616DBCFCE00868894 /* speex_callbacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_callbacks.h; sourceTree = ""; }; + A1FDCE7716DBCFCE00868894 /* speex_config_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_config_types.h; sourceTree = ""; }; + A1FDCE7816DBCFCE00868894 /* speex_config_types.h.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = speex_config_types.h.in; sourceTree = ""; }; + A1FDCE7916DBCFCE00868894 /* speex_echo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_echo.h; sourceTree = ""; }; + A1FDCE7A16DBCFCE00868894 /* speex_header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_header.h; sourceTree = ""; }; + A1FDCE7B16DBCFCE00868894 /* speex_jitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_jitter.h; sourceTree = ""; }; + A1FDCE7C16DBCFCE00868894 /* speex_preprocess.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_preprocess.h; sourceTree = ""; }; + A1FDCE7D16DBCFCE00868894 /* speex_resampler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_resampler.h; sourceTree = ""; }; + A1FDCE7E16DBCFCE00868894 /* speex_stereo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_stereo.h; sourceTree = ""; }; + A1FDCE7F16DBCFCE00868894 /* speex_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = speex_types.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A1FDCBF716DBC57D00868894 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A1FDCBF116DBC57D00868894 = { + isa = PBXGroup; + children = ( + A1FDCE6716DBCFCE00868894 /* include */, + A1FDCC0716DBC5A300868894 /* src */, + A1FDCBFB16DBC57D00868894 /* Products */, + ); + sourceTree = ""; + }; + A1FDCBFB16DBC57D00868894 /* Products */ = { + isa = PBXGroup; + children = ( + A1FDCBFA16DBC57D00868894 /* libspeex.a */, + ); + name = Products; + sourceTree = ""; + }; + A1FDCC0716DBC5A300868894 /* src */ = { + isa = PBXGroup; + children = ( + A1FDCD8916DBCA0400868894 /* _kiss_fft_guts.h */, + A1FDCD8A16DBCA0400868894 /* arch.h */, + A1FDCD8B16DBCA0400868894 /* bits.c */, + A1FDCD8C16DBCA0400868894 /* buffer.c */, + A1FDCD8D16DBCA0400868894 /* cb_search_arm4.h */, + A1FDCD8E16DBCA0400868894 /* cb_search_bfin.h */, + A1FDCD8F16DBCA0400868894 /* cb_search_sse.h */, + A1FDCD9016DBCA0400868894 /* cb_search.c */, + A1FDCD9116DBCA0400868894 /* cb_search.h */, + A1FDCD9316DBCA0400868894 /* exc_5_64_table.c */, + A1FDCD9416DBCA0400868894 /* exc_5_256_table.c */, + A1FDCD9516DBCA0400868894 /* exc_8_128_table.c */, + A1FDCD9616DBCA0400868894 /* exc_10_16_table.c */, + A1FDCD9716DBCA0400868894 /* exc_10_32_table.c */, + A1FDCD9816DBCA0400868894 /* exc_20_32_table.c */, + A1FDCD9916DBCA0400868894 /* fftwrap.c */, + A1FDCD9A16DBCA0400868894 /* fftwrap.h */, + A1FDCD9B16DBCA0400868894 /* filterbank.c */, + A1FDCD9C16DBCA0400868894 /* filterbank.h */, + A1FDCD9D16DBCA0400868894 /* filters_arm4.h */, + A1FDCD9E16DBCA0400868894 /* filters_bfin.h */, + A1FDCD9F16DBCA0400868894 /* filters_sse.h */, + A1FDCDA016DBCA0400868894 /* filters.c */, + A1FDCDA116DBCA0400868894 /* filters.h */, + A1FDCDA216DBCA0400868894 /* fixed_arm4.h */, + A1FDCDA316DBCA0400868894 /* fixed_arm5e.h */, + A1FDCDA416DBCA0400868894 /* fixed_bfin.h */, + A1FDCDA516DBCA0400868894 /* fixed_debug.h */, + A1FDCDA616DBCA0400868894 /* fixed_generic.h */, + A1FDCDA716DBCA0400868894 /* gain_table_lbr.c */, + A1FDCDA816DBCA0400868894 /* gain_table.c */, + A1FDCDA916DBCA0400868894 /* hexc_10_32_table.c */, + A1FDCDAA16DBCA0400868894 /* hexc_table.c */, + A1FDCDAB16DBCA0400868894 /* high_lsp_tables.c */, + A1FDCDAC16DBCA0400868894 /* jitter.c */, + A1FDCDAD16DBCA0400868894 /* kiss_fft.c */, + A1FDCDAE16DBCA0400868894 /* kiss_fft.h */, + A1FDCDAF16DBCA0400868894 /* kiss_fftr.c */, + A1FDCDB016DBCA0400868894 /* kiss_fftr.h */, + A1FDCDB116DBCA0400868894 /* lpc_bfin.h */, + A1FDCDB216DBCA0400868894 /* lpc.c */, + A1FDCDB316DBCA0400868894 /* lpc.h */, + A1FDCDB416DBCA0400868894 /* lsp_bfin.h */, + A1FDCDB516DBCA0400868894 /* lsp_tables_nb.c */, + A1FDCDB616DBCA0400868894 /* lsp.c */, + A1FDCDB716DBCA0400868894 /* lsp.h */, + A1FDCDB816DBCA0400868894 /* ltp_arm4.h */, + A1FDCDB916DBCA0400868894 /* ltp_bfin.h */, + A1FDCDBA16DBCA0400868894 /* ltp_sse.h */, + A1FDCDBB16DBCA0400868894 /* ltp.c */, + A1FDCDBC16DBCA0400868894 /* ltp.h */, + A1FDCDBD16DBCA0400868894 /* math_approx.h */, + A1FDCDBE16DBCA0400868894 /* mdf.c */, + A1FDCDBF16DBCA0400868894 /* misc_bfin.h */, + A1FDCDC016DBCA0400868894 /* modes_wb.c */, + A1FDCDC116DBCA0400868894 /* modes.c */, + A1FDCDC216DBCA0400868894 /* modes.h */, + A1FDCDC316DBCA0400868894 /* nb_celp.c */, + A1FDCDC416DBCA0400868894 /* nb_celp.h */, + A1FDCDC516DBCA0400868894 /* os_support.h */, + A1FDCDC616DBCA0400868894 /* preprocess.c */, + A1FDCDC716DBCA0400868894 /* pseudofloat.h */, + A1FDCDC816DBCA0400868894 /* quant_lsp_bfin.h */, + A1FDCDC916DBCA0400868894 /* quant_lsp.c */, + A1FDCDCA16DBCA0400868894 /* quant_lsp.h */, + A1FDCDCB16DBCA0400868894 /* resample_sse.h */, + A1FDCDCC16DBCA0400868894 /* resample.c */, + A1FDCDCD16DBCA0400868894 /* sb_celp.c */, + A1FDCDCE16DBCA0400868894 /* sb_celp.h */, + A1FDCDCF16DBCA0400868894 /* scal.c */, + A1FDCDD016DBCA0400868894 /* smallft.c */, + A1FDCDD116DBCA0400868894 /* smallft.h */, + A1FDCDD216DBCA0400868894 /* speex_callbacks.c */, + A1FDCDD316DBCA0400868894 /* speex_header.c */, + A1FDCDD416DBCA0400868894 /* speex.c */, + A1FDCDD516DBCA0400868894 /* stack_alloc.h */, + A1FDCDD616DBCA0400868894 /* stereo.c */, + A1FDCDD716DBCA0400868894 /* testdenoise.c */, + A1FDCDD816DBCA0400868894 /* testecho.c */, + A1FDCDD916DBCA0400868894 /* testenc_uwb.c */, + A1FDCDDA16DBCA0400868894 /* testenc_wb.c */, + A1FDCDDB16DBCA0400868894 /* testenc.c */, + A1FDCDDC16DBCA0400868894 /* testjitter.c */, + A1FDCDDD16DBCA0400868894 /* vbr.c */, + A1FDCDDE16DBCA0400868894 /* vbr.h */, + A1FDCDDF16DBCA0400868894 /* vorbis_psy.h */, + A1FDCDE016DBCA0400868894 /* vq_arm4.h */, + A1FDCDE116DBCA0400868894 /* vq_bfin.h */, + A1FDCDE216DBCA0400868894 /* vq_sse.h */, + A1FDCDE316DBCA0400868894 /* vq.c */, + A1FDCDE416DBCA0400868894 /* vq.h */, + A1FDCDE516DBCA0500868894 /* window.c */, + ); + name = src; + sourceTree = ""; + }; + A1FDCE6716DBCFCE00868894 /* include */ = { + isa = PBXGroup; + children = ( + A1FDCE6816DBCFCE00868894 /* bitwise.c */, + A1FDCE6916DBCFCE00868894 /* config.h */, + A1FDCE6A16DBCFCE00868894 /* framing.c */, + A1FDCE6B16DBCFCE00868894 /* ogg */, + A1FDCE6F16DBCFCE00868894 /* speex */, + ); + path = include; + sourceTree = ""; + }; + A1FDCE6B16DBCFCE00868894 /* ogg */ = { + isa = PBXGroup; + children = ( + A1FDCE6C16DBCFCE00868894 /* config_types.h */, + A1FDCE6D16DBCFCE00868894 /* ogg.h */, + A1FDCE6E16DBCFCE00868894 /* os_types.h */, + ); + path = ogg; + sourceTree = ""; + }; + A1FDCE6F16DBCFCE00868894 /* speex */ = { + isa = PBXGroup; + children = ( + A1FDCE7016DBCFCE00868894 /* Makefile */, + A1FDCE7116DBCFCE00868894 /* Makefile.am */, + A1FDCE7216DBCFCE00868894 /* Makefile.in */, + A1FDCE7316DBCFCE00868894 /* speex.h */, + A1FDCE7416DBCFCE00868894 /* speex_bits.h */, + A1FDCE7516DBCFCE00868894 /* speex_buffer.h */, + A1FDCE7616DBCFCE00868894 /* speex_callbacks.h */, + A1FDCE7716DBCFCE00868894 /* speex_config_types.h */, + A1FDCE7816DBCFCE00868894 /* speex_config_types.h.in */, + A1FDCE7916DBCFCE00868894 /* speex_echo.h */, + A1FDCE7A16DBCFCE00868894 /* speex_header.h */, + A1FDCE7B16DBCFCE00868894 /* speex_jitter.h */, + A1FDCE7C16DBCFCE00868894 /* speex_preprocess.h */, + A1FDCE7D16DBCFCE00868894 /* speex_resampler.h */, + A1FDCE7E16DBCFCE00868894 /* speex_stereo.h */, + A1FDCE7F16DBCFCE00868894 /* speex_types.h */, + ); + path = speex; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A1FDCBF816DBC57D00868894 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A1FDCDE616DBCA0500868894 /* _kiss_fft_guts.h in Headers */, + A1FDCDE716DBCA0500868894 /* arch.h in Headers */, + A1FDCDEA16DBCA0500868894 /* cb_search_arm4.h in Headers */, + A1FDCDEB16DBCA0500868894 /* cb_search_bfin.h in Headers */, + A1FDCDEC16DBCA0500868894 /* cb_search_sse.h in Headers */, + A1FDCDEE16DBCA0500868894 /* cb_search.h in Headers */, + A1FDCDF716DBCA0500868894 /* fftwrap.h in Headers */, + A1FDCDF916DBCA0500868894 /* filterbank.h in Headers */, + A1FDCDFA16DBCA0500868894 /* filters_arm4.h in Headers */, + A1FDCDFB16DBCA0500868894 /* filters_bfin.h in Headers */, + A1FDCDFC16DBCA0500868894 /* filters_sse.h in Headers */, + A1FDCDFE16DBCA0500868894 /* filters.h in Headers */, + A1FDCDFF16DBCA0500868894 /* fixed_arm4.h in Headers */, + A1FDCE0016DBCA0500868894 /* fixed_arm5e.h in Headers */, + A1FDCE0116DBCA0500868894 /* fixed_bfin.h in Headers */, + A1FDCE0216DBCA0500868894 /* fixed_debug.h in Headers */, + A1FDCE0316DBCA0500868894 /* fixed_generic.h in Headers */, + A1FDCE0B16DBCA0500868894 /* kiss_fft.h in Headers */, + A1FDCE0D16DBCA0500868894 /* kiss_fftr.h in Headers */, + A1FDCE0E16DBCA0500868894 /* lpc_bfin.h in Headers */, + A1FDCE1016DBCA0500868894 /* lpc.h in Headers */, + A1FDCE1116DBCA0500868894 /* lsp_bfin.h in Headers */, + A1FDCE1416DBCA0500868894 /* lsp.h in Headers */, + A1FDCE1516DBCA0500868894 /* ltp_arm4.h in Headers */, + A1FDCE1616DBCA0500868894 /* ltp_bfin.h in Headers */, + A1FDCE1716DBCA0500868894 /* ltp_sse.h in Headers */, + A1FDCE1916DBCA0500868894 /* ltp.h in Headers */, + A1FDCE1A16DBCA0500868894 /* math_approx.h in Headers */, + A1FDCE1C16DBCA0500868894 /* misc_bfin.h in Headers */, + A1FDCE1F16DBCA0500868894 /* modes.h in Headers */, + A1FDCE2116DBCA0500868894 /* nb_celp.h in Headers */, + A1FDCE2216DBCA0500868894 /* os_support.h in Headers */, + A1FDCE2416DBCA0500868894 /* pseudofloat.h in Headers */, + A1FDCE2516DBCA0500868894 /* quant_lsp_bfin.h in Headers */, + A1FDCE2716DBCA0500868894 /* quant_lsp.h in Headers */, + A1FDCE2816DBCA0500868894 /* resample_sse.h in Headers */, + A1FDCE2B16DBCA0500868894 /* sb_celp.h in Headers */, + A1FDCE2E16DBCA0500868894 /* smallft.h in Headers */, + A1FDCE3216DBCA0500868894 /* stack_alloc.h in Headers */, + A1FDCE3B16DBCA0500868894 /* vbr.h in Headers */, + A1FDCE3C16DBCA0500868894 /* vorbis_psy.h in Headers */, + A1FDCE3D16DBCA0500868894 /* vq_arm4.h in Headers */, + A1FDCE3E16DBCA0500868894 /* vq_bfin.h in Headers */, + A1FDCE3F16DBCA0500868894 /* vq_sse.h in Headers */, + A1FDCE4116DBCA0500868894 /* vq.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A1FDCBF916DBC57D00868894 /* speex */ = { + isa = PBXNativeTarget; + buildConfigurationList = A1FDCBFE16DBC57D00868894 /* Build configuration list for PBXNativeTarget "speex" */; + buildPhases = ( + A1FDCBF616DBC57D00868894 /* Sources */, + A1FDCBF716DBC57D00868894 /* Frameworks */, + A1FDCBF816DBC57D00868894 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = speex; + productName = speex; + productReference = A1FDCBFA16DBC57D00868894 /* libspeex.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A1FDCBF216DBC57D00868894 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Twisted Oak Studios"; + }; + buildConfigurationList = A1FDCBF516DBC57D00868894 /* Build configuration list for PBXProject "speex" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A1FDCBF116DBC57D00868894; + productRefGroup = A1FDCBFB16DBC57D00868894 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A1FDCBF916DBC57D00868894 /* speex */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + A1FDCBF616DBC57D00868894 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1FDCDE816DBCA0500868894 /* bits.c in Sources */, + A1FDCDE916DBCA0500868894 /* buffer.c in Sources */, + A1FDCDED16DBCA0500868894 /* cb_search.c in Sources */, + A1FDCDF016DBCA0500868894 /* exc_5_64_table.c in Sources */, + A1FDCDF116DBCA0500868894 /* exc_5_256_table.c in Sources */, + A1FDCDF216DBCA0500868894 /* exc_8_128_table.c in Sources */, + A1FDCDF316DBCA0500868894 /* exc_10_16_table.c in Sources */, + A1FDCDF416DBCA0500868894 /* exc_10_32_table.c in Sources */, + A1FDCDF516DBCA0500868894 /* exc_20_32_table.c in Sources */, + A1FDCDF616DBCA0500868894 /* fftwrap.c in Sources */, + A1FDCDF816DBCA0500868894 /* filterbank.c in Sources */, + A1FDCDFD16DBCA0500868894 /* filters.c in Sources */, + A1FDCE0416DBCA0500868894 /* gain_table_lbr.c in Sources */, + A1FDCE0516DBCA0500868894 /* gain_table.c in Sources */, + A1FDCE0616DBCA0500868894 /* hexc_10_32_table.c in Sources */, + A1FDCE0716DBCA0500868894 /* hexc_table.c in Sources */, + A1FDCE0816DBCA0500868894 /* high_lsp_tables.c in Sources */, + A1FDCE0916DBCA0500868894 /* jitter.c in Sources */, + A1FDCE0A16DBCA0500868894 /* kiss_fft.c in Sources */, + A1FDCE0C16DBCA0500868894 /* kiss_fftr.c in Sources */, + A1FDCE0F16DBCA0500868894 /* lpc.c in Sources */, + A1FDCE1216DBCA0500868894 /* lsp_tables_nb.c in Sources */, + A1FDCE1316DBCA0500868894 /* lsp.c in Sources */, + A1FDCE1816DBCA0500868894 /* ltp.c in Sources */, + A1FDCE1B16DBCA0500868894 /* mdf.c in Sources */, + A1FDCE1D16DBCA0500868894 /* modes_wb.c in Sources */, + A1FDCE1E16DBCA0500868894 /* modes.c in Sources */, + A1FDCE2016DBCA0500868894 /* nb_celp.c in Sources */, + A1FDCE2316DBCA0500868894 /* preprocess.c in Sources */, + A1FDCE2616DBCA0500868894 /* quant_lsp.c in Sources */, + A1FDCE2916DBCA0500868894 /* resample.c in Sources */, + A1FDCE2A16DBCA0500868894 /* sb_celp.c in Sources */, + A1FDCE2C16DBCA0500868894 /* scal.c in Sources */, + A1FDCE2D16DBCA0500868894 /* smallft.c in Sources */, + A1FDCE2F16DBCA0500868894 /* speex_callbacks.c in Sources */, + A1FDCE3016DBCA0500868894 /* speex_header.c in Sources */, + A1FDCE3116DBCA0500868894 /* speex.c in Sources */, + A1FDCE3316DBCA0500868894 /* stereo.c in Sources */, + A1FDCE3416DBCA0500868894 /* testdenoise.c in Sources */, + A1FDCE3516DBCA0500868894 /* testecho.c in Sources */, + A1FDCE3616DBCA0500868894 /* testenc_uwb.c in Sources */, + A1FDCE3716DBCA0500868894 /* testenc_wb.c in Sources */, + A1FDCE3816DBCA0500868894 /* testenc.c in Sources */, + A1FDCE3916DBCA0500868894 /* testjitter.c in Sources */, + A1FDCE3A16DBCA0500868894 /* vbr.c in Sources */, + A1FDCE4016DBCA0500868894 /* vq.c in Sources */, + A1FDCE4216DBCA0500868894 /* window.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A1FDCBFC16DBC57D00868894 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/RedPhone/src/ref/libspeex/.libs\"", + "\"$(SRCROOT)\"", + ); + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + A1FDCBFD16DBC57D00868894 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/RedPhone/src/ref/libspeex/.libs\"", + "\"$(SRCROOT)\"", + ); + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = iphoneos; + }; + name = Release; + }; + A1FDCBFF16DBC57D00868894 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + "HEADER_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/include"; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + "USER_HEADER_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/include"; + }; + name = Debug; + }; + A1FDCC0016DBC57D00868894 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + "HEADER_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/include"; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + "USER_HEADER_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/include"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A1FDCBF516DBC57D00868894 /* Build configuration list for PBXProject "speex" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1FDCBFC16DBC57D00868894 /* Debug */, + A1FDCBFD16DBC57D00868894 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A1FDCBFE16DBC57D00868894 /* Build configuration list for PBXNativeTarget "speex" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1FDCBFF16DBC57D00868894 /* Debug */, + A1FDCC0016DBC57D00868894 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A1FDCBF216DBC57D00868894 /* Project object */; +} diff --git a/Libraries/speex/speex_callbacks.c b/Libraries/speex/speex_callbacks.c new file mode 100644 index 000000000..0e077c380 --- /dev/null +++ b/Libraries/speex/speex_callbacks.c @@ -0,0 +1,144 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File speex_callbacks.c + Callback handling and in-band signalling + + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "arch.h" +#include "os_support.h" + +EXPORT int speex_inband_handler(SpeexBits *bits, SpeexCallback *callback_list, void *state) +{ + int id; + SpeexCallback *callback; + /*speex_bits_advance(bits, 5);*/ + id=speex_bits_unpack_unsigned(bits, 4); + callback = callback_list+id; + + if (callback->func) + { + return callback->func(bits, state, callback->data); + } else + /*If callback is not registered, skip the right number of bits*/ + { + int adv; + if (id<2) + adv = 1; + else if (id<8) + adv = 4; + else if (id<10) + adv = 8; + else if (id<12) + adv = 16; + else if (id<14) + adv = 32; + else + adv = 64; + speex_bits_advance(bits, adv); + } + return 0; +} + +EXPORT int speex_std_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_MODE, &m); + return 0; +} + +EXPORT int speex_std_low_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_LOW_MODE, &m); + return 0; +} + +EXPORT int speex_std_high_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_HIGH_MODE, &m); + return 0; +} + +#ifndef DISABLE_VBR +EXPORT int speex_std_vbr_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t vbr; + vbr = speex_bits_unpack_unsigned(bits, 1); + speex_encoder_ctl(data, SPEEX_SET_VBR, &vbr); + return 0; +} +#endif /* #ifndef DISABLE_VBR */ + +EXPORT int speex_std_enh_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t enh; + enh = speex_bits_unpack_unsigned(bits, 1); + speex_decoder_ctl(data, SPEEX_SET_ENH, &enh); + return 0; +} + +#ifndef DISABLE_VBR +EXPORT int speex_std_vbr_quality_request_handler(SpeexBits *bits, void *state, void *data) +{ + float qual; + qual = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_VBR_QUALITY, &qual); + return 0; +} +#endif /* #ifndef DISABLE_VBR */ + +EXPORT int speex_std_char_handler(SpeexBits *bits, void *state, void *data) +{ + unsigned char ch; + ch = speex_bits_unpack_unsigned(bits, 8); + _speex_putc(ch, data); + /*printf("speex_std_char_handler ch=%x\n", ch);*/ + return 0; +} + + + +/* Default handler for user callbacks: skip it */ +EXPORT int speex_default_user_handler(SpeexBits *bits, void *state, void *data) +{ + int req_size = speex_bits_unpack_unsigned(bits, 4); + speex_bits_advance(bits, 5+8*req_size); + return 0; +} diff --git a/Libraries/speex/speex_header.c b/Libraries/speex/speex_header.c new file mode 100644 index 000000000..b7430595f --- /dev/null +++ b/Libraries/speex/speex_header.c @@ -0,0 +1,200 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_header.c + Describes the Speex header + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "arch.h" +#include +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +/** Convert little endian */ +static inline spx_int32_t le_int(spx_int32_t i) +{ +#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) ) + spx_uint32_t ui, ret; + ui = i; + ret = ui>>24; + ret |= (ui>>8)&0x0000ff00; + ret |= (ui<<8)&0x00ff0000; + ret |= (ui<<24); + return ret; +#else + return i; +#endif +} + +#define ENDIAN_SWITCH(x) {x=le_int(x);} + + +/* +typedef struct SpeexHeader { + char speex_string[8]; + char speex_version[SPEEX_HEADER_VERSION_LENGTH]; + int speex_version_id; + int header_size; + int rate; + int mode; + int mode_bitstream_version; + int nb_channels; + int bitrate; + int frame_size; + int vbr; + int frames_per_packet; + int extra_headers; + int reserved1; + int reserved2; +} SpeexHeader; +*/ + +EXPORT void speex_init_header(SpeexHeader *header, int rate, int nb_channels, const SpeexMode *m) +{ + int i; + const char *h="Speex "; + /* + strncpy(header->speex_string, "Speex ", 8); + strncpy(header->speex_version, SPEEX_VERSION, SPEEX_HEADER_VERSION_LENGTH-1); + header->speex_version[SPEEX_HEADER_VERSION_LENGTH-1]=0; + */ + for (i=0;i<8;i++) + header->speex_string[i]=h[i]; + for (i=0;ispeex_version[i]=SPEEX_VERSION[i]; + for (;ispeex_version[i]=0; + + header->speex_version_id = 1; + header->header_size = sizeof(SpeexHeader); + + header->rate = rate; + header->mode = m->modeID; + header->mode_bitstream_version = m->bitstream_version; + if (m->modeID<0) + speex_warning("This mode is meant to be used alone"); + header->nb_channels = nb_channels; + header->bitrate = -1; + speex_mode_query(m, SPEEX_MODE_FRAME_SIZE, &header->frame_size); + header->vbr = 0; + + header->frames_per_packet = 0; + header->extra_headers = 0; + header->reserved1 = 0; + header->reserved2 = 0; +} + +EXPORT char *speex_header_to_packet(SpeexHeader *header, int *size) +{ + SpeexHeader *le_header; + le_header = (SpeexHeader*)speex_alloc(sizeof(SpeexHeader)); + + SPEEX_COPY(le_header, header, 1); + + /*Make sure everything is now little-endian*/ + ENDIAN_SWITCH(le_header->speex_version_id); + ENDIAN_SWITCH(le_header->header_size); + ENDIAN_SWITCH(le_header->rate); + ENDIAN_SWITCH(le_header->mode); + ENDIAN_SWITCH(le_header->mode_bitstream_version); + ENDIAN_SWITCH(le_header->nb_channels); + ENDIAN_SWITCH(le_header->bitrate); + ENDIAN_SWITCH(le_header->frame_size); + ENDIAN_SWITCH(le_header->vbr); + ENDIAN_SWITCH(le_header->frames_per_packet); + ENDIAN_SWITCH(le_header->extra_headers); + + *size = sizeof(SpeexHeader); + return (char *)le_header; +} + +EXPORT SpeexHeader *speex_packet_to_header(char *packet, int size) +{ + int i; + SpeexHeader *le_header; + const char *h = "Speex "; + for (i=0;i<8;i++) + if (packet[i]!=h[i]) + { + speex_notify("This doesn't look like a Speex file"); + return NULL; + } + + /*FIXME: Do we allow larger headers?*/ + if (size < (int)sizeof(SpeexHeader)) + { + speex_notify("Speex header too small"); + return NULL; + } + + le_header = (SpeexHeader*)speex_alloc(sizeof(SpeexHeader)); + + SPEEX_COPY(le_header, (SpeexHeader*)packet, 1); + + /*Make sure everything is converted correctly from little-endian*/ + ENDIAN_SWITCH(le_header->speex_version_id); + ENDIAN_SWITCH(le_header->header_size); + ENDIAN_SWITCH(le_header->rate); + ENDIAN_SWITCH(le_header->mode); + ENDIAN_SWITCH(le_header->mode_bitstream_version); + ENDIAN_SWITCH(le_header->nb_channels); + ENDIAN_SWITCH(le_header->bitrate); + ENDIAN_SWITCH(le_header->frame_size); + ENDIAN_SWITCH(le_header->vbr); + ENDIAN_SWITCH(le_header->frames_per_packet); + ENDIAN_SWITCH(le_header->extra_headers); + + if (le_header->mode >= SPEEX_NB_MODES || le_header->mode < 0) + { + speex_notify("Invalid mode specified in Speex header"); + speex_free (le_header); + return NULL; + } + + if (le_header->nb_channels>2) + le_header->nb_channels = 2; + if (le_header->nb_channels<1) + le_header->nb_channels = 1; + + return le_header; + +} + +EXPORT void speex_header_free(void *ptr) +{ + speex_free(ptr); +} diff --git a/Libraries/speex/stack_alloc.h b/Libraries/speex/stack_alloc.h new file mode 100644 index 000000000..5264e666b --- /dev/null +++ b/Libraries/speex/stack_alloc.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file stack_alloc.h + @brief Temporary memory allocation on stack +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef STACK_ALLOC_H +#define STACK_ALLOC_H + +#ifdef USE_ALLOCA +# ifdef WIN32 +# include +# else +# ifdef HAVE_ALLOCA_H +# include +# else +# include +# endif +# endif +#endif + +/** + * @def ALIGN(stack, size) + * + * Aligns the stack to a 'size' boundary + * + * @param stack Stack + * @param size New size boundary + */ + +/** + * @def PUSH(stack, size, type) + * + * Allocates 'size' elements of type 'type' on the stack + * + * @param stack Stack + * @param size Number of elements + * @param type Type of element + */ + +/** + * @def VARDECL(var) + * + * Declare variable on stack + * + * @param var Variable to declare + */ + +/** + * @def ALLOC(var, size, type) + * + * Allocate 'size' elements of 'type' on stack + * + * @param var Name of variable to allocate + * @param size Number of elements + * @param type Type of element + */ + +#ifdef ENABLE_VALGRIND + +#include + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#else + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#endif + +#if defined(VAR_ARRAYS) +#define VARDECL(var) +#define ALLOC(var, size, type) type var[size] +#elif defined(USE_ALLOCA) +#define VARDECL(var) var +#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size)) +#else +#define VARDECL(var) var +#define ALLOC(var, size, type) var = PUSH(stack, size, type) +#endif + + +#endif diff --git a/Libraries/speex/stereo.c b/Libraries/speex/stereo.c new file mode 100644 index 000000000..db5ea4a85 --- /dev/null +++ b/Libraries/speex/stereo.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: stereo.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "math_approx.h" +#include "vq.h" +#include +#include "os_support.h" + +typedef struct RealSpeexStereoState { + spx_word32_t balance; /**< Left/right balance info */ + spx_word32_t e_ratio; /**< Ratio of energies: E(left+right)/[E(left)+E(right)] */ + spx_word32_t smooth_left; /**< Smoothed left channel gain */ + spx_word32_t smooth_right; /**< Smoothed right channel gain */ + spx_uint32_t reserved1; /**< Reserved for future use */ + spx_int32_t reserved2; /**< Reserved for future use */ +} RealSpeexStereoState; + + +/*float e_ratio_quant[4] = {1, 1.26, 1.587, 2};*/ +#ifndef FIXED_POINT +static const float e_ratio_quant[4] = {.25f, .315f, .397f, .5f}; +static const float e_ratio_quant_bounds[3] = {0.2825f, 0.356f, 0.4485f}; +#else +static const spx_word16_t e_ratio_quant[4] = {8192, 10332, 13009, 16384}; +static const spx_word16_t e_ratio_quant_bounds[3] = {9257, 11665, 14696}; +static const spx_word16_t balance_bounds[31] = {18, 23, 30, 38, 49, 63, 81, 104, + 134, 172, 221, 284, 364, 468, 600, 771, + 990, 1271, 1632, 2096, 2691, 3455, 4436, 5696, + 7314, 9392, 12059, 15484, 19882, 25529, 32766}; +#endif + +/* This is an ugly compatibility hack that properly resets the stereo state + In case it it compiled in fixed-point, but initialised with the deprecated + floating point static initialiser */ +#ifdef FIXED_POINT +#define COMPATIBILITY_HACK(s) do {if ((s)->reserved1 != 0xdeadbeef) speex_stereo_state_reset((SpeexStereoState*)s); } while (0); +#else +#define COMPATIBILITY_HACK(s) +#endif + +EXPORT SpeexStereoState *speex_stereo_state_init() +{ + SpeexStereoState *stereo = speex_alloc(sizeof(SpeexStereoState)); + speex_stereo_state_reset(stereo); + return stereo; +} + +EXPORT void speex_stereo_state_reset(SpeexStereoState *_stereo) +{ + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; +#ifdef FIXED_POINT + stereo->balance = 65536; + stereo->e_ratio = 16384; + stereo->smooth_left = 16384; + stereo->smooth_right = 16384; + stereo->reserved1 = 0xdeadbeef; + stereo->reserved2 = 0; +#else + stereo->balance = 1.0f; + stereo->e_ratio = .5f; + stereo->smooth_left = 1.f; + stereo->smooth_right = 1.f; + stereo->reserved1 = 0; + stereo->reserved2 = 0; +#endif +} + +EXPORT void speex_stereo_state_destroy(SpeexStereoState *stereo) +{ + speex_free(stereo); +} + +#ifndef DISABLE_FLOAT_API +EXPORT void speex_encode_stereo(float *data, int frame_size, SpeexBits *bits) +{ + int i, tmp; + float e_left=0, e_right=0, e_tot=0; + float balance, e_ratio; + for (i=0;i0) + speex_bits_pack(bits, 0, 1); + else + speex_bits_pack(bits, 1, 1); + balance=floor(.5+fabs(balance)); + if (balance>30) + balance=31; + + speex_bits_pack(bits, (int)balance, 5); + + /* FIXME: this is a hack */ + tmp=scal_quant(e_ratio*Q15_ONE, e_ratio_quant_bounds, 4); + speex_bits_pack(bits, tmp, 2); +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +EXPORT void speex_encode_stereo_int(spx_int16_t *data, int frame_size, SpeexBits *bits) +{ + int i, tmp; + spx_word32_t e_left=0, e_right=0, e_tot=0; + spx_word32_t balance, e_ratio; + spx_word32_t largest, smallest; + int balance_id; +#ifdef FIXED_POINT + int shift; +#endif + + /* In band marker */ + speex_bits_pack(bits, 14, 5); + /* Stereo marker */ + speex_bits_pack(bits, SPEEX_INBAND_STEREO, 4); + + for (i=0;i e_right) + { + speex_bits_pack(bits, 0, 1); + largest = e_left; + smallest = e_right; + } else { + speex_bits_pack(bits, 1, 1); + largest = e_right; + smallest = e_left; + } + + /* Balance quantization */ +#ifdef FIXED_POINT + shift = spx_ilog2(largest)-15; + largest = VSHR32(largest, shift-4); + smallest = VSHR32(smallest, shift); + balance = DIV32(largest, ADD32(smallest, 1)); + if (balance > 32767) + balance = 32767; + balance_id = scal_quant(EXTRACT16(balance), balance_bounds, 32); +#else + balance=(largest+1.)/(smallest+1.); + balance=4*log(balance); + balance_id=floor(.5+fabs(balance)); + if (balance_id>30) + balance_id=31; +#endif + + speex_bits_pack(bits, balance_id, 5); + + /* "coherence" quantisation */ +#ifdef FIXED_POINT + shift = spx_ilog2(e_tot); + e_tot = VSHR32(e_tot, shift-25); + e_left = VSHR32(e_left, shift-10); + e_right = VSHR32(e_right, shift-10); + e_ratio = DIV32(e_tot, e_left+e_right+1); +#else + e_ratio = e_tot/(1.+e_left+e_right); +#endif + + tmp=scal_quant(EXTRACT16(e_ratio), e_ratio_quant_bounds, 4); + /*fprintf (stderr, "%d %d %d %d\n", largest, smallest, balance_id, e_ratio);*/ + speex_bits_pack(bits, tmp, 2); +} + +#ifndef DISABLE_FLOAT_API +EXPORT void speex_decode_stereo(float *data, int frame_size, SpeexStereoState *_stereo) +{ + int i; + spx_word32_t balance; + spx_word16_t e_left, e_right, e_ratio; + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; + + COMPATIBILITY_HACK(stereo); + + balance=stereo->balance; + e_ratio=stereo->e_ratio; + + /* These two are Q14, with max value just below 2. */ + e_right = DIV32(QCONST32(1., 22), spx_sqrt(MULT16_32_Q15(e_ratio, ADD32(QCONST32(1., 16), balance)))); + e_left = SHR32(MULT16_16(spx_sqrt(balance), e_right), 8); + + for (i=frame_size-1;i>=0;i--) + { + spx_word16_t tmp=data[i]; + stereo->smooth_left = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_left, QCONST16(0.98, 15)), e_left, QCONST16(0.02, 15)), 15)); + stereo->smooth_right = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_right, QCONST16(0.98, 15)), e_right, QCONST16(0.02, 15)), 15)); + data[2*i] = (float)MULT16_16_P14(stereo->smooth_left, tmp); + data[2*i+1] = (float)MULT16_16_P14(stereo->smooth_right, tmp); + } +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +EXPORT void speex_decode_stereo_int(spx_int16_t *data, int frame_size, SpeexStereoState *_stereo) +{ + int i; + spx_word32_t balance; + spx_word16_t e_left, e_right, e_ratio; + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; + + COMPATIBILITY_HACK(stereo); + + balance=stereo->balance; + e_ratio=stereo->e_ratio; + + /* These two are Q14, with max value just below 2. */ + e_right = DIV32(QCONST32(1., 22), spx_sqrt(MULT16_32_Q15(e_ratio, ADD32(QCONST32(1., 16), balance)))); + e_left = SHR32(MULT16_16(spx_sqrt(balance), e_right), 8); + + for (i=frame_size-1;i>=0;i--) + { + spx_int16_t tmp=data[i]; + stereo->smooth_left = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_left, QCONST16(0.98, 15)), e_left, QCONST16(0.02, 15)), 15)); + stereo->smooth_right = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_right, QCONST16(0.98, 15)), e_right, QCONST16(0.02, 15)), 15)); + data[2*i] = (spx_int16_t)MULT16_16_P14(stereo->smooth_left, tmp); + data[2*i+1] = (spx_int16_t)MULT16_16_P14(stereo->smooth_right, tmp); + } +} + +EXPORT int speex_std_stereo_request_handler(SpeexBits *bits, void *state, void *data) +{ + RealSpeexStereoState *stereo; + spx_word16_t sign=1, dexp; + int tmp; + + stereo = (RealSpeexStereoState*)data; + + COMPATIBILITY_HACK(stereo); + + if (speex_bits_unpack_unsigned(bits, 1)) + sign=-1; + dexp = speex_bits_unpack_unsigned(bits, 5); +#ifndef FIXED_POINT + stereo->balance = exp(sign*.25*dexp); +#else + stereo->balance = spx_exp(MULT16_16(sign, SHL16(dexp, 9))); +#endif + tmp = speex_bits_unpack_unsigned(bits, 2); + stereo->e_ratio = e_ratio_quant[tmp]; + + return 0; +} diff --git a/Libraries/speex/testdenoise.c b/Libraries/speex/testdenoise.c new file mode 100644 index 000000000..49f512023 --- /dev/null +++ b/Libraries/speex/testdenoise.c @@ -0,0 +1,44 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#define NN 160 + +int main() +{ + short in[NN]; + int i; + SpeexPreprocessState *st; + int count=0; + float f; + + st = speex_preprocess_state_init(NN, 8000); + i=1; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DENOISE, &i); + i=0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC, &i); + i=8000; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC_LEVEL, &i); + i=0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB, &i); + f=.0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f); + f=.0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f); + while (1) + { + int vad; + fread(in, sizeof(short), NN, stdin); + if (feof(stdin)) + break; + vad = speex_preprocess_run(st, in); + /*fprintf (stderr, "%d\n", vad);*/ + fwrite(in, sizeof(short), NN, stdout); + count++; + } + speex_preprocess_state_destroy(st); + return 0; +} diff --git a/Libraries/speex/testecho.c b/Libraries/speex/testecho.c new file mode 100644 index 000000000..5ae855f08 --- /dev/null +++ b/Libraries/speex/testecho.c @@ -0,0 +1,53 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include "speex/speex_echo.h" +#include "speex/speex_preprocess.h" + + +#define NN 128 +#define TAIL 1024 + +int main(int argc, char **argv) +{ + FILE *echo_fd, *ref_fd, *e_fd; + short echo_buf[NN], ref_buf[NN], e_buf[NN]; + SpeexEchoState *st; + SpeexPreprocessState *den; + int sampleRate = 8000; + + if (argc != 4) + { + fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n"); + exit(1); + } + echo_fd = fopen(argv[2], "rb"); + ref_fd = fopen(argv[1], "rb"); + e_fd = fopen(argv[3], "wb"); + + st = speex_echo_state_init(NN, TAIL); + den = speex_preprocess_state_init(NN, sampleRate); + speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate); + speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st); + + while (!feof(ref_fd) && !feof(echo_fd)) + { + fread(ref_buf, sizeof(short), NN, ref_fd); + fread(echo_buf, sizeof(short), NN, echo_fd); + speex_echo_cancellation(st, ref_buf, echo_buf, e_buf); + speex_preprocess_run(den, e_buf); + fwrite(e_buf, sizeof(short), NN, e_fd); + } + speex_echo_state_destroy(st); + speex_preprocess_state_destroy(den); + fclose(e_fd); + fclose(echo_fd); + fclose(ref_fd); + return 0; +} diff --git a/Libraries/speex/testenc.c b/Libraries/speex/testenc.c new file mode 100644 index 000000000..44c132f1c --- /dev/null +++ b/Libraries/speex/testenc.c @@ -0,0 +1,146 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 160 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_NB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_NB)); + + /* BEGIN: You probably don't need the following in a real application */ + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + /* END of unnecessary stuff */ + + tmp=1; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=8; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + + /* Turn this off if you want to measure SNR (on by default) */ + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_HIGHPASS, &tmp); + speex_decoder_ctl(dec, SPEEX_SET_HIGHPASS, &tmp); + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + speex_bits_reset(&bits); + + speex_encode_int(st, in_short, &bits); + nbBits = speex_bits_write(&bits, cbits, 200); + bitCount+=bits.nbBits; + + if (argc==4) + fwrite(cbits, 1, nbBits, fbits); + speex_bits_rewind(&bits); + + speex_decode_int(dec, &bits, out_short); + speex_bits_reset(&bits); + + fwrite(&out_short[skip_group_delay], sizeof(short), FRAME_SIZE-skip_group_delay, fout); + skip_group_delay = 0; + } + fprintf (stderr, "Total encoded size: %d bits\n", bitCount); + speex_encoder_destroy(st); + speex_decoder_destroy(dec); + speex_bits_destroy(&bits); + +#ifndef DISABLE_FLOAT_API + { + float sigpow,errpow,snr, seg_snr=0; + sigpow = 0; + errpow = 0; + + /* This code just computes SNR, so you don't need it either */ + rewind(fin); + rewind(fout); + + while ( FRAME_SIZE == fread(in_short, sizeof(short), FRAME_SIZE, fin) + && + FRAME_SIZE == fread(out_short, sizeof(short), FRAME_SIZE,fout) ) + { + float s=0, e=0; + for (i=0;i +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 640 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + float in_float[FRAME_SIZE]; + float sigpow,errpow,snr, seg_snr=0; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + sigpow = 0; + errpow = 0; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_UWB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_UWB)); + + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + tmp=0; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=7; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + for (i=0;i +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 320 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + float sigpow,errpow,snr, seg_snr=0; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + sigpow = 0; + errpow = 0; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_WB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_WB)); + + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + tmp=1; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=8; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=3; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + /*tmp=3; + speex_encoder_ctl(st, SPEEX_SET_HIGH_MODE, &tmp); + tmp=6; + speex_encoder_ctl(st, SPEEX_SET_LOW_MODE, &tmp); +*/ + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + speex_bits_reset(&bits); + + speex_encode_int(st, in_short, &bits); + nbBits = speex_bits_write(&bits, cbits, 200); + bitCount+=bits.nbBits; + + if (argc==4) + fwrite(cbits, 1, nbBits, fbits); + speex_bits_rewind(&bits); + + speex_decode_int(dec, &bits, out_short); + speex_bits_reset(&bits); + + fwrite(&out_short[skip_group_delay], sizeof(short), FRAME_SIZE-skip_group_delay, fout); + skip_group_delay = 0; + } + fprintf (stderr, "Total encoded size: %d bits\n", bitCount); + speex_encoder_destroy(st); + speex_decoder_destroy(dec); + speex_bits_destroy(&bits); + + rewind(fin); + rewind(fout); + + while ( FRAME_SIZE == fread(in_short, sizeof(short), FRAME_SIZE, fin) + && + FRAME_SIZE == fread(out_short, sizeof(short), FRAME_SIZE,fout) ) + { + float s=0, e=0; + for (i=0;i +#include + +union jbpdata { + unsigned int idx; + unsigned char data[4]; +}; + +void synthIn(JitterBufferPacket *in, int idx, int span) { + union jbpdata d; + d.idx = idx; + + in->data = (char*)d.data; + in->len = sizeof(d); + in->timestamp = idx * 10; + in->span = span * 10; + in->sequence = idx; + in->user_data = 0; +} + +void jitterFill(JitterBuffer *jb) { + char buffer[65536]; + JitterBufferPacket in, out; + int i; + + out.data = buffer; + + jitter_buffer_reset(jb); + + for(i=0;i<100;++i) { + synthIn(&in, i, 1); + jitter_buffer_put(jb, &in); + + out.len = 65536; + if (jitter_buffer_get(jb, &out, 10, NULL) != JITTER_BUFFER_OK) { + printf("Fill test failed iteration %d\n", i); + } + if (out.timestamp != i * 10) { + printf("Fill test expected %d got %d\n", i*10, out.timestamp); + } + jitter_buffer_tick(jb); + } +} + +int main() +{ + char buffer[65536]; + JitterBufferPacket in, out; + int i; + + JitterBuffer *jb = jitter_buffer_init(10); + + out.data = buffer; + + /* Frozen sender case */ + jitterFill(jb); + for(i=0;i<100;++i) { + out.len = 65536; + jitter_buffer_get(jb, &out, 10, NULL); + jitter_buffer_tick(jb); + } + synthIn(&in, 100, 1); + jitter_buffer_put(jb, &in); + out.len = 65536; + if (jitter_buffer_get(jb, &out, 10, NULL) != JITTER_BUFFER_OK) { + printf("Failed frozen sender resynchronize\n"); + } else { + printf("Frozen sender: Jitter %d\n", out.timestamp - 100*10); + } + return 0; +} diff --git a/Libraries/speex/vbr.c b/Libraries/speex/vbr.c new file mode 100644 index 000000000..5b7dd9bfa --- /dev/null +++ b/Libraries/speex/vbr.c @@ -0,0 +1,275 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: vbr.c + + VBR-related routines + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "vbr.h" +#include + + +#define sqr(x) ((x)*(x)) + +#define MIN_ENERGY 6000 +#define NOISE_POW .3 + +#ifndef DISABLE_VBR + +const float vbr_nb_thresh[9][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* CNG */ + { 4.0f, 2.5f, 2.0f, 1.2f, 0.5f, 0.0f, -0.5f, -0.7f, -0.8f, -0.9f, -1.0f}, /* 2 kbps */ + {10.0f, 6.5f, 5.2f, 4.5f, 3.9f, 3.5f, 3.0f, 2.5f, 2.3f, 1.8f, 1.0f}, /* 6 kbps */ + {11.0f, 8.8f, 7.5f, 6.5f, 5.0f, 3.9f, 3.9f, 3.9f, 3.5f, 3.0f, 1.0f}, /* 8 kbps */ + {11.0f, 11.0f, 9.9f, 8.5f, 7.0f, 6.0f, 4.5f, 4.0f, 4.0f, 4.0f, 2.0f}, /* 11 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.5f, 8.0f, 7.0f, 6.0f, 5.0f, 3.0f}, /* 15 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.5f, 7.0f, 6.0f, 5.0f}, /* 18 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.8f, 9.5f, 7.5f}, /* 24 kbps */ + { 7.0f, 4.5f, 3.7f, 3.0f, 2.5f, 2.0f, 1.8f, 1.5f, 1.0f, 0.0f, 0.0f} /* 4 kbps */ +}; + + +const float vbr_hb_thresh[5][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* silence */ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* 2 kbps */ + {11.0f, 11.0f, 9.5f, 8.5f, 7.5f, 6.0f, 5.0f, 3.9f, 3.0f, 2.0f, 1.0f}, /* 6 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.7f, 7.8f, 7.0f, 6.5f, 4.0f}, /* 10 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.8f, 7.5f, 5.5f} /* 18 kbps */ +}; + +const float vbr_uhb_thresh[2][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* silence */ + { 3.9f, 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f} /* 2 kbps */ +}; + +void vbr_init(VBRState *vbr) +{ + int i; + + vbr->average_energy=0; + vbr->last_energy=1; + vbr->accum_sum=0; + vbr->energy_alpha=.1; + vbr->soft_pitch=0; + vbr->last_pitch_coef=0; + vbr->last_quality=0; + + vbr->noise_accum = .05*pow(MIN_ENERGY, NOISE_POW); + vbr->noise_accum_count=.05; + vbr->noise_level=vbr->noise_accum/vbr->noise_accum_count; + vbr->consec_noise=0; + + + for (i=0;ilast_log_energy[i] = log(MIN_ENERGY); +} + + +/* + This function should analyse the signal and decide how critical the + coding error will be perceptually. The following factors should be + taken into account: + + -Attacks (positive energy derivative) should be coded with more bits + + -Stationary voiced segments should receive more bits + + -Segments with (very) low absolute energy should receive less bits (maybe + only shaped noise?) + + -DTX for near-zero energy? + + -Stationary fricative segments should have less bits + + -Temporal masking: when energy slope is decreasing, decrease the bit-rate + + -Decrease bit-rate for males (low pitch)? + + -(wideband only) less bits in the high-band when signal is very + non-stationary (harder to notice high-frequency noise)??? + +*/ + +float vbr_analysis(VBRState *vbr, spx_word16_t *sig, int len, int pitch, float pitch_coef) +{ + int i; + float ener=0, ener1=0, ener2=0; + float qual=7; + int va; + float log_energy; + float non_st=0; + float voicing; + float pow_ener; + + for (i=0;i>1;i++) + ener1 += ((float)sig[i])*sig[i]; + + for (i=len>>1;ilast_log_energy[i]); + non_st = non_st/(30*VBR_MEMORY_SIZE); + if (non_st>1) + non_st=1; + + voicing = 3*(pitch_coef-.4)*fabs(pitch_coef-.4); + vbr->average_energy = (1-vbr->energy_alpha)*vbr->average_energy + vbr->energy_alpha*ener; + vbr->noise_level=vbr->noise_accum/vbr->noise_accum_count; + pow_ener = pow(ener,NOISE_POW); + if (vbr->noise_accum_count<.06 && ener>MIN_ENERGY) + vbr->noise_accum = .05*pow_ener; + + if ((voicing<.3 && non_st < .2 && pow_ener < 1.2*vbr->noise_level) + || (voicing<.3 && non_st < .05 && pow_ener < 1.5*vbr->noise_level) + || (voicing<.4 && non_st < .05 && pow_ener < 1.2*vbr->noise_level) + || (voicing<0 && non_st < .05)) + { + float tmp; + va = 0; + vbr->consec_noise++; + if (pow_ener > 3*vbr->noise_level) + tmp = 3*vbr->noise_level; + else + tmp = pow_ener; + if (vbr->consec_noise>=4) + { + vbr->noise_accum = .95*vbr->noise_accum + .05*tmp; + vbr->noise_accum_count = .95*vbr->noise_accum_count + .05; + } + } else { + va = 1; + vbr->consec_noise=0; + } + + if (pow_ener < vbr->noise_level && ener>MIN_ENERGY) + { + vbr->noise_accum = .95*vbr->noise_accum + .05*pow_ener; + vbr->noise_accum_count = .95*vbr->noise_accum_count + .05; + } + + /* Checking for very low absolute energy */ + if (ener < 30000) + { + qual -= .7; + if (ener < 10000) + qual-=.7; + if (ener < 3000) + qual-=.7; + } else { + float short_diff, long_diff; + short_diff = log((ener+1)/(1+vbr->last_energy)); + long_diff = log((ener+1)/(1+vbr->average_energy)); + /*fprintf (stderr, "%f %f\n", short_diff, long_diff);*/ + + if (long_diff<-5) + long_diff=-5; + if (long_diff>2) + long_diff=2; + + if (long_diff>0) + qual += .6*long_diff; + if (long_diff<0) + qual += .5*long_diff; + if (short_diff>0) + { + if (short_diff>5) + short_diff=5; + qual += .5*short_diff; + } + /* Checking for energy increases */ + if (ener2 > 1.6*ener1) + qual += .5; + } + vbr->last_energy = ener; + vbr->soft_pitch = .6*vbr->soft_pitch + .4*pitch_coef; + qual += 2.2*((pitch_coef-.4) + (vbr->soft_pitch-.4)); + + if (qual < vbr->last_quality) + qual = .5*qual + .5*vbr->last_quality; + if (qual<4) + qual=4; + if (qual>10) + qual=10; + + /* + if (vbr->consec_noise>=2) + qual-=1.3; + if (vbr->consec_noise>=5) + qual-=1.3; + if (vbr->consec_noise>=12) + qual-=1.3; + */ + if (vbr->consec_noise>=3) + qual=4; + + if (vbr->consec_noise) + qual -= 1.0 * (log(3.0 + vbr->consec_noise)-log(3)); + if (qual<0) + qual=0; + + if (ener<60000) + { + if (vbr->consec_noise>2) + qual-=0.5*(log(3.0 + vbr->consec_noise)-log(3)); + if (ener<10000&&vbr->consec_noise>2) + qual-=0.5*(log(3.0 + vbr->consec_noise)-log(3)); + if (qual<0) + qual=0; + qual += .3*log(.0001+ener/60000.0); + } + if (qual<-1) + qual=-1; + + /*printf ("%f %f %f %f %d\n", qual, voicing, non_st, pow_ener/(.01+vbr->noise_level), va);*/ + + vbr->last_pitch_coef = pitch_coef; + vbr->last_quality = qual; + + for (i=VBR_MEMORY_SIZE-1;i>0;i--) + vbr->last_log_energy[i] = vbr->last_log_energy[i-1]; + vbr->last_log_energy[0] = log_energy; + + /*printf ("VBR: %f %f %f %d %f\n", (float)(log_energy-log(vbr->average_energy+MIN_ENERGY)), non_st, voicing, va, vbr->noise_level);*/ + + return qual; +} + +void vbr_destroy(VBRState *vbr) +{ +} + +#endif /* #ifndef DISABLE_VBR */ diff --git a/Libraries/speex/vbr.h b/Libraries/speex/vbr.h new file mode 100644 index 000000000..ff1e3e46f --- /dev/null +++ b/Libraries/speex/vbr.h @@ -0,0 +1,70 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file vbr.h + @brief Variable Bit-Rate (VBR) related routines +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef VBR_H +#define VBR_H + +#include "arch.h" + +#define VBR_MEMORY_SIZE 5 + +extern const float vbr_nb_thresh[9][11]; +extern const float vbr_hb_thresh[5][11]; +extern const float vbr_uhb_thresh[2][11]; + +/** VBR state. */ +typedef struct VBRState { + float energy_alpha; + float average_energy; + float last_energy; + float last_log_energy[VBR_MEMORY_SIZE]; + float accum_sum; + float last_pitch_coef; + float soft_pitch; + float last_quality; + float noise_level; + float noise_accum; + float noise_accum_count; + int consec_noise; +} VBRState; + +void vbr_init(VBRState *vbr); + +float vbr_analysis(VBRState *vbr, spx_word16_t *sig, int len, int pitch, float pitch_coef); + +void vbr_destroy(VBRState *vbr); + +#endif diff --git a/Libraries/speex/vorbis_psy.h b/Libraries/speex/vorbis_psy.h new file mode 100644 index 000000000..687105775 --- /dev/null +++ b/Libraries/speex/vorbis_psy.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2005 Jean-Marc Valin, CSIRO, Christopher Montgomery + File: vorbis_psy.h + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VORBIS_PSY_H +#define VORBIS_PSY_H + +#ifdef VORBIS_PSYCHO + +#include "smallft.h" +#define P_BANDS 17 /* 62Hz to 16kHz */ +#define NOISE_COMPAND_LEVELS 40 + + +#define todB(x) ((x)>1e-13?log((x)*(x))*4.34294480f:-30) +#define fromdB(x) (exp((x)*.11512925f)) + +/* The bark scale equations are approximations, since the original + table was somewhat hand rolled. The below are chosen to have the + best possible fit to the rolled tables, thus their somewhat odd + appearance (these are more accurate and over a longer range than + the oft-quoted bark equations found in the texts I have). The + approximations are valid from 0 - 30kHz (nyquist) or so. + + all f in Hz, z in Bark */ + +#define toBARK(n) (13.1f*atan(.00074f*(n))+2.24f*atan((n)*(n)*1.85e-8f)+1e-4f*(n)) +#define fromBARK(z) (102.f*(z)-2.f*pow(z,2.f)+.4f*pow(z,3.f)+pow(1.46f,z)-1.f) + +/* Frequency to octave. We arbitrarily declare 63.5 Hz to be octave + 0.0 */ + +#define toOC(n) (log(n)*1.442695f-5.965784f) +#define fromOC(o) (exp(((o)+5.965784f)*.693147f)) + + +typedef struct { + + float noisewindowlo; + float noisewindowhi; + int noisewindowlomin; + int noisewindowhimin; + int noisewindowfixed; + float noiseoff[P_BANDS]; + float noisecompand[NOISE_COMPAND_LEVELS]; + +} VorbisPsyInfo; + + + +typedef struct { + int n; + int rate; + struct drft_lookup lookup; + VorbisPsyInfo *vi; + + float *window; + float *noiseoffset; + long *bark; + +} VorbisPsy; + + +VorbisPsy *vorbis_psy_init(int rate, int size); +void vorbis_psy_destroy(VorbisPsy *psy); +void compute_curve(VorbisPsy *psy, float *audio, float *curve); +void curve_to_lpc(VorbisPsy *psy, float *curve, float *awk1, float *awk2, int ord); + +#endif +#endif diff --git a/Libraries/speex/vq.c b/Libraries/speex/vq.c new file mode 100644 index 000000000..609f124e7 --- /dev/null +++ b/Libraries/speex/vq.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: vq.c + Vector quantization + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "vq.h" +#include "stack_alloc.h" +#include "arch.h" + +#ifdef _USE_SSE +#include +#include "vq_sse.h" +#elif defined(SHORTCUTS) && (defined(ARM4_ASM) || defined(ARM5E_ASM)) +#include "vq_arm4.h" +#elif defined(BFIN_ASM) +#include "vq_bfin.h" +#endif + + +int scal_quant(spx_word16_t in, const spx_word16_t *boundary, int entries) +{ + int i=0; + while (iboundary[0]) + { + boundary++; + i++; + } + return i; +} + +int scal_quant32(spx_word32_t in, const spx_word32_t *boundary, int entries) +{ + int i=0; + while (iboundary[0]) + { + boundary++; + i++; + } + return i; +} + + +#ifndef OVERRIDE_VQ_NBEST +/*Finds the indices of the n-best entries in a codebook*/ +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + used = 0; + for (i=0;i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + } + } +} +#endif + + + + +#ifndef OVERRIDE_VQ_NBEST_SIGN +/*Finds the indices of the n-best entries in a codebook with sign*/ +void vq_nbest_sign(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k, sign, used; + used=0; + for (i=0;i0) + { + sign=0; + dist=-dist; + } else + { + sign=1; + } +#ifdef FIXED_POINT + dist = ADD32(dist,SHR32(E[i],1)); +#else + dist = ADD32(dist,.5f*E[i]); +#endif + if (i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + if (sign) + nbest[k]+=entries; + } + } +} +#endif diff --git a/Libraries/speex/vq.h b/Libraries/speex/vq.h new file mode 100644 index 000000000..5a4ced249 --- /dev/null +++ b/Libraries/speex/vq.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file vq.h + @brief Vector quantization +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VQ_H +#define VQ_H + +#include "arch.h" + +int scal_quant(spx_word16_t in, const spx_word16_t *boundary, int entries); +int scal_quant32(spx_word32_t in, const spx_word32_t *boundary, int entries); + +#ifdef _USE_SSE +#include +void vq_nbest(spx_word16_t *in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); + +void vq_nbest_sign(spx_word16_t *in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); +#else +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); + +void vq_nbest_sign(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); +#endif + +#endif diff --git a/Libraries/speex/vq_arm4.h b/Libraries/speex/vq_arm4.h new file mode 100644 index 000000000..585b8613c --- /dev/null +++ b/Libraries/speex/vq_arm4.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file vq_arm4.h + @brief ARM4-optimized vq routine +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_VQ_NBEST +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j; + for (i=0;i>= 1;\n\t" + "A0 = %0;\n\t" + "R0.L = W[%1++%7] || R1.L = W[I0++];\n\t" + "LOOP vq_loop%= LC1 = %5;\n\t" + "LOOP_BEGIN vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS) || R0.L = W[%1++%7] || R1.L = W[I0++];\n\t" + "LOOP_END vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS);\n\t" + "cc = %0 < %2;\n\t" + "if cc %2 = %0;\n\t" + "if cc %3 = R2;\n\t" + "R2 += 1;\n\t" + "LOOP_END entries_loop%=;\n\t" + : "=&D" (dist), "=&a" (codebook), "=&d" (best_dist[0]), "=&d" (nbest[0]), "=&a" (E) + : "a" (len-1), "a" (in), "a" (2), "d" (entries), "d" (len<<1), "1" (codebook), "4" (E), "2" (best_dist[0]), "3" (nbest[0]) + : "R0", "R1", "R2", "I0", "L0", "B0", "A0", "cc", "memory" + ); + } + } else { + int i,k,used; + used = 0; + for (i=0;i>= 1;\n\t" + "A0 = %0;\n\t" + "I0 = %3;\n\t" + "L0 = 0;\n\t" + "R0.L = W[%1++%4] || R1.L = W[I0++];\n\t" + "LOOP vq_loop%= LC0 = %2;\n\t" + "LOOP_BEGIN vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS) || R0.L = W[%1++%4] || R1.L = W[I0++];\n\t" + "LOOP_END vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS);\n\t" + : "=D" (dist), "=a" (codebook) + : "a" (len-1), "a" (in), "a" (2), "1" (codebook), "0" (E[i]) + : "R0", "R1", "I0", "L0", "A0" + ); + if (i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + } + } + } +} diff --git a/Libraries/speex/vq_sse.h b/Libraries/speex/vq_sse.h new file mode 100644 index 000000000..00a42ce35 --- /dev/null +++ b/Libraries/speex/vq_sse.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file vq_sse.h + @brief SSE-optimized vq routine +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_VQ_NBEST +void vq_nbest(spx_word16_t *_in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + VARDECL(float *dist); + VARDECL(__m128 *in); + __m128 half; + used = 0; + ALLOC(dist, entries, float); + half = _mm_set_ps1(.5f); + ALLOC(in, len, __m128); + for (i=0;i>2;i++) + { + __m128 d = _mm_mul_ps(E[i], half); + for (j=0;j= 1) && (k > used || dist[i] < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist[i]; + nbest[k]=i; + used++; + } + } +} + + + + +#define OVERRIDE_VQ_NBEST_SIGN +void vq_nbest_sign(spx_word16_t *_in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + VARDECL(float *dist); + VARDECL(__m128 *in); + __m128 half; + used = 0; + ALLOC(dist, entries, float); + half = _mm_set_ps1(.5f); + ALLOC(in, len, __m128); + for (i=0;i>2;i++) + { + __m128 d = _mm_setzero_ps(); + for (j=0;j0) + { + sign=0; + dist[i]=-dist[i]; + } else + { + sign=1; + } + dist[i] += .5f*((float*)E)[i]; + if (i= 1) && (k > used || dist[i] < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist[i]; + nbest[k]=i; + used++; + if (sign) + nbest[k]+=entries; + } + } +} diff --git a/Libraries/speex/window.c b/Libraries/speex/window.c new file mode 100644 index 000000000..ac042d45f --- /dev/null +++ b/Libraries/speex/window.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2006 Jean-Marc Valin + File: window.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "arch.h" + +#ifdef FIXED_POINT +const spx_word16_t lag_window[11] = { + 16384, 16337, 16199, 15970, 15656, 15260, 14790, 14254, 13659, 13015, 12330 +}; + +const spx_word16_t lpc_window[200] = { +1310, 1313, 1321, 1333, 1352, 1375, 1403, 1436, +1475, 1518, 1567, 1621, 1679, 1743, 1811, 1884, +1962, 2044, 2132, 2224, 2320, 2421, 2526, 2636, +2750, 2868, 2990, 3116, 3246, 3380, 3518, 3659, +3804, 3952, 4104, 4259, 4417, 4578, 4742, 4909, +5079, 5251, 5425, 5602, 5781, 5963, 6146, 6331, +6518, 6706, 6896, 7087, 7280, 7473, 7668, 7863, +8059, 8256, 8452, 8650, 8847, 9044, 9241, 9438, +9635, 9831, 10026, 10220, 10414, 10606, 10797, 10987, +11176, 11363, 11548, 11731, 11912, 12091, 12268, 12443, +12615, 12785, 12952, 13116, 13277, 13435, 13590, 13742, +13890, 14035, 14176, 14314, 14448, 14578, 14704, 14826, +14944, 15058, 15168, 15273, 15374, 15470, 15562, 15649, +15732, 15810, 15883, 15951, 16015, 16073, 16127, 16175, +16219, 16257, 16291, 16319, 16342, 16360, 16373, 16381, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16361, 16294, 16183, 16028, 15830, +15588, 15304, 14979, 14613, 14207, 13763, 13282, 12766, +12215, 11631, 11016, 10373, 9702, 9007, 8289, 7551, +6797, 6028, 5251, 4470, 3695, 2943, 2248, 1696 +}; +#else +const spx_word16_t lag_window[11] = { + 1.00000, 0.99716, 0.98869, 0.97474, 0.95554, 0.93140, 0.90273, 0.86998, 0.83367, 0.79434, 0.75258 +}; + +const spx_word16_t lpc_window[200] = { + 0.080000f, 0.080158f, 0.080630f, 0.081418f, 0.082520f, 0.083935f, 0.085663f, 0.087703f, + 0.090052f, 0.092710f, 0.095674f, 0.098943f, 0.102514f, 0.106385f, 0.110553f, 0.115015f, + 0.119769f, 0.124811f, 0.130137f, 0.135744f, 0.141628f, 0.147786f, 0.154212f, 0.160902f, + 0.167852f, 0.175057f, 0.182513f, 0.190213f, 0.198153f, 0.206328f, 0.214731f, 0.223357f, + 0.232200f, 0.241254f, 0.250513f, 0.259970f, 0.269619f, 0.279453f, 0.289466f, 0.299651f, + 0.310000f, 0.320507f, 0.331164f, 0.341965f, 0.352901f, 0.363966f, 0.375151f, 0.386449f, + 0.397852f, 0.409353f, 0.420943f, 0.432615f, 0.444361f, 0.456172f, 0.468040f, 0.479958f, + 0.491917f, 0.503909f, 0.515925f, 0.527959f, 0.540000f, 0.552041f, 0.564075f, 0.576091f, + 0.588083f, 0.600042f, 0.611960f, 0.623828f, 0.635639f, 0.647385f, 0.659057f, 0.670647f, + 0.682148f, 0.693551f, 0.704849f, 0.716034f, 0.727099f, 0.738035f, 0.748836f, 0.759493f, + 0.770000f, 0.780349f, 0.790534f, 0.800547f, 0.810381f, 0.820030f, 0.829487f, 0.838746f, + 0.847800f, 0.856643f, 0.865269f, 0.873672f, 0.881847f, 0.889787f, 0.897487f, 0.904943f, + 0.912148f, 0.919098f, 0.925788f, 0.932214f, 0.938372f, 0.944256f, 0.949863f, 0.955189f, + 0.960231f, 0.964985f, 0.969447f, 0.973615f, 0.977486f, 0.981057f, 0.984326f, 0.987290f, + 0.989948f, 0.992297f, 0.994337f, 0.996065f, 0.997480f, 0.998582f, 0.999370f, 0.999842f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 0.998640f, 0.994566f, 0.987787f, 0.978324f, 0.966203f, + 0.951458f, 0.934131f, 0.914270f, 0.891931f, 0.867179f, 0.840084f, 0.810723f, 0.779182f, + 0.745551f, 0.709930f, 0.672424f, 0.633148f, 0.592223f, 0.549781f, 0.505964f, 0.460932f, + 0.414863f, 0.367968f, 0.320511f, 0.272858f, 0.225569f, 0.179655f, 0.137254f, 0.103524f +}; +#endif diff --git a/Podfile b/Podfile new file mode 100644 index 000000000..b2729a691 --- /dev/null +++ b/Podfile @@ -0,0 +1,7 @@ +platform :ios, '7.0' + +link_with ["Signal", "SignalTests"] + +pod 'OpenSSL', '~> 1.0.1' +pod 'MMDrawerController', '~> 0.5.0' +pod 'libPhoneNumber-iOS', '~> 0.7' diff --git a/README.md b/README.md new file mode 100644 index 000000000..a18e5fad0 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Signal for iOS + +Signal allows you to make private phone calls and we are working on bringing secure messaging to it soon. + +## Building + +1) Clone the repo to a working directory + +2) [CocoaPods](http://cocoapods.org) is used to manage dependencies. Pods are setup easily and are distributed via a ruby gem. Follow the simple instructions on the website to setup. After setup, run the following command from the toplevel directory of TextSecureiOS to download the dependencies for TextSecure iOS: + +``` +pod install +``` +If you are having build issues, first make sure your pods are up to date +``` +pod update +pod install +``` +occasionally, CocoaPods itself will need to be updated. Do this with +``` +sudo gem update +``` + +3) Open the `Signal.xcworkspace` in Xcode. Build and Run and you are ready to go! + +## Interoperability + +Signal works with [RedPhone on Android](https://github.com/WhisperSystems/Redphone). + +## Cryptography Notice + +This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. +BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. +See for more information. + +The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. +The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. + +## License + +Copyright 2014 Whisper Systems + +Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html diff --git a/SettingsBundle/Settings.bundle/Acknowledgements.plist b/SettingsBundle/Settings.bundle/Acknowledgements.plist new file mode 100644 index 000000000..1187e2cae --- /dev/null +++ b/SettingsBundle/Settings.bundle/Acknowledgements.plist @@ -0,0 +1,929 @@ + + + + + StringsTable + Acknowledgements + PreferenceSpecifiers + + + Type + PSGroupSpecifier + FooterText + MMDrawerController + + + Type + PSGroupSpecifier + FooterText + MMDrawerController2 + + + Type + PSGroupSpecifier + FooterText + MMDrawerController3 + + + Type + PSGroupSpecifier + FooterText + MMDrawerController4 + + + Type + PSGroupSpecifier + FooterText + MMDrawerController5 + + + Type + PSGroupSpecifier + FooterText + OpenSSL + + + Type + PSGroupSpecifier + FooterText + OpenSSL2 + + + Type + PSGroupSpecifier + FooterText + OpenSSL3 + + + Type + PSGroupSpecifier + FooterText + OpenSSL4 + + + Type + PSGroupSpecifier + FooterText + OpenSSL5 + + + Type + PSGroupSpecifier + FooterText + OpenSSL6 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers2 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers3 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers4 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers5 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers6 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers7 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers8 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers9 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers10 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers11 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers12 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers13 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers14 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers15 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers16 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers17 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers18 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers19 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers20 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers21 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers22 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers23 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers24 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers25 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers26 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers27 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers28 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers29 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers30 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers31 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers32 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers33 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers34 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers35 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers36 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers37 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers38 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers39 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers40 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers41 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers42 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers43 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers44 + + + Type + PSGroupSpecifier + FooterText + ProtoBuffers45 + + + Type + PSGroupSpecifier + FooterText + Spandsp + + + Type + PSGroupSpecifier + FooterText + Spandsp2 + + + Type + PSGroupSpecifier + FooterText + Spandsp3 + + + Type + PSGroupSpecifier + FooterText + Spandsp4 + + + Type + PSGroupSpecifier + FooterText + Spandsp5 + + + Type + PSGroupSpecifier + FooterText + Spandsp6 + + + Type + PSGroupSpecifier + FooterText + Spandsp7 + + + Type + PSGroupSpecifier + FooterText + Spandsp8 + + + Type + PSGroupSpecifier + FooterText + Spandsp9 + + + Type + PSGroupSpecifier + FooterText + Spandsp10 + + + Type + PSGroupSpecifier + FooterText + Spandsp11 + + + Type + PSGroupSpecifier + FooterText + Spandsp12 + + + Type + PSGroupSpecifier + FooterText + Spandsp13 + + + Type + PSGroupSpecifier + FooterText + Spandsp14 + + + Type + PSGroupSpecifier + FooterText + Spandsp15 + + + Type + PSGroupSpecifier + FooterText + Spandsp16 + + + Type + PSGroupSpecifier + FooterText + Spandsp17 + + + Type + PSGroupSpecifier + FooterText + Spandsp18 + + + Type + PSGroupSpecifier + FooterText + Spandsp19 + + + Type + PSGroupSpecifier + FooterText + Spandsp20 + + + Type + PSGroupSpecifier + FooterText + Spandsp21 + + + Type + PSGroupSpecifier + FooterText + Spandsp22 + + + Type + PSGroupSpecifier + FooterText + Spandsp23 + + + Type + PSGroupSpecifier + FooterText + Spandsp24 + + + Type + PSGroupSpecifier + FooterText + Spandsp25 + + + Type + PSGroupSpecifier + FooterText + Spandsp26 + + + Type + PSGroupSpecifier + FooterText + Spandsp27 + + + Type + PSGroupSpecifier + FooterText + Spandsp28 + + + Type + PSGroupSpecifier + FooterText + Spandsp29 + + + Type + PSGroupSpecifier + FooterText + Spandsp30 + + + Type + PSGroupSpecifier + FooterText + Spandsp31 + + + Type + PSGroupSpecifier + FooterText + Spandsp32 + + + Type + PSGroupSpecifier + FooterText + Spandsp33 + + + Type + PSGroupSpecifier + FooterText + Spandsp34 + + + Type + PSGroupSpecifier + FooterText + Spandsp35 + + + Type + PSGroupSpecifier + FooterText + Spandsp36 + + + Type + PSGroupSpecifier + FooterText + Spandsp37 + + + Type + PSGroupSpecifier + FooterText + Spandsp38 + + + Type + PSGroupSpecifier + FooterText + Spandsp39 + + + Type + PSGroupSpecifier + FooterText + Spandsp40 + + + Type + PSGroupSpecifier + FooterText + Spandsp41 + + + Type + PSGroupSpecifier + FooterText + Spandsp42 + + + Type + PSGroupSpecifier + FooterText + Spandsp43 + + + Type + PSGroupSpecifier + FooterText + Spandsp44 + + + Type + PSGroupSpecifier + FooterText + Spandsp45 + + + Type + PSGroupSpecifier + FooterText + Spandsp46 + + + Type + PSGroupSpecifier + FooterText + Spandsp47 + + + Type + PSGroupSpecifier + FooterText + Spandsp48 + + + Type + PSGroupSpecifier + FooterText + Spandsp49 + + + Type + PSGroupSpecifier + FooterText + Spandsp50 + + + Type + PSGroupSpecifier + FooterText + Spandsp51 + + + Type + PSGroupSpecifier + FooterText + Spandsp52 + + + Type + PSGroupSpecifier + FooterText + Spandsp53 + + + Type + PSGroupSpecifier + FooterText + Spandsp54 + + + Type + PSGroupSpecifier + FooterText + Spandsp55 + + + Type + PSGroupSpecifier + FooterText + Spandsp56 + + + Type + PSGroupSpecifier + FooterText + Spandsp57 + + + Type + PSGroupSpecifier + FooterText + Spandsp58 + + + Type + PSGroupSpecifier + FooterText + Spandsp59 + + + Type + PSGroupSpecifier + FooterText + Spandsp60 + + + Type + PSGroupSpecifier + FooterText + Spandsp61 + + + Type + PSGroupSpecifier + FooterText + Spandsp62 + + + Type + PSGroupSpecifier + FooterText + Spandsp63 + + + Type + PSGroupSpecifier + FooterText + Spandsp64 + + + Type + PSGroupSpecifier + FooterText + Spandsp65 + + + Type + PSGroupSpecifier + FooterText + Spandsp66 + + + Type + PSGroupSpecifier + FooterText + Speex + + + Type + PSGroupSpecifier + FooterText + Speex2 + + + Type + PSGroupSpecifier + FooterText + Speex3 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS2 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS3 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS4 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS5 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS6 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS7 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS8 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS9 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS10 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS11 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS12 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS13 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS14 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS15 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS16 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS17 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS18 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS19 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS20 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS21 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS22 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS23 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS24 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS25 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS26 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS27 + + + Type + PSGroupSpecifier + FooterText + libPhoneNumber-iOS28 + + + + diff --git a/SettingsBundle/Settings.bundle/Root.plist b/SettingsBundle/Settings.bundle/Root.plist new file mode 100644 index 000000000..3f5c697d9 --- /dev/null +++ b/SettingsBundle/Settings.bundle/Root.plist @@ -0,0 +1,19 @@ + + + + + PreferenceSpecifiers + + + File + Acknowledgements + Title + Acknowledgements + Type + PSChildPaneSpecifier + + + StringsTable + Root + + diff --git a/SettingsBundle/Settings.bundle/en.lproj/Acknowledgements.strings b/SettingsBundle/Settings.bundle/en.lproj/Acknowledgements.strings new file mode 100644 index 000000000..b9aedc9f2 --- /dev/null +++ b/SettingsBundle/Settings.bundle/en.lproj/Acknowledgements.strings @@ -0,0 +1,153 @@ +"MMDrawerController" = "Mutual Mobile Drawer Controller https://github.com/mutualmobile/MMDrawerController"; +"MMDrawerController2" = "Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)"; +"MMDrawerController3" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; +"MMDrawerController4" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; +"MMDrawerController5" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; +"OpenSSL" = "OpenSSL"; +"OpenSSL2" = "The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts. Actually both licenses are BSD-style Open Source licenses. In case of any license issues related to OpenSSL please contact openssl-core@openssl.org."; +"OpenSSL3" = " OpenSSL License ---------------"; +"OpenSSL4" = "/* ==================================================================== * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * 'This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit. (http://www.openssl.org/)' * * 4. The names 'OpenSSL Toolkit' and 'OpenSSL Project' must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * openssl-core@openssl.org. * * 5. Products derived from this software may not be called 'OpenSSL' * nor may 'OpenSSL' appear in their names without prior written * permission of the OpenSSL Project. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * 'This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit (http://www.openssl.org/)' * * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * */"; +"OpenSSL5" = " Original SSLeay License -----------------------"; +"OpenSSL6" = "/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * 'This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)' * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * 'This product includes software written by Tim Hudson (tjh@cryptsoft.com)' * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ "; +"ProtoBuffers" = "ProtocolBuffers by Cyrus Najmabadi Objective-C Protocol Buffers for OSX and the iPhone"; +"ProtoBuffers2" = " GNU GENERAL PUBLIC LICENSE Version 2, June 1991"; +"ProtoBuffers3" = " Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed."; +"ProtoBuffers4" = " Preamble"; +"ProtoBuffers5" = " The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too."; +"ProtoBuffers6" = " When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things."; +"ProtoBuffers7" = " To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it."; +"ProtoBuffers8" = " For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights."; +"ProtoBuffers9" = " We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software."; +"ProtoBuffers10" = " Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations."; +"ProtoBuffers11" = " Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all."; +"ProtoBuffers12" = " The precise terms and conditions for copying, distribution and modification follow."; +"ProtoBuffers13" = " GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION"; +"ProtoBuffers14" = " 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The 'Program', below, refers to any such program or work, and a 'work based on the Program' means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term 'modification'.) Each licensee is addressed as 'you'."; +"ProtoBuffers15" = "Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does."; +"ProtoBuffers16" = " 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program."; +"ProtoBuffers17" = "You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee."; +"ProtoBuffers18" = " 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:"; +"ProtoBuffers19" = " a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change."; +"ProtoBuffers20" = " b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License."; +"ProtoBuffers21" = " c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)"; +"ProtoBuffers22" = "These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it."; +"ProtoBuffers23" = "Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program."; +"ProtoBuffers24" = "In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License."; +"ProtoBuffers25" = " 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:"; +"ProtoBuffers26" = " a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; +"ProtoBuffers27" = " b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; +"ProtoBuffers28" = " c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)"; +"ProtoBuffers29" = "The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable."; +"ProtoBuffers30" = "If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code."; +"ProtoBuffers31" = " 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance."; +"ProtoBuffers32" = " 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it."; +"ProtoBuffers33" = " 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License."; +"ProtoBuffers34" = " 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program."; +"ProtoBuffers35" = "If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances."; +"ProtoBuffers36" = "It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice."; +"ProtoBuffers37" = "This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License."; +"ProtoBuffers38" = " 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License."; +"ProtoBuffers39" = " 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns."; +"ProtoBuffers40" = "Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and 'any later version', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation."; +"ProtoBuffers41" = " 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally."; +"ProtoBuffers42" = " NO WARRANTY"; +"ProtoBuffers43" = " 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION."; +"ProtoBuffers44" = " 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES."; +"ProtoBuffers45" = " END OF TERMS AND CONDITIONS "; +"Spandsp" = "SpandSP"; +"Spandsp2" = " GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999"; +"Spandsp3" = " Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed."; +"Spandsp4" = "[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]"; +"Spandsp5" = " Preamble"; +"Spandsp6" = " The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users."; +"Spandsp7" = " This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below."; +"Spandsp8" = " When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things."; +"Spandsp9" = " To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it."; +"Spandsp10" = " For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights."; +"Spandsp11" = " We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library."; +"Spandsp12" = " To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license."; +"Spandsp13" = " Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs."; +"Spandsp14" = " When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library."; +"Spandsp15" = " We call this license the 'Lesser' General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances."; +"Spandsp16" = " For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License."; +"Spandsp17" = " In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system."; +"Spandsp18" = " Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library."; +"Spandsp19" = " The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a 'work based on the library' and a 'work that uses the library'. The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION"; +"Spandsp20" = " 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called 'this License'). Each licensee is addressed as 'you'."; +"Spandsp21" = " A 'library' means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables."; +"Spandsp22" = " The 'Library', below, refers to any such software library or work which has been distributed under these terms. A 'work based on the Library' means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term 'modification'.)"; +"Spandsp23" = " 'Source code' for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library."; +"Spandsp24" = " Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does."; +"Spandsp25" = " 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library."; +"Spandsp26" = " You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:"; +"Spandsp27" = " a) The modified work must itself be a software library."; +"Spandsp28" = " b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change."; +"Spandsp29" = " c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License."; +"Spandsp30" = " d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful."; +"Spandsp31" = " (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)"; +"Spandsp32" = "These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it."; +"Spandsp33" = "Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library."; +"Spandsp34" = "In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License."; +"Spandsp35" = " 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy."; +"Spandsp36" = " This option is useful when you wish to copy part of the code of the Library into a program that is not a library."; +"Spandsp37" = " 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange."; +"Spandsp38" = " If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code."; +"Spandsp39" = " 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a 'work that uses the Library'. Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License."; +"Spandsp40" = " However, linking a 'work that uses the Library' with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a 'work that uses the library'. The executable is therefore covered by this License. Section 6 states terms for distribution of such executables."; +"Spandsp41" = " When a 'work that uses the Library' uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law."; +"Spandsp42" = " If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)"; +"Spandsp43" = " Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a 'work that uses the Library' with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications."; +"Spandsp44" = " You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:"; +"Spandsp45" = " a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable 'work that uses the Library', as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)"; +"Spandsp46" = " b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with."; +"Spandsp47" = " c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution."; +"Spandsp48" = " d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place."; +"Spandsp49" = " e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy."; +"Spandsp50" = " For an executable, the required form of the 'work that uses the Library' must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable."; +"Spandsp51" = " It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:"; +"Spandsp52" = " a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above."; +"Spandsp53" = " b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work."; +"Spandsp54" = " 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance."; +"Spandsp55" = " 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it."; +"Spandsp56" = " 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library."; +"Spandsp57" = "If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances."; +"Spandsp58" = "It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice."; +"Spandsp59" = "This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License."; +"Spandsp60" = " 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License."; +"Spandsp61" = " 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns."; +"Spandsp62" = "Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and 'any later version', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally."; +"Spandsp63" = " NO WARRANTY"; +"Spandsp64" = " 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION."; +"Spandsp65" = " 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES."; +"Spandsp66" = " END OF TERMS AND CONDITIONS "; +"Speex" = "Speex: A Free Codec For Free Speech http://www.speex.org/"; +"Speex2" = "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:"; +"Speex3" = "Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. "; +"libPhoneNumber-iOS" = " libPhoneNumber-iOS by me2day https://github.com/me2day/libPhoneNumber-iOS"; +"libPhoneNumber-iOS2" = " Apache License Version 2.0, January 2004 http://www.apache.org/licenses/"; +"libPhoneNumber-iOS3" = " TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION"; +"libPhoneNumber-iOS4" = " 1. Definitions."; +"libPhoneNumber-iOS5" = " 'License' shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document."; +"libPhoneNumber-iOS6" = " 'Licensor' shall mean the copyright owner or entity authorized by the copyright owner that is granting the License."; +"libPhoneNumber-iOS7" = " 'Legal Entity' shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, 'control' means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity."; +"libPhoneNumber-iOS8" = " 'You' (or 'Your') shall mean an individual or Legal Entity exercising permissions granted by this License."; +"libPhoneNumber-iOS9" = " 'Source' form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files."; +"libPhoneNumber-iOS10" = " 'Object' form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types."; +"libPhoneNumber-iOS11" = " 'Work' shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below)."; +"libPhoneNumber-iOS12" = " 'Derivative Works' shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof."; +"libPhoneNumber-iOS13" = " 'Contribution' shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, 'submitted' means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as 'Not a Contribution.'"; +"libPhoneNumber-iOS14" = " 'Contributor' shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work."; +"libPhoneNumber-iOS15" = " 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form."; +"libPhoneNumber-iOS16" = " 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed."; +"libPhoneNumber-iOS17" = " 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:"; +"libPhoneNumber-iOS18" = " (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and"; +"libPhoneNumber-iOS19" = " (b) You must cause any modified files to carry prominent notices stating that You changed the files; and"; +"libPhoneNumber-iOS20" = " (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and"; +"libPhoneNumber-iOS21" = " (d) If the Work includes a 'NOTICE' text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License."; +"libPhoneNumber-iOS22" = " You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License."; +"libPhoneNumber-iOS23" = " 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions."; +"libPhoneNumber-iOS24" = " 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file."; +"libPhoneNumber-iOS25" = " 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License."; +"libPhoneNumber-iOS26" = " 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages."; +"libPhoneNumber-iOS27" = " 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability."; +"libPhoneNumber-iOS28" = " END OF TERMS AND CONDITIONS "; diff --git a/SettingsBundle/Settings.bundle/en.lproj/Root.strings b/SettingsBundle/Settings.bundle/en.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..8cd87b9d6b20c1fbf87bd4db3db267fca5ad4df9 GIT binary patch literal 546 zcmaixOHRW;5JYRuDMndFh#Ua1V1d}N;sVAV2TO?uC3a9aJn*VxFrY}tnon0(S66#J z-d9>G>6W!ur(SDqlp`9nn~*(m%iWnv?yq`Qfp6XbK1?+om~~#r)ZnhkYQU_VbfjuT zHNn`CX<0sd*m1A}>&5sU$akD=GTXJ1e literal 0 HcmV?d00001 diff --git a/SettingsBundle/licenses/MMDrawerController.license b/SettingsBundle/licenses/MMDrawerController.license new file mode 100644 index 000000000..7e8f84119 --- /dev/null +++ b/SettingsBundle/licenses/MMDrawerController.license @@ -0,0 +1,22 @@ +Mutual Mobile Drawer Controller +https://github.com/mutualmobile/MMDrawerController + +Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/SettingsBundle/licenses/OpenSSL.license b/SettingsBundle/licenses/OpenSSL.license new file mode 100644 index 000000000..6a08574c3 --- /dev/null +++ b/SettingsBundle/licenses/OpenSSL.license @@ -0,0 +1,124 @@ +OpenSSL + +The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ diff --git a/SettingsBundle/licenses/ProtoBuffers.license b/SettingsBundle/licenses/ProtoBuffers.license new file mode 100644 index 000000000..9ef2e4bbd --- /dev/null +++ b/SettingsBundle/licenses/ProtoBuffers.license @@ -0,0 +1,283 @@ +ProtocolBuffers by Cyrus Najmabadi +Objective-C Protocol Buffers for OSX and the iPhone + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/SettingsBundle/licenses/Spandsp.license b/SettingsBundle/licenses/Spandsp.license new file mode 100644 index 000000000..3627e2529 --- /dev/null +++ b/SettingsBundle/licenses/Spandsp.license @@ -0,0 +1,461 @@ +SpandSP + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + diff --git a/SettingsBundle/licenses/Speex.license b/SettingsBundle/licenses/Speex.license new file mode 100644 index 000000000..e3e4b8086 --- /dev/null +++ b/SettingsBundle/licenses/Speex.license @@ -0,0 +1,9 @@ +Speex: A Free Codec For Free Speech +http://www.speex.org/ + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/SettingsBundle/licenses/compileLicences.pl b/SettingsBundle/licenses/compileLicences.pl new file mode 100755 index 000000000..fddb7469e --- /dev/null +++ b/SettingsBundle/licenses/compileLicences.pl @@ -0,0 +1,57 @@ +#!/usr/bin/perl -w + +use strict; + +my $out = "../Settings.bundle/en.lproj/Acknowledgements.strings"; +my $plistout = "../Settings.bundle/Acknowledgements.plist"; + +unlink $out; + +open(my $outfh, '>', $out) or die $!; +open(my $plistfh, '>', $plistout) or die $!; + +print $plistfh <<'EOD'; + + + + + StringsTable + Acknowledgements + PreferenceSpecifiers + +EOD +for my $i (sort glob("*.license")) +{ + my $value=`cat $i`; + $value =~ s/\r//g; + $value =~ s/\n/\r/g; + $value =~ s/[ \t]+\r/\r/g; + $value =~ s/\"/\'/g; + my $key=$i; + $key =~ s/\.license$//; + + my $cnt = 1; + my $keynum = $key; + for my $str (split /\r\r/, $value) + { + print $plistfh <<"EOD"; + + Type + PSGroupSpecifier + FooterText + $keynum + +EOD + + print $outfh "\"$keynum\" = \"$str\";\n"; + $keynum = $key.(++$cnt); + } +} + +print $plistfh <<'EOD'; + + + +EOD +close($outfh); +close($plistfh); diff --git a/SettingsBundle/licenses/libPhoneNumber-iOS.license b/SettingsBundle/licenses/libPhoneNumber-iOS.license new file mode 100644 index 000000000..0ac991f7c --- /dev/null +++ b/SettingsBundle/licenses/libPhoneNumber-iOS.license @@ -0,0 +1,180 @@ + +libPhoneNumber-iOS by me2day +https://github.com/me2day/libPhoneNumber-iOS + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj new file mode 100644 index 000000000..1e471e0ef --- /dev/null +++ b/Signal.xcodeproj/project.pbxproj @@ -0,0 +1,3810 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 701231B518ECAA4500D456C4 /* EvpMessageDigest.m in Sources */ = {isa = PBXBuildFile; fileRef = 701231B418ECAA4500D456C4 /* EvpMessageDigest.m */; }; + 70377AA91916BA0500CAF501 /* InteractiveLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 70377AA81916BA0500CAF501 /* InteractiveLabel.m */; }; + 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; + 7038632718F70C0700D4A43F /* CryptoTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 7038632418F70C0700D4A43F /* CryptoTools.m */; }; + 7038632818F70C0700D4A43F /* EvpSymetricUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 7038632618F70C0700D4A43F /* EvpSymetricUtil.m */; }; + 707E548C18FF0B8A00C8649D /* InviteContactModal.m in Sources */ = {isa = PBXBuildFile; fileRef = 707E548B18FF0B8A00C8649D /* InviteContactModal.m */; }; + 707E549218FF26E800C8649D /* SmsInvite.m in Sources */ = {isa = PBXBuildFile; fileRef = 707E549118FF26E800C8649D /* SmsInvite.m */; }; + 7095B7B018F46D35002C66E2 /* PhoneNumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 7095B7AF18F46D35002C66E2 /* PhoneNumberUtil.m */; }; + 70B800A6190C53180042E3F0 /* libspandsp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 70B800A3190C529C0042E3F0 /* libspandsp.a */; }; + 70B800AF190C548D0042E3F0 /* libspeex.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 70B800AC190C54790042E3F0 /* libspeex.a */; }; + 70B8010C190C55660042E3F0 /* AbstractMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800E3190C55660042E3F0 /* AbstractMessage.m */; }; + 70B8010D190C55660042E3F0 /* AbstractMessage_Builder.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800E5190C55660042E3F0 /* AbstractMessage_Builder.m */; }; + 70B8010E190C55660042E3F0 /* CodedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800E8190C55660042E3F0 /* CodedInputStream.m */; }; + 70B8010F190C55660042E3F0 /* CodedOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800EA190C55660042E3F0 /* CodedOutputStream.m */; }; + 70B80110190C55660042E3F0 /* ConcreteExtensionField.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800EC190C55660042E3F0 /* ConcreteExtensionField.m */; }; + 70B80111190C55660042E3F0 /* ExtendableMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800EE190C55660042E3F0 /* ExtendableMessage.m */; }; + 70B80112190C55660042E3F0 /* ExtendableMessage_Builder.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800F0190C55660042E3F0 /* ExtendableMessage_Builder.m */; }; + 70B80113190C55660042E3F0 /* ExtensionRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800F3190C55660042E3F0 /* ExtensionRegistry.m */; }; + 70B80114190C55660042E3F0 /* Field.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800F5190C55660042E3F0 /* Field.m */; }; + 70B80115190C55660042E3F0 /* GeneratedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800F8190C55660042E3F0 /* GeneratedMessage.m */; }; + 70B80116190C55660042E3F0 /* GeneratedMessage_Builder.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800FA190C55660042E3F0 /* GeneratedMessage_Builder.m */; }; + 70B80117190C55660042E3F0 /* MutableExtensionRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B800FE190C55660042E3F0 /* MutableExtensionRegistry.m */; }; + 70B80118190C55660042E3F0 /* MutableField.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B80100190C55660042E3F0 /* MutableField.m */; }; + 70B80119190C55660042E3F0 /* TextFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B80103190C55660042E3F0 /* TextFormat.m */; }; + 70B8011A190C55660042E3F0 /* UnknownFieldSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B80105190C55660042E3F0 /* UnknownFieldSet.m */; }; + 70B8011B190C55660042E3F0 /* UnknownFieldSet_Builder.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B80107190C55660042E3F0 /* UnknownFieldSet_Builder.m */; }; + 70B8011C190C55660042E3F0 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B80109190C55660042E3F0 /* Utilities.m */; }; + 70B8011D190C55660042E3F0 /* WireFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B8010B190C55660042E3F0 /* WireFormat.m */; }; + 70B8FEE21909FE360042E3F0 /* 171756__nenadsimic__picked-coin-echo-2.wav in Resources */ = {isa = PBXBuildFile; fileRef = 70B8FEE11909FE360042E3F0 /* 171756__nenadsimic__picked-coin-echo-2.wav */; }; + 70BAFD5D190584BE00FA5E0B /* NotificationTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 70BAFD5C190584BE00FA5E0B /* NotificationTracker.m */; }; + 762D9DCF18281C7400A5E418 /* SettingsTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 762D9DCE18281C7400A5E418 /* SettingsTableHeaderView.m */; }; + 762D9DD018281C7400A5E418 /* SettingsTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 762D9DCE18281C7400A5E418 /* SettingsTableHeaderView.m */; }; + 765052A1182945EF008313E1 /* LocalizableCustomFontLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 765052A0182945EF008313E1 /* LocalizableCustomFontLabel.m */; }; + 765052A2182945EF008313E1 /* LocalizableCustomFontLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 765052A0182945EF008313E1 /* LocalizableCustomFontLabel.m */; }; + 765052AA18294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052A518294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf */; }; + 765052AC18294C9F008313E1 /* HelveticaNeueLTStd-Md.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052A618294C9F008313E1 /* HelveticaNeueLTStd-Md.otf */; }; + 765052AF182AC9B5008313E1 /* DialerButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 765052AE182AC9B5008313E1 /* DialerButtonView.m */; }; + 765052B0182AC9B5008313E1 /* DialerButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 765052AE182AC9B5008313E1 /* DialerButtonView.m */; }; + 765052B3182BF839008313E1 /* HelveticaNeueLTStd-Th.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052B1182BF839008313E1 /* HelveticaNeueLTStd-Th.otf */; }; + 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; + 76919BF71805D190008C664A /* ContactManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 76919BF61805D190008C664A /* ContactManagerTest.m */; }; + 76B8189E182C39460088060E /* PreferenceListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76B8189C182C39460088060E /* PreferenceListViewController.m */; }; + 76B8189F182C39460088060E /* PreferenceListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76B8189C182C39460088060E /* PreferenceListViewController.m */; }; + 76B818A1182C39460088060E /* PreferenceListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76B8189D182C39460088060E /* PreferenceListViewController.xib */; }; + 76C87F13181EE11C00C4ACAB /* InboxFeedFooterCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76C87F12181EE11C00C4ACAB /* InboxFeedFooterCell.m */; }; + 76C87F14181EE11C00C4ACAB /* InboxFeedFooterCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76C87F12181EE11C00C4ACAB /* InboxFeedFooterCell.m */; }; + 76C87F17181EE2EB00C4ACAB /* InboxFeedFooterCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76C87F15181EE2EB00C4ACAB /* InboxFeedFooterCell.xib */; }; + 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; + 76D713E7182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D713E6182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m */; }; + 76D713E8182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D713E6182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m */; }; + 76D713EB182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76D713E9182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib */; }; + 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; + 76EB054118170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; + 76EB054218170B33006006FC /* AsyncUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C618170B33006006FC /* AsyncUtil.m */; }; + 76EB054318170B33006006FC /* AsyncUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C618170B33006006FC /* AsyncUtil.m */; }; + 76EB054418170B33006006FC /* AsyncUtilHelperRacingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C818170B33006006FC /* AsyncUtilHelperRacingOperation.m */; }; + 76EB054518170B33006006FC /* AsyncUtilHelperRacingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C818170B33006006FC /* AsyncUtilHelperRacingOperation.m */; }; + 76EB054618170B33006006FC /* CancelledToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CA18170B33006006FC /* CancelledToken.m */; }; + 76EB054718170B33006006FC /* CancelledToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CA18170B33006006FC /* CancelledToken.m */; }; + 76EB054818170B33006006FC /* CancelTokenSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CC18170B33006006FC /* CancelTokenSource.m */; }; + 76EB054918170B33006006FC /* CancelTokenSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CC18170B33006006FC /* CancelTokenSource.m */; }; + 76EB054A18170B33006006FC /* Future.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CE18170B33006006FC /* Future.m */; }; + 76EB054B18170B33006006FC /* Future.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03CE18170B33006006FC /* Future.m */; }; + 76EB054C18170B33006006FC /* FutureSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D018170B33006006FC /* FutureSource.m */; }; + 76EB054D18170B33006006FC /* FutureSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D018170B33006006FC /* FutureSource.m */; }; + 76EB054E18170B33006006FC /* FutureUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D218170B33006006FC /* FutureUtil.m */; }; + 76EB054F18170B33006006FC /* FutureUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D218170B33006006FC /* FutureUtil.m */; }; + 76EB055018170B33006006FC /* ObservableValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D418170B33006006FC /* ObservableValue.m */; }; + 76EB055118170B33006006FC /* ObservableValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D418170B33006006FC /* ObservableValue.m */; }; + 76EB055218170B33006006FC /* TimeoutFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D818170B33006006FC /* TimeoutFailure.m */; }; + 76EB055318170B33006006FC /* TimeoutFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03D818170B33006006FC /* TimeoutFailure.m */; }; + 76EB057218170B33006006FC /* RecentCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040018170B33006006FC /* RecentCall.m */; }; + 76EB057318170B33006006FC /* RecentCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040018170B33006006FC /* RecentCall.m */; }; + 76EB057418170B33006006FC /* RecentCallManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040218170B33006006FC /* RecentCallManager.m */; }; + 76EB057518170B33006006FC /* RecentCallManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040218170B33006006FC /* RecentCallManager.m */; }; + 76EB057618170B33006006FC /* Contact.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040518170B33006006FC /* Contact.m */; }; + 76EB057718170B33006006FC /* Contact.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040518170B33006006FC /* Contact.m */; }; + 76EB057A18170B33006006FC /* ContactsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040918170B33006006FC /* ContactsManager.m */; }; + 76EB057B18170B33006006FC /* ContactsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB040918170B33006006FC /* ContactsManager.m */; }; + 76EB058218170B33006006FC /* Environment.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041318170B33006006FC /* Environment.m */; }; + 76EB058318170B33006006FC /* Environment.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041318170B33006006FC /* Environment.m */; }; + 76EB058418170B33006006FC /* LocalizableText.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041518170B33006006FC /* LocalizableText.m */; }; + 76EB058518170B33006006FC /* LocalizableText.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041518170B33006006FC /* LocalizableText.m */; }; + 76EB058618170B33006006FC /* PreferencesUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041718170B33006006FC /* PreferencesUtil.m */; }; + 76EB058718170B33006006FC /* PreferencesUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041718170B33006006FC /* PreferencesUtil.m */; }; + 76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041918170B33006006FC /* PropertyListPreferences.m */; }; + 76EB058918170B33006006FC /* PropertyListPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041918170B33006006FC /* PropertyListPreferences.m */; }; + 76EB058A18170B33006006FC /* Release.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041B18170B33006006FC /* Release.m */; }; + 76EB058B18170B33006006FC /* Release.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB041B18170B33006006FC /* Release.m */; }; + 76EB058C18170B33006006FC /* DnsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042018170B33006006FC /* DnsManager.m */; }; + 76EB058D18170B33006006FC /* DnsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042018170B33006006FC /* DnsManager.m */; }; + 76EB058E18170B33006006FC /* HostNameEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042218170B33006006FC /* HostNameEndPoint.m */; }; + 76EB058F18170B33006006FC /* HostNameEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042218170B33006006FC /* HostNameEndPoint.m */; }; + 76EB059018170B33006006FC /* IgnoredPacketFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042518170B33006006FC /* IgnoredPacketFailure.m */; }; + 76EB059118170B33006006FC /* IgnoredPacketFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042518170B33006006FC /* IgnoredPacketFailure.m */; }; + 76EB059218170B33006006FC /* UnrecognizedRequestFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042718170B33006006FC /* UnrecognizedRequestFailure.m */; }; + 76EB059318170B33006006FC /* UnrecognizedRequestFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042718170B33006006FC /* UnrecognizedRequestFailure.m */; }; + 76EB059418170B33006006FC /* HttpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042A18170B33006006FC /* HttpManager.m */; }; + 76EB059518170B33006006FC /* HttpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042A18170B33006006FC /* HttpManager.m */; }; + 76EB059618170B33006006FC /* HttpRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042C18170B33006006FC /* HttpRequest.m */; }; + 76EB059718170B33006006FC /* HttpRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042C18170B33006006FC /* HttpRequest.m */; }; + 76EB059818170B33006006FC /* HttpRequestOrResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042E18170B33006006FC /* HttpRequestOrResponse.m */; }; + 76EB059918170B33006006FC /* HttpRequestOrResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB042E18170B33006006FC /* HttpRequestOrResponse.m */; }; + 76EB059A18170B33006006FC /* HttpRequestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043018170B33006006FC /* HttpRequestUtil.m */; }; + 76EB059B18170B33006006FC /* HttpRequestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043018170B33006006FC /* HttpRequestUtil.m */; }; + 76EB059C18170B33006006FC /* HttpResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043218170B33006006FC /* HttpResponse.m */; }; + 76EB059D18170B33006006FC /* HttpResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043218170B33006006FC /* HttpResponse.m */; }; + 76EB059E18170B33006006FC /* HttpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043418170B33006006FC /* HttpSocket.m */; }; + 76EB059F18170B33006006FC /* HttpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043418170B33006006FC /* HttpSocket.m */; }; + 76EB05A018170B33006006FC /* IpAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043618170B33006006FC /* IpAddress.m */; }; + 76EB05A118170B33006006FC /* IpAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043618170B33006006FC /* IpAddress.m */; }; + 76EB05A218170B33006006FC /* IpEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043818170B33006006FC /* IpEndPoint.m */; }; + 76EB05A318170B33006006FC /* IpEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043818170B33006006FC /* IpEndPoint.m */; }; + 76EB05A418170B33006006FC /* PacketHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043B18170B33006006FC /* PacketHandler.m */; }; + 76EB05A518170B33006006FC /* PacketHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043B18170B33006006FC /* PacketHandler.m */; }; + 76EB05A618170B33006006FC /* RtpPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043E18170B33006006FC /* RtpPacket.m */; }; + 76EB05A718170B33006006FC /* RtpPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB043E18170B33006006FC /* RtpPacket.m */; }; + 76EB05A818170B33006006FC /* RtpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044018170B33006006FC /* RtpSocket.m */; }; + 76EB05A918170B33006006FC /* RtpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044018170B33006006FC /* RtpSocket.m */; }; + 76EB05AA18170B33006006FC /* SequenceCounter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044318170B33006006FC /* SequenceCounter.m */; }; + 76EB05AB18170B33006006FC /* SequenceCounter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044318170B33006006FC /* SequenceCounter.m */; }; + 76EB05AC18170B33006006FC /* SrtpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044518170B33006006FC /* SrtpSocket.m */; }; + 76EB05AD18170B33006006FC /* SrtpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044518170B33006006FC /* SrtpSocket.m */; }; + 76EB05AE18170B33006006FC /* SrtpStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044718170B33006006FC /* SrtpStream.m */; }; + 76EB05AF18170B33006006FC /* SrtpStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044718170B33006006FC /* SrtpStream.m */; }; + 76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044D18170B33006006FC /* DH3KKeyAgreementProtocol.m */; }; + 76EB05B318170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044D18170B33006006FC /* DH3KKeyAgreementProtocol.m */; }; + 76EB05B418170B33006006FC /* HashChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044F18170B33006006FC /* HashChain.m */; }; + 76EB05B518170B33006006FC /* HashChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB044F18170B33006006FC /* HashChain.m */; }; + 76EB05B618170B33006006FC /* MasterSecret.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045118170B33006006FC /* MasterSecret.m */; }; + 76EB05B718170B33006006FC /* MasterSecret.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045118170B33006006FC /* MasterSecret.m */; }; + 76EB05B818170B33006006FC /* NegotiationFailed.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045318170B33006006FC /* NegotiationFailed.m */; }; + 76EB05B918170B33006006FC /* NegotiationFailed.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045318170B33006006FC /* NegotiationFailed.m */; }; + 76EB05BA18170B33006006FC /* CommitPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045618170B33006006FC /* CommitPacket.m */; }; + 76EB05BB18170B33006006FC /* CommitPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045618170B33006006FC /* CommitPacket.m */; }; + 76EB05BC18170B33006006FC /* ConfirmAckPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045818170B33006006FC /* ConfirmAckPacket.m */; }; + 76EB05BD18170B33006006FC /* ConfirmAckPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045818170B33006006FC /* ConfirmAckPacket.m */; }; + 76EB05BE18170B33006006FC /* ConfirmPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045A18170B33006006FC /* ConfirmPacket.m */; }; + 76EB05BF18170B33006006FC /* ConfirmPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045A18170B33006006FC /* ConfirmPacket.m */; }; + 76EB05C018170B33006006FC /* DhPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045C18170B33006006FC /* DhPacket.m */; }; + 76EB05C118170B33006006FC /* DhPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045C18170B33006006FC /* DhPacket.m */; }; + 76EB05C218170B33006006FC /* DhPacketSharedSecretHashes.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045E18170B33006006FC /* DhPacketSharedSecretHashes.m */; }; + 76EB05C318170B33006006FC /* DhPacketSharedSecretHashes.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB045E18170B33006006FC /* DhPacketSharedSecretHashes.m */; }; + 76EB05C418170B33006006FC /* HandshakePacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046018170B33006006FC /* HandshakePacket.m */; }; + 76EB05C518170B33006006FC /* HandshakePacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046018170B33006006FC /* HandshakePacket.m */; }; + 76EB05C618170B33006006FC /* HelloAckPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046218170B33006006FC /* HelloAckPacket.m */; }; + 76EB05C718170B33006006FC /* HelloAckPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046218170B33006006FC /* HelloAckPacket.m */; }; + 76EB05C818170B33006006FC /* HelloPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046418170B33006006FC /* HelloPacket.m */; }; + 76EB05C918170B33006006FC /* HelloPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046418170B33006006FC /* HelloPacket.m */; }; + 76EB05CA18170B33006006FC /* RecipientUnavailable.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046A18170B33006006FC /* RecipientUnavailable.m */; }; + 76EB05CB18170B33006006FC /* RecipientUnavailable.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046A18170B33006006FC /* RecipientUnavailable.m */; }; + 76EB05CC18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046C18170B33006006FC /* ShortAuthenticationStringGenerator.m */; }; + 76EB05CD18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046C18170B33006006FC /* ShortAuthenticationStringGenerator.m */; }; + 76EB05CE18170B33006006FC /* ZrtpHandshakeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046E18170B33006006FC /* ZrtpHandshakeResult.m */; }; + 76EB05CF18170B33006006FC /* ZrtpHandshakeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB046E18170B33006006FC /* ZrtpHandshakeResult.m */; }; + 76EB05D018170B33006006FC /* ZrtpHandshakeSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047018170B33006006FC /* ZrtpHandshakeSocket.m */; }; + 76EB05D118170B33006006FC /* ZrtpHandshakeSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047018170B33006006FC /* ZrtpHandshakeSocket.m */; }; + 76EB05D218170B33006006FC /* ZrtpInitiator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047218170B33006006FC /* ZrtpInitiator.m */; }; + 76EB05D318170B33006006FC /* ZrtpInitiator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047218170B33006006FC /* ZrtpInitiator.m */; }; + 76EB05D418170B33006006FC /* ZrtpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047418170B33006006FC /* ZrtpManager.m */; }; + 76EB05D518170B33006006FC /* ZrtpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047418170B33006006FC /* ZrtpManager.m */; }; + 76EB05D618170B33006006FC /* ZrtpResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047618170B33006006FC /* ZrtpResponder.m */; }; + 76EB05D718170B33006006FC /* ZrtpResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047618170B33006006FC /* ZrtpResponder.m */; }; + 76EB05D818170B33006006FC /* LowLatencyCandidate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047918170B33006006FC /* LowLatencyCandidate.m */; }; + 76EB05D918170B33006006FC /* LowLatencyCandidate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047918170B33006006FC /* LowLatencyCandidate.m */; }; + 76EB05DA18170B33006006FC /* LowLatencyConnector.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047B18170B33006006FC /* LowLatencyConnector.m */; }; + 76EB05DB18170B33006006FC /* LowLatencyConnector.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047B18170B33006006FC /* LowLatencyConnector.m */; }; + 76EB05DC18170B33006006FC /* StreamPair.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047D18170B33006006FC /* StreamPair.m */; }; + 76EB05DD18170B33006006FC /* StreamPair.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB047D18170B33006006FC /* StreamPair.m */; }; + 76EB05DE18170B33006006FC /* Certificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048018170B33006006FC /* Certificate.m */; }; + 76EB05DF18170B33006006FC /* Certificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048018170B33006006FC /* Certificate.m */; }; + 76EB05E018170B33006006FC /* NetworkStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048218170B33006006FC /* NetworkStream.m */; }; + 76EB05E118170B33006006FC /* NetworkStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048218170B33006006FC /* NetworkStream.m */; }; + 76EB05E218170B33006006FC /* SecureEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048418170B33006006FC /* SecureEndPoint.m */; }; + 76EB05E318170B33006006FC /* SecureEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048418170B33006006FC /* SecureEndPoint.m */; }; + 76EB05E418170B33006006FC /* UdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048718170B33006006FC /* UdpSocket.m */; }; + 76EB05E518170B33006006FC /* UdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048718170B33006006FC /* UdpSocket.m */; }; + 76EB05E618170B33006006FC /* CallController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048B18170B33006006FC /* CallController.m */; }; + 76EB05E718170B33006006FC /* CallController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048B18170B33006006FC /* CallController.m */; }; + 76EB05E818170B33006006FC /* CallFailedServerMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048D18170B33006006FC /* CallFailedServerMessage.m */; }; + 76EB05E918170B33006006FC /* CallFailedServerMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048D18170B33006006FC /* CallFailedServerMessage.m */; }; + 76EB05EA18170B33006006FC /* CallProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048F18170B33006006FC /* CallProgress.m */; }; + 76EB05EB18170B33006006FC /* CallProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB048F18170B33006006FC /* CallProgress.m */; }; + 76EB05EC18170B33006006FC /* CallState.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049118170B33006006FC /* CallState.m */; }; + 76EB05ED18170B33006006FC /* CallState.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049118170B33006006FC /* CallState.m */; }; + 76EB05EE18170B33006006FC /* CallTermination.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049318170B33006006FC /* CallTermination.m */; }; + 76EB05EF18170B33006006FC /* CallTermination.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049318170B33006006FC /* CallTermination.m */; }; + 76EB05F018170B33006006FC /* PhoneManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049518170B33006006FC /* PhoneManager.m */; }; + 76EB05F118170B33006006FC /* PhoneManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049518170B33006006FC /* PhoneManager.m */; }; + 76EB05F218170B33006006FC /* PhoneNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049718170B33006006FC /* PhoneNumber.m */; }; + 76EB05F318170B33006006FC /* PhoneNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049718170B33006006FC /* PhoneNumber.m */; }; + 76EB05F418170B33006006FC /* CallConnectResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049A18170B33006006FC /* CallConnectResult.m */; }; + 76EB05F518170B33006006FC /* CallConnectResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049A18170B33006006FC /* CallConnectResult.m */; }; + 76EB05F618170B33006006FC /* CallConnectUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049C18170B33006006FC /* CallConnectUtil.m */; }; + 76EB05F718170B33006006FC /* CallConnectUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049C18170B33006006FC /* CallConnectUtil.m */; }; + 76EB05F818170B33006006FC /* CallConnectUtil_Initiator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049E18170B33006006FC /* CallConnectUtil_Initiator.m */; }; + 76EB05F918170B33006006FC /* CallConnectUtil_Initiator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB049E18170B33006006FC /* CallConnectUtil_Initiator.m */; }; + 76EB05FA18170B33006006FC /* CallConnectUtil_Responder.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A018170B33006006FC /* CallConnectUtil_Responder.m */; }; + 76EB05FB18170B33006006FC /* CallConnectUtil_Responder.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A018170B33006006FC /* CallConnectUtil_Responder.m */; }; + 76EB05FC18170B33006006FC /* CallConnectUtil_Server.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A218170B33006006FC /* CallConnectUtil_Server.m */; }; + 76EB05FD18170B33006006FC /* CallConnectUtil_Server.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A218170B33006006FC /* CallConnectUtil_Server.m */; }; + 76EB05FE18170B33006006FC /* InitiateSignal.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A418170B33006006FC /* InitiateSignal.pb.m */; }; + 76EB05FF18170B33006006FC /* InitiateSignal.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A418170B33006006FC /* InitiateSignal.pb.m */; }; + 76EB060118170B33006006FC /* InitiateSignal.proto in Resources */ = {isa = PBXBuildFile; fileRef = 76EB04A518170B33006006FC /* InitiateSignal.proto */; }; + 76EB060218170B33006006FC /* InitiatorSessionDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A718170B33006006FC /* InitiatorSessionDescriptor.m */; }; + 76EB060318170B33006006FC /* InitiatorSessionDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04A718170B33006006FC /* InitiatorSessionDescriptor.m */; }; + 76EB060418170B33006006FC /* PhoneNumberDirectoryFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AA18170B33006006FC /* PhoneNumberDirectoryFilter.m */; }; + 76EB060518170B33006006FC /* PhoneNumberDirectoryFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AA18170B33006006FC /* PhoneNumberDirectoryFilter.m */; }; + 76EB060618170B33006006FC /* PhoneNumberDirectoryFilterManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AC18170B33006006FC /* PhoneNumberDirectoryFilterManager.m */; }; + 76EB060718170B33006006FC /* PhoneNumberDirectoryFilterManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AC18170B33006006FC /* PhoneNumberDirectoryFilterManager.m */; }; + 76EB060818170B33006006FC /* ResponderSessionDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AE18170B33006006FC /* ResponderSessionDescriptor.m */; }; + 76EB060918170B33006006FC /* ResponderSessionDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04AE18170B33006006FC /* ResponderSessionDescriptor.m */; }; + 76EB060A18170B33006006FC /* SignalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B018170B33006006FC /* SignalUtil.m */; }; + 76EB060B18170B33006006FC /* SignalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B018170B33006006FC /* SignalUtil.m */; }; + 76EB060C18170B33006006FC /* CategorizingLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B318170B33006006FC /* CategorizingLogger.m */; }; + 76EB060D18170B33006006FC /* CategorizingLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B318170B33006006FC /* CategorizingLogger.m */; }; + 76EB060E18170B33006006FC /* DecayingSampleEstimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B518170B33006006FC /* DecayingSampleEstimator.m */; }; + 76EB060F18170B33006006FC /* DecayingSampleEstimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B518170B33006006FC /* DecayingSampleEstimator.m */; }; + 76EB061018170B33006006FC /* EventWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B718170B33006006FC /* EventWindow.m */; }; + 76EB061118170B33006006FC /* EventWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B718170B33006006FC /* EventWindow.m */; }; + 76EB061218170B33006006FC /* LoggingUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B918170B33006006FC /* LoggingUtil.m */; }; + 76EB061318170B33006006FC /* LoggingUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04B918170B33006006FC /* LoggingUtil.m */; }; + 76EB061418170B33006006FC /* AnonymousConditionLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C018170B33006006FC /* AnonymousConditionLogger.m */; }; + 76EB061518170B33006006FC /* AnonymousConditionLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C018170B33006006FC /* AnonymousConditionLogger.m */; }; + 76EB061618170B33006006FC /* AnonymousOccurrenceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C218170B33006006FC /* AnonymousOccurrenceLogger.m */; }; + 76EB061718170B33006006FC /* AnonymousOccurrenceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C218170B33006006FC /* AnonymousOccurrenceLogger.m */; }; + 76EB061818170B33006006FC /* AnonymousValueLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C418170B33006006FC /* AnonymousValueLogger.m */; }; + 76EB061918170B33006006FC /* AnonymousValueLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C418170B33006006FC /* AnonymousValueLogger.m */; }; + 76EB061A18170B33006006FC /* DiscardingLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C618170B33006006FC /* DiscardingLog.m */; }; + 76EB061B18170B33006006FC /* DiscardingLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04C618170B33006006FC /* DiscardingLog.m */; }; + 76EB061C18170B33006006FC /* ArrayUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04CA18170B33006006FC /* ArrayUtil.m */; }; + 76EB061D18170B33006006FC /* ArrayUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04CA18170B33006006FC /* ArrayUtil.m */; }; + 76EB062018170B33006006FC /* BloomFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04CE18170B33006006FC /* BloomFilter.m */; }; + 76EB062118170B33006006FC /* BloomFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04CE18170B33006006FC /* BloomFilter.m */; }; + 76EB062218170B33006006FC /* CyclicalBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D118170B33006006FC /* CyclicalBuffer.m */; }; + 76EB062318170B33006006FC /* CyclicalBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D118170B33006006FC /* CyclicalBuffer.m */; }; + 76EB062418170B33006006FC /* PriorityQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D318170B33006006FC /* PriorityQueue.m */; }; + 76EB062518170B33006006FC /* PriorityQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D318170B33006006FC /* PriorityQueue.m */; }; + 76EB062618170B33006006FC /* Queue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D518170B33006006FC /* Queue.m */; }; + 76EB062718170B33006006FC /* Queue.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D518170B33006006FC /* Queue.m */; }; + 76EB062818170B33006006FC /* BadArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D818170B33006006FC /* BadArgument.m */; }; + 76EB062918170B33006006FC /* BadArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04D818170B33006006FC /* BadArgument.m */; }; + 76EB062A18170B33006006FC /* BadState.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DA18170B33006006FC /* BadState.m */; }; + 76EB062B18170B33006006FC /* BadState.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DA18170B33006006FC /* BadState.m */; }; + 76EB062C18170B33006006FC /* OperationFailed.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DD18170B33006006FC /* OperationFailed.m */; }; + 76EB062D18170B33006006FC /* OperationFailed.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DD18170B33006006FC /* OperationFailed.m */; }; + 76EB062E18170B33006006FC /* SecurityFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DF18170B33006006FC /* SecurityFailure.m */; }; + 76EB062F18170B33006006FC /* SecurityFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04DF18170B33006006FC /* SecurityFailure.m */; }; + 76EB063018170B33006006FC /* Conversions.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E118170B33006006FC /* Conversions.m */; }; + 76EB063118170B33006006FC /* Conversions.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E118170B33006006FC /* Conversions.m */; }; + 76EB063218170B33006006FC /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E318170B33006006FC /* Crc32.m */; }; + 76EB063318170B33006006FC /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E318170B33006006FC /* Crc32.m */; }; + 76EB063618170B33006006FC /* DataUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E718170B33006006FC /* DataUtil.m */; }; + 76EB063718170B33006006FC /* DataUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E718170B33006006FC /* DataUtil.m */; }; + 76EB063818170B33006006FC /* DictionaryUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E918170B33006006FC /* DictionaryUtil.m */; }; + 76EB063918170B33006006FC /* DictionaryUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04E918170B33006006FC /* DictionaryUtil.m */; }; + 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04EB18170B33006006FC /* FunctionalUtil.m */; }; + 76EB063B18170B33006006FC /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04EB18170B33006006FC /* FunctionalUtil.m */; }; + 76EB063C18170B33006006FC /* NumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04ED18170B33006006FC /* NumberUtil.m */; }; + 76EB063D18170B33006006FC /* NumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04ED18170B33006006FC /* NumberUtil.m */; }; + 76EB063E18170B33006006FC /* Operation.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04EF18170B33006006FC /* Operation.m */; }; + 76EB063F18170B33006006FC /* Operation.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04EF18170B33006006FC /* Operation.m */; }; + 76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F418170B33006006FC /* AnonymousTerminator.m */; }; + 76EB064118170B33006006FC /* AnonymousTerminator.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F418170B33006006FC /* AnonymousTerminator.m */; }; + 76EB064218170B33006006FC /* StringUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F618170B33006006FC /* StringUtil.m */; }; + 76EB064318170B33006006FC /* StringUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F618170B33006006FC /* StringUtil.m */; }; + 76EB064418170B33006006FC /* ThreadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F818170B33006006FC /* ThreadManager.m */; }; + 76EB064518170B33006006FC /* ThreadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04F818170B33006006FC /* ThreadManager.m */; }; + 76EB064618170B33006006FC /* TimeUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04FA18170B33006006FC /* TimeUtil.m */; }; + 76EB064718170B33006006FC /* TimeUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04FA18170B33006006FC /* TimeUtil.m */; }; + 76EB064818170B33006006FC /* Zid.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04FD18170B33006006FC /* Zid.m */; }; + 76EB064918170B33006006FC /* Zid.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04FD18170B33006006FC /* Zid.m */; }; + 76EB064C18170B34006006FC /* ContactBrowseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050218170B33006006FC /* ContactBrowseViewController.m */; }; + 76EB064D18170B34006006FC /* ContactBrowseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050218170B33006006FC /* ContactBrowseViewController.m */; }; + 76EB064E18170B34006006FC /* ContactDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050418170B33006006FC /* ContactDetailViewController.m */; }; + 76EB064F18170B34006006FC /* ContactDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050418170B33006006FC /* ContactDetailViewController.m */; }; + 76EB065018170B34006006FC /* DialerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050618170B33006006FC /* DialerViewController.m */; }; + 76EB065118170B34006006FC /* DialerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050618170B33006006FC /* DialerViewController.m */; }; + 76EB065218170B34006006FC /* FavouritesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050818170B33006006FC /* FavouritesViewController.m */; }; + 76EB065318170B34006006FC /* FavouritesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050818170B33006006FC /* FavouritesViewController.m */; }; + 76EB065418170B34006006FC /* InboxFeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050A18170B33006006FC /* InboxFeedViewController.m */; }; + 76EB065518170B34006006FC /* InboxFeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050A18170B33006006FC /* InboxFeedViewController.m */; }; + 76EB065618170B34006006FC /* InCallViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050C18170B33006006FC /* InCallViewController.m */; }; + 76EB065718170B34006006FC /* InCallViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050C18170B33006006FC /* InCallViewController.m */; }; + 76EB065818170B34006006FC /* LeftSideMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050E18170B33006006FC /* LeftSideMenuViewController.m */; }; + 76EB065918170B34006006FC /* LeftSideMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB050E18170B33006006FC /* LeftSideMenuViewController.m */; }; + 76EB065A18170B34006006FC /* NextResponderScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051018170B33006006FC /* NextResponderScrollView.m */; }; + 76EB065B18170B34006006FC /* NextResponderScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051018170B33006006FC /* NextResponderScrollView.m */; }; + 76EB065C18170B34006006FC /* CallLogViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051218170B33006006FC /* CallLogViewController.m */; }; + 76EB065D18170B34006006FC /* CallLogViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051218170B33006006FC /* CallLogViewController.m */; }; + 76EB066018170B34006006FC /* RegisterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051618170B33006006FC /* RegisterViewController.m */; }; + 76EB066118170B34006006FC /* RegisterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051618170B33006006FC /* RegisterViewController.m */; }; + 76EB066218170B34006006FC /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051818170B33006006FC /* SettingsViewController.m */; }; + 76EB066318170B34006006FC /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051818170B33006006FC /* SettingsViewController.m */; }; + 76EB066418170B34006006FC /* TabBarParentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051A18170B33006006FC /* TabBarParentViewController.m */; }; + 76EB066518170B34006006FC /* TabBarParentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB051A18170B33006006FC /* TabBarParentViewController.m */; }; + 76EB066718170B34006006FC /* TabBarParentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051B18170B33006006FC /* TabBarParentViewController.xib */; }; + 76EB066918170B34006006FC /* CallAudioManagerDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051D18170B33006006FC /* CallAudioManagerDemo.xib */; }; + 76EB066D18170B34006006FC /* ContactDetailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051F18170B33006006FC /* ContactDetailTableViewCell.xib */; }; + 76EB066F18170B34006006FC /* ContactDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052018170B33006006FC /* ContactDetailViewController.xib */; }; + 76EB067118170B34006006FC /* DialerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052118170B33006006FC /* DialerViewController.xib */; }; + 76EB067318170B34006006FC /* DowngradeCallViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052218170B33006006FC /* DowngradeCallViewController.xib */; }; + 76EB067518170B34006006FC /* FavouritesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052318170B33006006FC /* FavouritesViewController.xib */; }; + 76EB067718170B34006006FC /* InboxFeedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052418170B33006006FC /* InboxFeedViewController.xib */; }; + 76EB067918170B34006006FC /* InCallViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052518170B33006006FC /* InCallViewController.xib */; }; + 76EB067B18170B34006006FC /* LeftSideMenuViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052618170B33006006FC /* LeftSideMenuViewController.xib */; }; + 76EB067D18170B34006006FC /* CallLogViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052718170B33006006FC /* CallLogViewController.xib */; }; + 76EB068118170B34006006FC /* RegisterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052918170B33006006FC /* RegisterViewController.xib */; }; + 76EB068318170B34006006FC /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052A18170B33006006FC /* SettingsViewController.xib */; }; + 76EB068418170B34006006FC /* ContactDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB052D18170B33006006FC /* ContactDetailTableViewCell.m */; }; + 76EB068518170B34006006FC /* ContactDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB052D18170B33006006FC /* ContactDetailTableViewCell.m */; }; + 76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB052F18170B33006006FC /* ContactTableViewCell.m */; }; + 76EB068718170B34006006FC /* ContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB052F18170B33006006FC /* ContactTableViewCell.m */; }; + 76EB068C18170B34006006FC /* InboxFeedTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB053518170B33006006FC /* InboxFeedTableViewCell.m */; }; + 76EB068D18170B34006006FC /* InboxFeedTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB053518170B33006006FC /* InboxFeedTableViewCell.m */; }; + 76EB068E18170B34006006FC /* CallLogTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB053718170B33006006FC /* CallLogTableViewCell.m */; }; + 76EB068F18170B34006006FC /* CallLogTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB053718170B33006006FC /* CallLogTableViewCell.m */; }; + 76EB069318170B34006006FC /* ContactTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053A18170B33006006FC /* ContactTableViewCell.xib */; }; + 76EB069718170B34006006FC /* InboxFeedTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053C18170B33006006FC /* InboxFeedTableViewCell.xib */; }; + 76EB069D18170B34006006FC /* CallLogTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053F18170B33006006FC /* CallLogTableViewCell.xib */; }; + A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; + A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; + A123C14916F902EE000AE905 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; + A157075417F0CD6D007C2BD6 /* AsyncUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706ED17F0CD6D007C2BD6 /* AsyncUtilTest.m */; }; + A157075517F0CD6D007C2BD6 /* FutureSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706EF17F0CD6D007C2BD6 /* FutureSourceTest.m */; }; + A157075617F0CD6D007C2BD6 /* ObservableTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706F117F0CD6D007C2BD6 /* ObservableTest.m */; }; + A157075717F0CD6D007C2BD6 /* AudioFrameTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706F417F0CD6D007C2BD6 /* AudioFrameTest.m */; }; + A157075817F0CD6D007C2BD6 /* AudioRemoteIOTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706F617F0CD6D007C2BD6 /* AudioRemoteIOTest.m */; }; + A157075917F0CD6D007C2BD6 /* AudioStretcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706F817F0CD6D007C2BD6 /* AudioStretcherTest.m */; }; + A157075A17F0CD6D007C2BD6 /* JitterQueueTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706FA17F0CD6D007C2BD6 /* JitterQueueTest.m */; }; + A157075B17F0CD6D007C2BD6 /* SpeexCodecTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A15706FC17F0CD6D007C2BD6 /* SpeexCodecTest.m */; }; + A157075D17F0CD6D007C2BD6 /* DnsManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157070317F0CD6D007C2BD6 /* DnsManagerTest.m */; }; + A157075E17F0CD6D007C2BD6 /* HttpRequestResponseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157070617F0CD6D007C2BD6 /* HttpRequestResponseTest.m */; }; + A157075F17F0CD6D007C2BD6 /* IpAddressTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157070817F0CD6D007C2BD6 /* IpAddressTest.m */; }; + A157076017F0CD6D007C2BD6 /* IpEndPointTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157070A17F0CD6D007C2BD6 /* IpEndPointTest.m */; }; + A157076117F0CD6D007C2BD6 /* RtpPacketTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A157070D17F0CD6D007C2BD6 /* RtpPacketTests.m */; }; + A157076217F0CD6D007C2BD6 /* SecureStreamTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071017F0CD6D007C2BD6 /* SecureStreamTest.m */; }; + A157076317F0CD6D007C2BD6 /* SequenceCounterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071217F0CD6D007C2BD6 /* SequenceCounterTest.m */; }; + A157076417F0CD6D007C2BD6 /* DH3KAgreerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071517F0CD6D007C2BD6 /* DH3KAgreerTest.m */; }; + A157076517F0CD6D007C2BD6 /* HandshakePacketTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071717F0CD6D007C2BD6 /* HandshakePacketTest.m */; }; + A157076617F0CD6D007C2BD6 /* HashChainTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071917F0CD6D007C2BD6 /* HashChainTest.m */; }; + A157076717F0CD6D007C2BD6 /* MasterSecretTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071B17F0CD6D007C2BD6 /* MasterSecretTest.m */; }; + A157076817F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157071D17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m */; }; + A157076917F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072017F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m */; }; + A157076A17F0CD6D007C2BD6 /* ZrtpTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072217F0CD6D007C2BD6 /* ZrtpTest.m */; }; + A157076B17F0CD6D007C2BD6 /* LowLatencyConnectorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072517F0CD6D007C2BD6 /* LowLatencyConnectorTest.m */; }; + A157076C17F0CD6D007C2BD6 /* NetworkStreamTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072817F0CD6D007C2BD6 /* NetworkStreamTest.m */; }; + A157076D17F0CD6D007C2BD6 /* SecureEndPointTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072A17F0CD6D007C2BD6 /* SecureEndPointTest.m */; }; + A157076E17F0CD6D007C2BD6 /* UdpSocketTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157072D17F0CD6D007C2BD6 /* UdpSocketTest.m */; }; + A157076F17F0CD6D007C2BD6 /* PhoneNumberTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073017F0CD6D007C2BD6 /* PhoneNumberTest.m */; }; + A157077017F0CD6D007C2BD6 /* SessionDescriptorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073317F0CD6D007C2BD6 /* SessionDescriptorTest.m */; }; + A157077117F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073617F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.m */; }; + A157077217F0CD6D007C2BD6 /* EventWindowTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073817F0CD6D007C2BD6 /* EventWindowTest.m */; }; + A157077417F0CD6D007C2BD6 /* TestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073C17F0CD6D007C2BD6 /* TestUtil.m */; }; + A157077517F0CD6D007C2BD6 /* BloomFilterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157073F17F0CD6D007C2BD6 /* BloomFilterTest.m */; }; + A157077617F0CD6D007C2BD6 /* CancelTokenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074117F0CD6D007C2BD6 /* CancelTokenTest.m */; }; + A157077717F0CD6D007C2BD6 /* ConversionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074317F0CD6D007C2BD6 /* ConversionsTest.m */; }; + A157077817F0CD6D007C2BD6 /* Crc32Test.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074517F0CD6D007C2BD6 /* Crc32Test.m */; }; + A157077917F0CD6D007C2BD6 /* CryptoUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074717F0CD6D007C2BD6 /* CryptoUtilTest.m */; }; + A157077A17F0CD6D007C2BD6 /* CyclicalBufferTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074917F0CD6D007C2BD6 /* CyclicalBufferTest.m */; }; + A157077B17F0CD6D007C2BD6 /* ExceptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074B17F0CD6D007C2BD6 /* ExceptionsTest.m */; }; + A157077C17F0CD6D007C2BD6 /* FunctionalUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074D17F0CD6D007C2BD6 /* FunctionalUtilTest.m */; }; + A157077D17F0CD6D007C2BD6 /* PriorityQueueTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157074F17F0CD6D007C2BD6 /* PriorityQueueTest.m */; }; + A157077E17F0CD6D007C2BD6 /* QueueTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157075117F0CD6D007C2BD6 /* QueueTest.m */; }; + A157077F17F0CD6D007C2BD6 /* UtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A157075317F0CD6D007C2BD6 /* UtilTest.m */; }; + A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; + A194D3B917A08CD1004BD3A9 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; + A194D3BA17A08CD5004BD3A9 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; + A1A018521805C5E800A052A6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; + A1A018531805C60D00A052A6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A091169C9E5E00537ABF /* CoreGraphics.framework */; }; + A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; + A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; + AA0C8E498E2046B0B81EEE6E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8313AE91B4954215858A5662 /* libPods.a */; }; + B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; + B68DF7B71918D7FC00C7BAB9 /* KeyChainStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B68DF7B61918D7FC00C7BAB9 /* KeyChainStorage.m */; }; + B6B6C3C71919440C00C0B76B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6B6C3C51919440C00C0B76B /* Localizable.strings */; }; + B6BAB53F1918CA4100E4DF53 /* KeychainWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = B6BAB53E1918CA4100E4DF53 /* KeychainWrapper.m */; }; + B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; + B90418E7183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; + B942EB0E183A9633000887BB /* SearchBarTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = B942EB0D183A9633000887BB /* SearchBarTitleView.m */; }; + B942EB0F183A9633000887BB /* SearchBarTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = B942EB0D183A9633000887BB /* SearchBarTitleView.m */; }; + B942EB10183AC467000887BB /* ContactBrowseViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051E18170B33006006FC /* ContactBrowseViewController.xib */; }; + B96A3100187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */; }; + B97940271832BD2400BD66CB /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B97940261832BD2400BD66CB /* UIUtil.m */; }; + B97940281832BD2400BD66CB /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B97940261832BD2400BD66CB /* UIUtil.m */; }; + B97CBFA818860EA3008E0DE9 /* CountryCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B97CBFA618860EA3008E0DE9 /* CountryCodeViewController.m */; }; + B97CBFA918860EA3008E0DE9 /* CountryCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B97CBFA618860EA3008E0DE9 /* CountryCodeViewController.m */; }; + B97CBFAB18860EA3008E0DE9 /* CountryCodeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B97CBFA718860EA3008E0DE9 /* CountryCodeViewController.xib */; }; + B97CBFAE1886100E008E0DE9 /* CountryCodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B97CBFAD1886100E008E0DE9 /* CountryCodeTableViewCell.m */; }; + B97CBFAF1886100E008E0DE9 /* CountryCodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B97CBFAD1886100E008E0DE9 /* CountryCodeTableViewCell.m */; }; + B97CBFB218861023008E0DE9 /* CountryCodeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B97CBFB018861023008E0DE9 /* CountryCodeTableViewCell.xib */; }; + B9A578B1183D60EE00C17105 /* FavouriteTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9A578B0183D60ED00C17105 /* FavouriteTableViewCell.m */; }; + B9A578B2183D60EE00C17105 /* FavouriteTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9A578B0183D60ED00C17105 /* FavouriteTableViewCell.m */; }; + B9A578B5183D610300C17105 /* FavouriteTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9A578B3183D610300C17105 /* FavouriteTableViewCell.xib */; }; + B9B89C54185A2B5F00A24465 /* LeftSideMenuCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9B89C53185A2B5F00A24465 /* LeftSideMenuCell.m */; }; + B9B89C55185A2B5F00A24465 /* LeftSideMenuCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9B89C53185A2B5F00A24465 /* LeftSideMenuCell.m */; }; + B9B89C58185A2B7000A24465 /* LeftSideMenuCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9B89C56185A2B7000A24465 /* LeftSideMenuCell.xib */; }; + B9CA51BA18809ACA007E204E /* InviteContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B9CA51B818809ACA007E204E /* InviteContactsViewController.m */; }; + B9CA51BB18809ACA007E204E /* InviteContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B9CA51B818809ACA007E204E /* InviteContactsViewController.m */; }; + B9CA51BD18809ACA007E204E /* InviteContactsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9CA51B918809ACA007E204E /* InviteContactsViewController.xib */; }; + B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; + B9EB5AC61884D370007CBB57 /* UnseenWhisperUserCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9EB5AC51884D370007CBB57 /* UnseenWhisperUserCell.m */; }; + B9EB5AC71884D370007CBB57 /* UnseenWhisperUserCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B9EB5AC51884D370007CBB57 /* UnseenWhisperUserCell.m */; }; + B9EB5ACA1884D387007CBB57 /* UnseenWhisperUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EB5AC81884D387007CBB57 /* UnseenWhisperUserCell.xib */; }; + D202868116DBE0E7009068E9 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; + D202868216DBE0F4009068E9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; + D202868316DBE0FC009068E9 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; + D202868416DBE108009068E9 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */; }; + D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; + D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; + D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; + D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08F169C9E5E00537ABF /* Foundation.framework */; }; + D221A092169C9E5E00537ABF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A091169C9E5E00537ABF /* CoreGraphics.framework */; }; + D221A09A169C9E5E00537ABF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D221A099169C9E5E00537ABF /* main.m */; }; + D221A0AC169C9E5F00537ABF /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0AB169C9E5F00537ABF /* SenTestingKit.framework */; }; + D221A0AD169C9E5F00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; + D221A0AE169C9E5F00537ABF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08F169C9E5E00537ABF /* Foundation.framework */; }; + D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0E7169DFFC500537ABF /* AVFoundation.framework */; }; + D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; }; + D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; + E1368CBE18A1C36B00109378 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; + E1370BE018A0686600826894 /* busy.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40718A05754001A532A /* busy.mp3 */; }; + E1370BE118A0686C00826894 /* completed.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40818A05754001A532A /* completed.mp3 */; }; + E1370BE218A0686C00826894 /* failure.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40918A05754001A532A /* failure.mp3 */; }; + E1370BE318A0686C00826894 /* handshake.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40A18A05754001A532A /* handshake.mp3 */; }; + E1370BE418A0686C00826894 /* outring.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40B18A05754001A532A /* outring.mp3 */; }; + E1370BE518A0686C00826894 /* r.caf in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40C18A05754001A532A /* r.caf */; }; + E1370BE618A0686C00826894 /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40D18A05754001A532A /* sonarping.mp3 */; }; + E1370BE718A0688300826894 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370BDA18A066F600826894 /* Default-568h@2x.png */; }; + E1370BE818A0688300826894 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370BDB18A066F600826894 /* Default.png */; }; + E1370BE918A0688300826894 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370BDC18A066F600826894 /* Default@2x.png */; }; + E1370BEA18A0689000826894 /* AppIcon29x29.jpg in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F418A05734001A532A /* AppIcon29x29.jpg */; }; + E1370BEB18A0689000826894 /* AppIcon29x29.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F518A05734001A532A /* AppIcon29x29.png */; }; + E1370BEC18A0689000826894 /* AppIcon29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F618A05734001A532A /* AppIcon29x29@2x.png */; }; + E1370BED18A0689000826894 /* AppIcon40x40.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F718A05734001A532A /* AppIcon40x40.png */; }; + E1370BEE18A0689000826894 /* AppIcon40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F818A05734001A532A /* AppIcon40x40@2x.png */; }; + E1370BEF18A0689000826894 /* AppIcon60x60.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3F918A05734001A532A /* AppIcon60x60.png */; }; + E1370BF018A0689000826894 /* AppIcon60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3FA18A05734001A532A /* AppIcon60x60@2x.png */; }; + E1370BF118A0689000826894 /* AppIcon76x76.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3FB18A05734001A532A /* AppIcon76x76.png */; }; + E1370BF218A0689000826894 /* AppIcon76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E18AB3FC18A05734001A532A /* AppIcon76x76@2x.png */; }; + E1370BF618A068A600826894 /* whisperReal.der in Resources */ = {isa = PBXBuildFile; fileRef = E1C407C117F0C246007BEE65 /* whisperReal.der */; }; + E14874A218A0692F002CC4F3 /* archive_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3018A0660300826894 /* archive_icon.png */; }; + E14874A318A0692F002CC4F3 /* archive_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3118A0660300826894 /* archive_icon@2x.png */; }; + E14874A418A0692F002CC4F3 /* backspace.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3218A0660300826894 /* backspace.png */; }; + E14874A518A0692F002CC4F3 /* backspace@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3318A0660300826894 /* backspace@2x.png */; }; + E14874A618A0692F002CC4F3 /* checkbox_checkmark.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3418A0660300826894 /* checkbox_checkmark.png */; }; + E14874A718A0692F002CC4F3 /* checkbox_checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3518A0660300826894 /* checkbox_checkmark@2x.png */; }; + E14874A818A0692F002CC4F3 /* checkbox_empty.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3618A0660300826894 /* checkbox_empty.png */; }; + E14874A918A0692F002CC4F3 /* checkbox_empty@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3718A0660300826894 /* checkbox_empty@2x.png */; }; + E14874AA18A0692F002CC4F3 /* contact_default_feed.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3818A0660300826894 /* contact_default_feed.png */; }; + E14874AB18A0692F002CC4F3 /* contacts_arrow.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3918A0660300826894 /* contacts_arrow.png */; }; + E14874AC18A0692F002CC4F3 /* contacts_arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3A18A0660300826894 /* contacts_arrow@2x.png */; }; + E14874AD18A0692F002CC4F3 /* DefaultContactImage.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3B18A0660300826894 /* DefaultContactImage.png */; }; + E14874AE18A0692F002CC4F3 /* dismiss_notification_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3C18A0660300826894 /* dismiss_notification_icon.png */; }; + E14874AF18A0692F002CC4F3 /* dismiss_notification_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3D18A0660300826894 /* dismiss_notification_icon@2x.png */; }; + E14874B018A0692F002CC4F3 /* drop_down_arrow_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3E18A0660300826894 /* drop_down_arrow_icon.png */; }; + E14874B118A0692F002CC4F3 /* drop_down_arrow_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B3F18A0660300826894 /* drop_down_arrow_icon@2x.png */; }; + E14874B218A0692F002CC4F3 /* expanded_cell_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4018A0660300826894 /* expanded_cell_icon.png */; }; + E14874B318A0692F002CC4F3 /* expanded_cell_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4118A0660300826894 /* expanded_cell_icon@2x.png */; }; + E14874B418A0692F002CC4F3 /* favourite_false_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4218A0660300826894 /* favourite_false_icon.png */; }; + E14874B518A0692F002CC4F3 /* favourite_false_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4318A0660300826894 /* favourite_false_icon@2x.png */; }; + E14874B618A0692F002CC4F3 /* favourite_true_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4418A0660300826894 /* favourite_true_icon.png */; }; + E14874B718A0692F002CC4F3 /* favourite_true_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4518A0660300826894 /* favourite_true_icon@2x.png */; }; + E14874B818A0692F002CC4F3 /* forward_button.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4618A0660300826894 /* forward_button.png */; }; + E14874B918A0692F002CC4F3 /* forward_button@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4718A0660300826894 /* forward_button@2x.png */; }; + E14874BA18A0692F002CC4F3 /* home_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4818A0660300826894 /* home_icon.png */; }; + E14874BB18A0692F002CC4F3 /* icon_contacts.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4918A0660300826894 /* icon_contacts.png */; }; + E14874BC18A0692F002CC4F3 /* icon_favourites.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4A18A0660300826894 /* icon_favourites.png */; }; + E14874BD18A0692F002CC4F3 /* icon_keypad.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4B18A0660300826894 /* icon_keypad.png */; }; + E14874BE18A0692F002CC4F3 /* icon_recents.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4C18A0660300826894 /* icon_recents.png */; }; + E14874BF18A0692F002CC4F3 /* in_call_phone_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4D18A0660300826894 /* in_call_phone_icon.png */; }; + E14874C018A0692F002CC4F3 /* in_call_phone_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4E18A0660300826894 /* in_call_phone_icon@2x.png */; }; + E14874C118A0692F002CC4F3 /* in_call_phrase_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B4F18A0660300826894 /* in_call_phrase_icon.png */; }; + E14874C218A0692F002CC4F3 /* in_call_phrase_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5018A0660300826894 /* in_call_phrase_icon@2x.png */; }; + E14874C318A0692F002CC4F3 /* incoming_call_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5118A0660300826894 /* incoming_call_icon.png */; }; + E14874C418A0692F002CC4F3 /* incoming_call_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5218A0660300826894 /* incoming_call_icon@2x.png */; }; + E14874C518A0692F002CC4F3 /* menu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5318A0660300826894 /* menu_icon.png */; }; + E14874C618A0692F002CC4F3 /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5418A0660300826894 /* menu_icon@2x.png */; }; + E14874C718A0692F002CC4F3 /* message_bubble.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5518A0660300826894 /* message_bubble.png */; }; + E14874C818A0692F002CC4F3 /* message_bubble@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5618A0660300826894 /* message_bubble@2x.png */; }; + E14874C918A0692F002CC4F3 /* mute_icon_selected.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5718A0660300826894 /* mute_icon_selected.png */; }; + E14874CA18A0692F002CC4F3 /* mute_icon_selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5818A0660300826894 /* mute_icon_selected@2x.png */; }; + E14874CB18A0692F002CC4F3 /* mute_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5918A0660300826894 /* mute_icon.png */; }; + E14874CC18A0692F002CC4F3 /* mute_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5A18A0660300826894 /* mute_icon@2x.png */; }; + E14874CD18A0692F002CC4F3 /* notification_detail_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5B18A0660300826894 /* notification_detail_icon.png */; }; + E14874CE18A0692F002CC4F3 /* notification_detail_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5C18A0660300826894 /* notification_detail_icon@2x.png */; }; + E14874CF18A0692F002CC4F3 /* notification_mini_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5D18A0660300826894 /* notification_mini_icon.png */; }; + E14874D018A0692F002CC4F3 /* notification_mini_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5E18A0660300826894 /* notification_mini_icon@2x.png */; }; + E14874D118A0692F002CC4F3 /* outgoing_call_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B5F18A0660300826894 /* outgoing_call_icon.png */; }; + E14874D218A0692F002CC4F3 /* outgoing_call_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6018A0660300826894 /* outgoing_call_icon@2x.png */; }; + E14874D318A0692F002CC4F3 /* search_cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6118A0660300826894 /* search_cancel.png */; }; + E14874D418A0692F002CC4F3 /* search_cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6218A0660300826894 /* search_cancel@2x.png */; }; + E14874D518A0692F002CC4F3 /* search_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6318A0660300826894 /* search_icon.png */; }; + E14874D618A0692F002CC4F3 /* search_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6418A0660300826894 /* search_icon@2x.png */; }; + E14874D718A0692F002CC4F3 /* send_code_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6518A0660300826894 /* send_code_icon.png */; }; + E14874D818A0692F002CC4F3 /* send_code_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6618A0660300826894 /* send_code_icon@2x.png */; }; + E14874D918A0692F002CC4F3 /* speaker_icon_selected.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6718A0660300826894 /* speaker_icon_selected.png */; }; + E14874DA18A0692F002CC4F3 /* speaker_icon_selected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6818A0660300826894 /* speaker_icon_selected@2x.png */; }; + E14874DB18A0692F002CC4F3 /* speaker_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6918A0660300826894 /* speaker_icon.png */; }; + E14874DC18A0692F002CC4F3 /* speaker_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6A18A0660300826894 /* speaker_icon@2x.png */; }; + E14874DD18A0692F002CC4F3 /* spinner_connecting_flash.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6B18A0660300826894 /* spinner_connecting_flash.png */; }; + E14874DE18A0692F002CC4F3 /* spinner_connecting_flash@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6C18A0660300826894 /* spinner_connecting_flash@2x.png */; }; + E14874DF18A06930002CC4F3 /* spinner_connecting.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6D18A0660300826894 /* spinner_connecting.png */; }; + E14874E018A06930002CC4F3 /* spinner_connecting@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6E18A0660300826894 /* spinner_connecting@2x.png */; }; + E14874E118A06930002CC4F3 /* spinner_error.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B6F18A0660300826894 /* spinner_error.png */; }; + E14874E218A06930002CC4F3 /* spinner_error@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7018A0660300826894 /* spinner_error@2x.png */; }; + E14874E318A06930002CC4F3 /* spinner_ringing.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7118A0660300826894 /* spinner_ringing.png */; }; + E14874E418A06930002CC4F3 /* spinner_ringing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7218A0660300826894 /* spinner_ringing@2x.png */; }; + E14874E518A06930002CC4F3 /* tab_icon_contacts.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7318A0660300826894 /* tab_icon_contacts.png */; }; + E14874E618A06930002CC4F3 /* tab_icon_contacts@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7418A0660300826894 /* tab_icon_contacts@2x.png */; }; + E14874E718A06930002CC4F3 /* tab_icon_favourites.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7518A0660300826894 /* tab_icon_favourites.png */; }; + E14874E818A06930002CC4F3 /* tab_icon_favourites@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7618A0660300826894 /* tab_icon_favourites@2x.png */; }; + E14874E918A06930002CC4F3 /* tab_icon_inbox.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7718A0660300826894 /* tab_icon_inbox.png */; }; + E14874EA18A06930002CC4F3 /* tab_icon_inbox@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7818A0660300826894 /* tab_icon_inbox@2x.png */; }; + E14874EB18A06930002CC4F3 /* tab_icon_keypad.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7918A0660300826894 /* tab_icon_keypad.png */; }; + E14874EC18A06930002CC4F3 /* tab_icon_keypad@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7A18A0660300826894 /* tab_icon_keypad@2x.png */; }; + E14874ED18A06930002CC4F3 /* tab_icon_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7B18A0660300826894 /* tab_icon_menu.png */; }; + E14874EE18A06930002CC4F3 /* tab_icon_menu@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7C18A0660300826894 /* tab_icon_menu@2x.png */; }; + E14874EF18A06930002CC4F3 /* trash_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7D18A0660300826894 /* trash_icon.png */; }; + E14874F018A06930002CC4F3 /* trash_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7E18A0660300826894 /* trash_icon@2x.png */; }; + E14874F118A06930002CC4F3 /* volume_high.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B7F18A0660300826894 /* volume_high.png */; }; + E14874F218A06930002CC4F3 /* volume_high@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B8018A0660300826894 /* volume_high@2x.png */; }; + E14874F318A06930002CC4F3 /* volume_low.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B8118A0660300826894 /* volume_low.png */; }; + E14874F418A06930002CC4F3 /* volume_low@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B8218A0660300826894 /* volume_low@2x.png */; }; + E14874F518A06930002CC4F3 /* whisper_notification_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B8318A0660300826894 /* whisper_notification_icon.png */; }; + E14874F618A06930002CC4F3 /* whisper_notification_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1370B8418A0660300826894 /* whisper_notification_icon@2x.png */; }; + E14874F718A06951002CC4F3 /* CallLogTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053F18170B33006006FC /* CallLogTableViewCell.xib */; }; + E14874F818A06951002CC4F3 /* ContactTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053A18170B33006006FC /* ContactTableViewCell.xib */; }; + E14874F918A06951002CC4F3 /* CountryCodeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B97CBFB018861023008E0DE9 /* CountryCodeTableViewCell.xib */; }; + E14874FA18A06951002CC4F3 /* FavouriteTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9A578B3183D610300C17105 /* FavouriteTableViewCell.xib */; }; + E14874FB18A06951002CC4F3 /* InboxFeedFooterCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76C87F15181EE2EB00C4ACAB /* InboxFeedFooterCell.xib */; }; + E14874FC18A06951002CC4F3 /* InboxFeedTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB053C18170B33006006FC /* InboxFeedTableViewCell.xib */; }; + E14874FD18A06951002CC4F3 /* LeftSideMenuCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9B89C56185A2B7000A24465 /* LeftSideMenuCell.xib */; }; + E14874FE18A06951002CC4F3 /* PreferenceListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76D713E9182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib */; }; + E14874FF18A06951002CC4F3 /* UnseenWhisperUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9EB5AC81884D387007CBB57 /* UnseenWhisperUserCell.xib */; }; + E148750018A06966002CC4F3 /* CallAudioManagerDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051D18170B33006006FC /* CallAudioManagerDemo.xib */; }; + E148750118A06966002CC4F3 /* CallLogViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052718170B33006006FC /* CallLogViewController.xib */; }; + E148750218A06966002CC4F3 /* ContactBrowseViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051E18170B33006006FC /* ContactBrowseViewController.xib */; }; + E148750318A06966002CC4F3 /* ContactDetailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051F18170B33006006FC /* ContactDetailTableViewCell.xib */; }; + E148750418A06966002CC4F3 /* ContactDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052018170B33006006FC /* ContactDetailViewController.xib */; }; + E148750518A06966002CC4F3 /* CountryCodeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B97CBFA718860EA3008E0DE9 /* CountryCodeViewController.xib */; }; + E148750618A06966002CC4F3 /* DialerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052118170B33006006FC /* DialerViewController.xib */; }; + E148750718A06966002CC4F3 /* DowngradeCallViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052218170B33006006FC /* DowngradeCallViewController.xib */; }; + E148750818A06966002CC4F3 /* FavouritesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052318170B33006006FC /* FavouritesViewController.xib */; }; + E148750918A06966002CC4F3 /* InboxFeedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052418170B33006006FC /* InboxFeedViewController.xib */; }; + E148750A18A06966002CC4F3 /* InCallViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052518170B33006006FC /* InCallViewController.xib */; }; + E148750B18A06966002CC4F3 /* InviteContactsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9CA51B918809ACA007E204E /* InviteContactsViewController.xib */; }; + E148750C18A06966002CC4F3 /* LeftSideMenuViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052618170B33006006FC /* LeftSideMenuViewController.xib */; }; + E148750D18A06966002CC4F3 /* PreferenceListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76B8189D182C39460088060E /* PreferenceListViewController.xib */; }; + E148750E18A06966002CC4F3 /* RegisterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052918170B33006006FC /* RegisterViewController.xib */; }; + E148750F18A06966002CC4F3 /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB052A18170B33006006FC /* SettingsViewController.xib */; }; + E148751018A06966002CC4F3 /* TabBarParentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 76EB051B18170B33006006FC /* TabBarParentViewController.xib */; }; + E148751218A06AFD002CC4F3 /* HelveticaNeueLTStd-Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */; }; + E148751318A06AFD002CC4F3 /* HelveticaNeueLTStd-Th.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052B1182BF839008313E1 /* HelveticaNeueLTStd-Th.otf */; }; + E148751418A06AFD002CC4F3 /* HelveticaNeueLTStd-Lt.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052A518294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf */; }; + E148751518A06AFD002CC4F3 /* HelveticaNeueLTStd-Md.otf in Resources */ = {isa = PBXBuildFile; fileRef = 765052A618294C9F008313E1 /* HelveticaNeueLTStd-Md.otf */; }; + E16E5BEE18AAC40200B7C403 /* EC25KeyAgreementParticipant.m in Sources */ = {isa = PBXBuildFile; fileRef = E16E5BE918AAC40200B7C403 /* EC25KeyAgreementParticipant.m */; }; + E16E5BEF18AAC40200B7C403 /* EC25KeyAgreementProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = E16E5BEB18AAC40200B7C403 /* EC25KeyAgreementProtocol.m */; }; + E16E5BF018AAC40200B7C403 /* EvpKeyAgreement.m in Sources */ = {isa = PBXBuildFile; fileRef = E16E5BED18AAC40200B7C403 /* EvpKeyAgreement.m */; }; + E16E5BF918AAF02100B7C403 /* EC25AgreerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E16E5BF818AAF02100B7C403 /* EC25AgreerTest.m */; }; + E16E5C1418AEDB5A00B7C403 /* message_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E16E5C1218AEDB5A00B7C403 /* message_icon.png */; }; + E16E5C1518AEDB5A00B7C403 /* phone_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = E16E5C1318AEDB5A00B7C403 /* phone_icon.png */; }; + E19167A418A9687800B7A468 /* DH3KKeyAgreementParticipant.m in Sources */ = {isa = PBXBuildFile; fileRef = E19167A318A9687800B7A468 /* DH3KKeyAgreementParticipant.m */; }; + E197B60C18BBEC1A00F073E5 /* AudioPacker.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5E918BBEC1A00F073E5 /* AudioPacker.m */; }; + E197B60D18BBEC1A00F073E5 /* AudioSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5EB18BBEC1A00F073E5 /* AudioSocket.m */; }; + E197B60E18BBEC1A00F073E5 /* CallAudioManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5ED18BBEC1A00F073E5 /* CallAudioManager.m */; }; + E197B60F18BBEC1A00F073E5 /* EncodedAudioFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5EF18BBEC1A00F073E5 /* EncodedAudioFrame.m */; }; + E197B61018BBEC1A00F073E5 /* EncodedAudioPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5F118BBEC1A00F073E5 /* EncodedAudioPacket.m */; }; + E197B61118BBEC1A00F073E5 /* AudioProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5F418BBEC1A00F073E5 /* AudioProcessor.m */; }; + E197B61218BBEC1A00F073E5 /* AudioStretcher.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5F618BBEC1A00F073E5 /* AudioStretcher.m */; }; + E197B61318BBEC1A00F073E5 /* DesiredBufferDepthController.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5F818BBEC1A00F073E5 /* DesiredBufferDepthController.m */; }; + E197B61418BBEC1A00F073E5 /* DropoutTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5FA18BBEC1A00F073E5 /* DropoutTracker.m */; }; + E197B61518BBEC1A00F073E5 /* JitterQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5FC18BBEC1A00F073E5 /* JitterQueue.m */; }; + E197B61618BBEC1A00F073E5 /* StretchFactorController.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B5FE18BBEC1A00F073E5 /* StretchFactorController.m */; }; + E197B61718BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B60518BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m */; }; + E197B61818BBEC1A00F073E5 /* RemoteIOAudio.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B60718BBEC1A00F073E5 /* RemoteIOAudio.m */; }; + E197B61918BBEC1A00F073E5 /* RemoteIOBufferListWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B60918BBEC1A00F073E5 /* RemoteIOBufferListWrapper.m */; }; + E197B61A18BBEC1A00F073E5 /* SpeexCodec.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B60B18BBEC1A00F073E5 /* SpeexCodec.m */; }; + E197B61E18BBEC6D00F073E5 /* AudioRouter.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B61D18BBEC6D00F073E5 /* AudioRouter.m */; }; + E197B62118BBF12700F073E5 /* AppAudioManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62018BBF12700F073E5 /* AppAudioManager.m */; }; + E197B62418BBF5BB00F073E5 /* SoundPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62318BBF5BB00F073E5 /* SoundPlayer.m */; }; + E197B62718BBF63B00F073E5 /* SoundBoard.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62618BBF63B00F073E5 /* SoundBoard.m */; }; + E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = E1CD329518BCFF9900B1A496 /* SoundInstance.m */; }; + F995AC2FFD6D4442B012604A /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8313AE91B4954215858A5662 /* libPods.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 70B800A2190C529C0042E3F0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 70B8009E190C529C0042E3F0 /* spandsp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A1B989641725EC1300B6E8B5; + remoteInfo = spandsp; + }; + 70B800A4190C52F80042E3F0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 70B8009E190C529C0042E3F0 /* spandsp.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = A1B989631725EC1300B6E8B5; + remoteInfo = spandsp; + }; + 70B800AB190C54790042E3F0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 70B800A7190C54790042E3F0 /* speex.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A1FDCBFA16DBC57D00868894; + remoteInfo = speex; + }; + 70B800AD190C54870042E3F0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 70B800A7190C54790042E3F0 /* speex.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = A1FDCBF916DBC57D00868894; + remoteInfo = speex; + }; + D221A0AF169C9E5F00537ABF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D221A080169C9E5E00537ABF /* Project object */; + proxyType = 1; + remoteGlobalIDString = D221A088169C9E5E00537ABF; + remoteInfo = RedPhone; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 701231B318ECAA4500D456C4 /* EvpMessageDigest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpMessageDigest.h; sourceTree = ""; }; + 701231B418ECAA4500D456C4 /* EvpMessageDigest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EvpMessageDigest.m; sourceTree = ""; }; + 70377AA71916BA0500CAF501 /* InteractiveLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteractiveLabel.h; sourceTree = ""; }; + 70377AA81916BA0500CAF501 /* InteractiveLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteractiveLabel.m; sourceTree = ""; }; + 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 7038632318F70C0700D4A43F /* CryptoTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoTools.h; sourceTree = ""; }; + 7038632418F70C0700D4A43F /* CryptoTools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoTools.m; sourceTree = ""; }; + 7038632518F70C0700D4A43F /* EvpSymetricUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpSymetricUtil.h; sourceTree = ""; }; + 7038632618F70C0700D4A43F /* EvpSymetricUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EvpSymetricUtil.m; sourceTree = ""; }; + 707E548A18FF0B8A00C8649D /* InviteContactModal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteContactModal.h; sourceTree = ""; }; + 707E548B18FF0B8A00C8649D /* InviteContactModal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteContactModal.m; sourceTree = ""; }; + 707E549018FF26E800C8649D /* SmsInvite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmsInvite.h; sourceTree = ""; }; + 707E549118FF26E800C8649D /* SmsInvite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmsInvite.m; sourceTree = ""; }; + 7095B7AE18F46D35002C66E2 /* PhoneNumberUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumberUtil.h; sourceTree = ""; }; + 7095B7AF18F46D35002C66E2 /* PhoneNumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberUtil.m; sourceTree = ""; }; + 70B8009E190C529C0042E3F0 /* spandsp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = spandsp.xcodeproj; path = Libraries/spandsp/spandsp/spandsp.xcodeproj; sourceTree = ""; }; + 70B800A7190C54790042E3F0 /* speex.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = speex.xcodeproj; path = Libraries/speex/speex.xcodeproj; sourceTree = ""; }; + 70B800E2190C55660042E3F0 /* AbstractMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AbstractMessage.h; sourceTree = ""; }; + 70B800E3190C55660042E3F0 /* AbstractMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AbstractMessage.m; sourceTree = ""; }; + 70B800E4190C55660042E3F0 /* AbstractMessage_Builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AbstractMessage_Builder.h; sourceTree = ""; }; + 70B800E5190C55660042E3F0 /* AbstractMessage_Builder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AbstractMessage_Builder.m; sourceTree = ""; }; + 70B800E6190C55660042E3F0 /* Bootstrap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bootstrap.h; sourceTree = ""; }; + 70B800E7190C55660042E3F0 /* CodedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodedInputStream.h; sourceTree = ""; }; + 70B800E8190C55660042E3F0 /* CodedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodedInputStream.m; sourceTree = ""; }; + 70B800E9190C55660042E3F0 /* CodedOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodedOutputStream.h; sourceTree = ""; }; + 70B800EA190C55660042E3F0 /* CodedOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodedOutputStream.m; sourceTree = ""; }; + 70B800EB190C55660042E3F0 /* ConcreteExtensionField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConcreteExtensionField.h; sourceTree = ""; }; + 70B800EC190C55660042E3F0 /* ConcreteExtensionField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConcreteExtensionField.m; sourceTree = ""; }; + 70B800ED190C55660042E3F0 /* ExtendableMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtendableMessage.h; sourceTree = ""; }; + 70B800EE190C55660042E3F0 /* ExtendableMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExtendableMessage.m; sourceTree = ""; }; + 70B800EF190C55660042E3F0 /* ExtendableMessage_Builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtendableMessage_Builder.h; sourceTree = ""; }; + 70B800F0190C55660042E3F0 /* ExtendableMessage_Builder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExtendableMessage_Builder.m; sourceTree = ""; }; + 70B800F1190C55660042E3F0 /* ExtensionField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtensionField.h; sourceTree = ""; }; + 70B800F2190C55660042E3F0 /* ExtensionRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtensionRegistry.h; sourceTree = ""; }; + 70B800F3190C55660042E3F0 /* ExtensionRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExtensionRegistry.m; sourceTree = ""; }; + 70B800F4190C55660042E3F0 /* Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Field.h; sourceTree = ""; }; + 70B800F5190C55660042E3F0 /* Field.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Field.m; sourceTree = ""; }; + 70B800F6190C55660042E3F0 /* ForwardDeclarations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ForwardDeclarations.h; sourceTree = ""; }; + 70B800F7190C55660042E3F0 /* GeneratedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedMessage.h; sourceTree = ""; }; + 70B800F8190C55660042E3F0 /* GeneratedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedMessage.m; sourceTree = ""; }; + 70B800F9190C55660042E3F0 /* GeneratedMessage_Builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedMessage_Builder.h; sourceTree = ""; }; + 70B800FA190C55660042E3F0 /* GeneratedMessage_Builder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedMessage_Builder.m; sourceTree = ""; }; + 70B800FB190C55660042E3F0 /* Message.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Message.h; sourceTree = ""; }; + 70B800FC190C55660042E3F0 /* Message_Builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Message_Builder.h; sourceTree = ""; }; + 70B800FD190C55660042E3F0 /* MutableExtensionRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MutableExtensionRegistry.h; sourceTree = ""; }; + 70B800FE190C55660042E3F0 /* MutableExtensionRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MutableExtensionRegistry.m; sourceTree = ""; }; + 70B800FF190C55660042E3F0 /* MutableField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MutableField.h; sourceTree = ""; }; + 70B80100190C55660042E3F0 /* MutableField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MutableField.m; sourceTree = ""; }; + 70B80101190C55660042E3F0 /* ProtocolBuffers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProtocolBuffers.h; sourceTree = ""; }; + 70B80102190C55660042E3F0 /* TextFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextFormat.h; sourceTree = ""; }; + 70B80103190C55660042E3F0 /* TextFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextFormat.m; sourceTree = ""; }; + 70B80104190C55660042E3F0 /* UnknownFieldSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnknownFieldSet.h; sourceTree = ""; }; + 70B80105190C55660042E3F0 /* UnknownFieldSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnknownFieldSet.m; sourceTree = ""; }; + 70B80106190C55660042E3F0 /* UnknownFieldSet_Builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnknownFieldSet_Builder.h; sourceTree = ""; }; + 70B80107190C55660042E3F0 /* UnknownFieldSet_Builder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnknownFieldSet_Builder.m; sourceTree = ""; }; + 70B80108190C55660042E3F0 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 70B80109190C55660042E3F0 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; + 70B8010A190C55660042E3F0 /* WireFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WireFormat.h; sourceTree = ""; }; + 70B8010B190C55660042E3F0 /* WireFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WireFormat.m; sourceTree = ""; }; + 70B8FEE11909FE360042E3F0 /* 171756__nenadsimic__picked-coin-echo-2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "171756__nenadsimic__picked-coin-echo-2.wav"; sourceTree = ""; }; + 70BAFD5B190584BE00FA5E0B /* NotificationTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationTracker.h; sourceTree = ""; }; + 70BAFD5C190584BE00FA5E0B /* NotificationTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationTracker.m; sourceTree = ""; }; + 70E803ED18F6DD1400BF77BC /* EvpUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpUtil.h; sourceTree = ""; }; + 762D9DCD18281C7400A5E418 /* SettingsTableHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsTableHeaderView.h; sourceTree = ""; }; + 762D9DCE18281C7400A5E418 /* SettingsTableHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsTableHeaderView.m; sourceTree = ""; }; + 7650529F182945EF008313E1 /* LocalizableCustomFontLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalizableCustomFontLabel.h; sourceTree = ""; }; + 765052A0182945EF008313E1 /* LocalizableCustomFontLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalizableCustomFontLabel.m; sourceTree = ""; }; + 765052A518294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Lt.otf"; sourceTree = ""; }; + 765052A618294C9F008313E1 /* HelveticaNeueLTStd-Md.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Md.otf"; sourceTree = ""; }; + 765052AD182AC9B5008313E1 /* DialerButtonView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DialerButtonView.h; sourceTree = ""; }; + 765052AE182AC9B5008313E1 /* DialerButtonView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DialerButtonView.m; sourceTree = ""; }; + 765052B1182BF839008313E1 /* HelveticaNeueLTStd-Th.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Th.otf"; sourceTree = ""; }; + 768A1A2A17FC9CD300E00ED8 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; + 76919BF61805D190008C664A /* ContactManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactManagerTest.m; path = contact/ContactManagerTest.m; sourceTree = ""; }; + 76B8189B182C39460088060E /* PreferenceListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferenceListViewController.h; sourceTree = ""; }; + 76B8189C182C39460088060E /* PreferenceListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferenceListViewController.m; sourceTree = ""; }; + 76B8189D182C39460088060E /* PreferenceListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PreferenceListViewController.xib; path = ../PreferenceListViewController.xib; sourceTree = ""; }; + 76C87F11181EE11C00C4ACAB /* InboxFeedFooterCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InboxFeedFooterCell.h; sourceTree = ""; }; + 76C87F12181EE11C00C4ACAB /* InboxFeedFooterCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InboxFeedFooterCell.m; sourceTree = ""; }; + 76C87F15181EE2EB00C4ACAB /* InboxFeedFooterCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = InboxFeedFooterCell.xib; path = ../InboxFeedFooterCell.xib; sourceTree = ""; }; + 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; + 76D713E5182D3E3F00C9C9C8 /* PreferenceListTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferenceListTableViewCell.h; sourceTree = ""; }; + 76D713E6182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferenceListTableViewCell.m; sourceTree = ""; }; + 76D713E9182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PreferenceListTableViewCell.xib; path = ../PreferenceListTableViewCell.xib; sourceTree = ""; }; + 76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 76EB03C518170B33006006FC /* AsyncUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncUtil.h; sourceTree = ""; }; + 76EB03C618170B33006006FC /* AsyncUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncUtil.m; sourceTree = ""; }; + 76EB03C718170B33006006FC /* AsyncUtilHelperRacingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncUtilHelperRacingOperation.h; sourceTree = ""; }; + 76EB03C818170B33006006FC /* AsyncUtilHelperRacingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncUtilHelperRacingOperation.m; sourceTree = ""; }; + 76EB03C918170B33006006FC /* CancelledToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CancelledToken.h; sourceTree = ""; }; + 76EB03CA18170B33006006FC /* CancelledToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CancelledToken.m; sourceTree = ""; }; + 76EB03CB18170B33006006FC /* CancelTokenSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CancelTokenSource.h; sourceTree = ""; }; + 76EB03CC18170B33006006FC /* CancelTokenSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CancelTokenSource.m; sourceTree = ""; }; + 76EB03CD18170B33006006FC /* Future.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Future.h; sourceTree = ""; }; + 76EB03CE18170B33006006FC /* Future.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Future.m; sourceTree = ""; }; + 76EB03CF18170B33006006FC /* FutureSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FutureSource.h; sourceTree = ""; }; + 76EB03D018170B33006006FC /* FutureSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FutureSource.m; sourceTree = ""; }; + 76EB03D118170B33006006FC /* FutureUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FutureUtil.h; sourceTree = ""; }; + 76EB03D218170B33006006FC /* FutureUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FutureUtil.m; sourceTree = ""; }; + 76EB03D318170B33006006FC /* ObservableValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObservableValue.h; sourceTree = ""; }; + 76EB03D418170B33006006FC /* ObservableValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObservableValue.m; sourceTree = ""; }; + 76EB03D618170B33006006FC /* CancelToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CancelToken.h; sourceTree = ""; }; + 76EB03D718170B33006006FC /* TimeoutFailure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeoutFailure.h; sourceTree = ""; }; + 76EB03D818170B33006006FC /* TimeoutFailure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeoutFailure.m; sourceTree = ""; }; + 76EB03FF18170B33006006FC /* RecentCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecentCall.h; sourceTree = ""; }; + 76EB040018170B33006006FC /* RecentCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentCall.m; sourceTree = ""; }; + 76EB040118170B33006006FC /* RecentCallManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecentCallManager.h; sourceTree = ""; }; + 76EB040218170B33006006FC /* RecentCallManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentCallManager.m; sourceTree = ""; }; + 76EB040418170B33006006FC /* Contact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Contact.h; sourceTree = ""; }; + 76EB040518170B33006006FC /* Contact.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Contact.m; sourceTree = ""; }; + 76EB040818170B33006006FC /* ContactsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsManager.h; sourceTree = ""; }; + 76EB040918170B33006006FC /* ContactsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactsManager.m; sourceTree = ""; }; + 76EB041218170B33006006FC /* Environment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Environment.h; sourceTree = ""; }; + 76EB041318170B33006006FC /* Environment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Environment.m; sourceTree = ""; }; + 76EB041418170B33006006FC /* LocalizableText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalizableText.h; sourceTree = ""; }; + 76EB041518170B33006006FC /* LocalizableText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalizableText.m; sourceTree = ""; }; + 76EB041618170B33006006FC /* PreferencesUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesUtil.h; sourceTree = ""; }; + 76EB041718170B33006006FC /* PreferencesUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesUtil.m; sourceTree = ""; }; + 76EB041818170B33006006FC /* PropertyListPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PropertyListPreferences.h; sourceTree = ""; }; + 76EB041918170B33006006FC /* PropertyListPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PropertyListPreferences.m; sourceTree = ""; }; + 76EB041A18170B33006006FC /* Release.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Release.h; sourceTree = ""; }; + 76EB041B18170B33006006FC /* Release.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Release.m; sourceTree = ""; }; + 76EB041F18170B33006006FC /* DnsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DnsManager.h; sourceTree = ""; }; + 76EB042018170B33006006FC /* DnsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DnsManager.m; sourceTree = ""; }; + 76EB042118170B33006006FC /* HostNameEndPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HostNameEndPoint.h; sourceTree = ""; }; + 76EB042218170B33006006FC /* HostNameEndPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HostNameEndPoint.m; sourceTree = ""; }; + 76EB042418170B33006006FC /* IgnoredPacketFailure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IgnoredPacketFailure.h; sourceTree = ""; }; + 76EB042518170B33006006FC /* IgnoredPacketFailure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IgnoredPacketFailure.m; sourceTree = ""; }; + 76EB042618170B33006006FC /* UnrecognizedRequestFailure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnrecognizedRequestFailure.h; sourceTree = ""; }; + 76EB042718170B33006006FC /* UnrecognizedRequestFailure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnrecognizedRequestFailure.m; sourceTree = ""; }; + 76EB042918170B33006006FC /* HttpManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpManager.h; sourceTree = ""; }; + 76EB042A18170B33006006FC /* HttpManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpManager.m; sourceTree = ""; }; + 76EB042B18170B33006006FC /* HttpRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpRequest.h; sourceTree = ""; }; + 76EB042C18170B33006006FC /* HttpRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpRequest.m; sourceTree = ""; }; + 76EB042D18170B33006006FC /* HttpRequestOrResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpRequestOrResponse.h; sourceTree = ""; }; + 76EB042E18170B33006006FC /* HttpRequestOrResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpRequestOrResponse.m; sourceTree = ""; }; + 76EB042F18170B33006006FC /* HttpRequestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpRequestUtil.h; sourceTree = ""; }; + 76EB043018170B33006006FC /* HttpRequestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpRequestUtil.m; sourceTree = ""; }; + 76EB043118170B33006006FC /* HttpResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpResponse.h; sourceTree = ""; }; + 76EB043218170B33006006FC /* HttpResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpResponse.m; sourceTree = ""; }; + 76EB043318170B33006006FC /* HttpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpSocket.h; sourceTree = ""; }; + 76EB043418170B33006006FC /* HttpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpSocket.m; sourceTree = ""; }; + 76EB043518170B33006006FC /* IpAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IpAddress.h; sourceTree = ""; }; + 76EB043618170B33006006FC /* IpAddress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IpAddress.m; sourceTree = ""; }; + 76EB043718170B33006006FC /* IpEndPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IpEndPoint.h; sourceTree = ""; }; + 76EB043818170B33006006FC /* IpEndPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IpEndPoint.m; sourceTree = ""; }; + 76EB043918170B33006006FC /* NetworkEndPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkEndPoint.h; sourceTree = ""; }; + 76EB043A18170B33006006FC /* PacketHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketHandler.h; sourceTree = ""; }; + 76EB043B18170B33006006FC /* PacketHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PacketHandler.m; sourceTree = ""; }; + 76EB043D18170B33006006FC /* RtpPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RtpPacket.h; sourceTree = ""; }; + 76EB043E18170B33006006FC /* RtpPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RtpPacket.m; sourceTree = ""; }; + 76EB043F18170B33006006FC /* RtpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RtpSocket.h; sourceTree = ""; }; + 76EB044018170B33006006FC /* RtpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RtpSocket.m; sourceTree = ""; }; + 76EB044218170B33006006FC /* SequenceCounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SequenceCounter.h; sourceTree = ""; }; + 76EB044318170B33006006FC /* SequenceCounter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SequenceCounter.m; sourceTree = ""; }; + 76EB044418170B33006006FC /* SrtpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SrtpSocket.h; sourceTree = ""; }; + 76EB044518170B33006006FC /* SrtpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SrtpSocket.m; sourceTree = ""; }; + 76EB044618170B33006006FC /* SrtpStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SrtpStream.h; sourceTree = ""; }; + 76EB044718170B33006006FC /* SrtpStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SrtpStream.m; sourceTree = ""; }; + 76EB044C18170B33006006FC /* DH3KKeyAgreementProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DH3KKeyAgreementProtocol.h; sourceTree = ""; }; + 76EB044D18170B33006006FC /* DH3KKeyAgreementProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DH3KKeyAgreementProtocol.m; sourceTree = ""; }; + 76EB044E18170B33006006FC /* HashChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashChain.h; sourceTree = ""; }; + 76EB044F18170B33006006FC /* HashChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HashChain.m; sourceTree = ""; }; + 76EB045018170B33006006FC /* MasterSecret.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterSecret.h; sourceTree = ""; }; + 76EB045118170B33006006FC /* MasterSecret.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterSecret.m; sourceTree = ""; }; + 76EB045218170B33006006FC /* NegotiationFailed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NegotiationFailed.h; sourceTree = ""; }; + 76EB045318170B33006006FC /* NegotiationFailed.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NegotiationFailed.m; sourceTree = ""; }; + 76EB045518170B33006006FC /* CommitPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommitPacket.h; sourceTree = ""; }; + 76EB045618170B33006006FC /* CommitPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommitPacket.m; sourceTree = ""; }; + 76EB045718170B33006006FC /* ConfirmAckPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfirmAckPacket.h; sourceTree = ""; }; + 76EB045818170B33006006FC /* ConfirmAckPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConfirmAckPacket.m; sourceTree = ""; }; + 76EB045918170B33006006FC /* ConfirmPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfirmPacket.h; sourceTree = ""; }; + 76EB045A18170B33006006FC /* ConfirmPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConfirmPacket.m; sourceTree = ""; }; + 76EB045B18170B33006006FC /* DhPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DhPacket.h; sourceTree = ""; }; + 76EB045C18170B33006006FC /* DhPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DhPacket.m; sourceTree = ""; }; + 76EB045D18170B33006006FC /* DhPacketSharedSecretHashes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DhPacketSharedSecretHashes.h; sourceTree = ""; }; + 76EB045E18170B33006006FC /* DhPacketSharedSecretHashes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DhPacketSharedSecretHashes.m; sourceTree = ""; }; + 76EB045F18170B33006006FC /* HandshakePacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HandshakePacket.h; sourceTree = ""; }; + 76EB046018170B33006006FC /* HandshakePacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HandshakePacket.m; sourceTree = ""; }; + 76EB046118170B33006006FC /* HelloAckPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelloAckPacket.h; sourceTree = ""; }; + 76EB046218170B33006006FC /* HelloAckPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelloAckPacket.m; sourceTree = ""; }; + 76EB046318170B33006006FC /* HelloPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelloPacket.h; sourceTree = ""; }; + 76EB046418170B33006006FC /* HelloPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelloPacket.m; sourceTree = ""; }; + 76EB046618170B33006006FC /* KeyAgreementParticipant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyAgreementParticipant.h; sourceTree = ""; }; + 76EB046718170B33006006FC /* KeyAgreementProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyAgreementProtocol.h; sourceTree = ""; }; + 76EB046818170B33006006FC /* ZrtpRole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpRole.h; sourceTree = ""; }; + 76EB046918170B33006006FC /* RecipientUnavailable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecipientUnavailable.h; sourceTree = ""; }; + 76EB046A18170B33006006FC /* RecipientUnavailable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecipientUnavailable.m; sourceTree = ""; }; + 76EB046B18170B33006006FC /* ShortAuthenticationStringGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShortAuthenticationStringGenerator.h; sourceTree = ""; }; + 76EB046C18170B33006006FC /* ShortAuthenticationStringGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShortAuthenticationStringGenerator.m; sourceTree = ""; }; + 76EB046D18170B33006006FC /* ZrtpHandshakeResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpHandshakeResult.h; sourceTree = ""; }; + 76EB046E18170B33006006FC /* ZrtpHandshakeResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpHandshakeResult.m; sourceTree = ""; }; + 76EB046F18170B33006006FC /* ZrtpHandshakeSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpHandshakeSocket.h; sourceTree = ""; }; + 76EB047018170B33006006FC /* ZrtpHandshakeSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpHandshakeSocket.m; sourceTree = ""; }; + 76EB047118170B33006006FC /* ZrtpInitiator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpInitiator.h; sourceTree = ""; }; + 76EB047218170B33006006FC /* ZrtpInitiator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpInitiator.m; sourceTree = ""; }; + 76EB047318170B33006006FC /* ZrtpManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpManager.h; sourceTree = ""; }; + 76EB047418170B33006006FC /* ZrtpManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpManager.m; sourceTree = ""; }; + 76EB047518170B33006006FC /* ZrtpResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpResponder.h; sourceTree = ""; }; + 76EB047618170B33006006FC /* ZrtpResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpResponder.m; sourceTree = ""; }; + 76EB047818170B33006006FC /* LowLatencyCandidate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LowLatencyCandidate.h; sourceTree = ""; }; + 76EB047918170B33006006FC /* LowLatencyCandidate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LowLatencyCandidate.m; sourceTree = ""; }; + 76EB047A18170B33006006FC /* LowLatencyConnector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LowLatencyConnector.h; sourceTree = ""; }; + 76EB047B18170B33006006FC /* LowLatencyConnector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LowLatencyConnector.m; sourceTree = ""; }; + 76EB047C18170B33006006FC /* StreamPair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamPair.h; sourceTree = ""; }; + 76EB047D18170B33006006FC /* StreamPair.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamPair.m; sourceTree = ""; }; + 76EB047F18170B33006006FC /* Certificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Certificate.h; sourceTree = ""; }; + 76EB048018170B33006006FC /* Certificate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Certificate.m; sourceTree = ""; }; + 76EB048118170B33006006FC /* NetworkStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkStream.h; sourceTree = ""; }; + 76EB048218170B33006006FC /* NetworkStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkStream.m; sourceTree = ""; }; + 76EB048318170B33006006FC /* SecureEndPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecureEndPoint.h; sourceTree = ""; }; + 76EB048418170B33006006FC /* SecureEndPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecureEndPoint.m; sourceTree = ""; }; + 76EB048618170B33006006FC /* UdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UdpSocket.h; sourceTree = ""; }; + 76EB048718170B33006006FC /* UdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UdpSocket.m; sourceTree = ""; }; + 76EB048A18170B33006006FC /* CallController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallController.h; sourceTree = ""; }; + 76EB048B18170B33006006FC /* CallController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallController.m; sourceTree = ""; }; + 76EB048C18170B33006006FC /* CallFailedServerMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallFailedServerMessage.h; sourceTree = ""; }; + 76EB048D18170B33006006FC /* CallFailedServerMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallFailedServerMessage.m; sourceTree = ""; }; + 76EB048E18170B33006006FC /* CallProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallProgress.h; sourceTree = ""; }; + 76EB048F18170B33006006FC /* CallProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallProgress.m; sourceTree = ""; }; + 76EB049018170B33006006FC /* CallState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallState.h; sourceTree = ""; }; + 76EB049118170B33006006FC /* CallState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallState.m; sourceTree = ""; }; + 76EB049218170B33006006FC /* CallTermination.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallTermination.h; sourceTree = ""; }; + 76EB049318170B33006006FC /* CallTermination.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallTermination.m; sourceTree = ""; }; + 76EB049418170B33006006FC /* PhoneManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneManager.h; sourceTree = ""; }; + 76EB049518170B33006006FC /* PhoneManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneManager.m; sourceTree = ""; }; + 76EB049618170B33006006FC /* PhoneNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumber.h; sourceTree = ""; }; + 76EB049718170B33006006FC /* PhoneNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumber.m; sourceTree = ""; }; + 76EB049918170B33006006FC /* CallConnectResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConnectResult.h; sourceTree = ""; }; + 76EB049A18170B33006006FC /* CallConnectResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConnectResult.m; sourceTree = ""; }; + 76EB049B18170B33006006FC /* CallConnectUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConnectUtil.h; sourceTree = ""; }; + 76EB049C18170B33006006FC /* CallConnectUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConnectUtil.m; sourceTree = ""; }; + 76EB049D18170B33006006FC /* CallConnectUtil_Initiator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConnectUtil_Initiator.h; sourceTree = ""; }; + 76EB049E18170B33006006FC /* CallConnectUtil_Initiator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConnectUtil_Initiator.m; sourceTree = ""; }; + 76EB049F18170B33006006FC /* CallConnectUtil_Responder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConnectUtil_Responder.h; sourceTree = ""; }; + 76EB04A018170B33006006FC /* CallConnectUtil_Responder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConnectUtil_Responder.m; sourceTree = ""; }; + 76EB04A118170B33006006FC /* CallConnectUtil_Server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConnectUtil_Server.h; sourceTree = ""; }; + 76EB04A218170B33006006FC /* CallConnectUtil_Server.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConnectUtil_Server.m; sourceTree = ""; }; + 76EB04A318170B33006006FC /* InitiateSignal.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InitiateSignal.pb.h; sourceTree = ""; }; + 76EB04A418170B33006006FC /* InitiateSignal.pb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InitiateSignal.pb.m; sourceTree = ""; }; + 76EB04A518170B33006006FC /* InitiateSignal.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = InitiateSignal.proto; sourceTree = ""; }; + 76EB04A618170B33006006FC /* InitiatorSessionDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InitiatorSessionDescriptor.h; sourceTree = ""; }; + 76EB04A718170B33006006FC /* InitiatorSessionDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InitiatorSessionDescriptor.m; sourceTree = ""; }; + 76EB04A918170B33006006FC /* PhoneNumberDirectoryFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumberDirectoryFilter.h; sourceTree = ""; }; + 76EB04AA18170B33006006FC /* PhoneNumberDirectoryFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberDirectoryFilter.m; sourceTree = ""; }; + 76EB04AB18170B33006006FC /* PhoneNumberDirectoryFilterManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumberDirectoryFilterManager.h; sourceTree = ""; }; + 76EB04AC18170B33006006FC /* PhoneNumberDirectoryFilterManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberDirectoryFilterManager.m; sourceTree = ""; }; + 76EB04AD18170B33006006FC /* ResponderSessionDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResponderSessionDescriptor.h; sourceTree = ""; }; + 76EB04AE18170B33006006FC /* ResponderSessionDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResponderSessionDescriptor.m; sourceTree = ""; }; + 76EB04AF18170B33006006FC /* SignalUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalUtil.h; sourceTree = ""; }; + 76EB04B018170B33006006FC /* SignalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalUtil.m; sourceTree = ""; }; + 76EB04B218170B33006006FC /* CategorizingLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CategorizingLogger.h; sourceTree = ""; }; + 76EB04B318170B33006006FC /* CategorizingLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CategorizingLogger.m; sourceTree = ""; }; + 76EB04B418170B33006006FC /* DecayingSampleEstimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DecayingSampleEstimator.h; sourceTree = ""; }; + 76EB04B518170B33006006FC /* DecayingSampleEstimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecayingSampleEstimator.m; sourceTree = ""; }; + 76EB04B618170B33006006FC /* EventWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventWindow.h; sourceTree = ""; }; + 76EB04B718170B33006006FC /* EventWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EventWindow.m; sourceTree = ""; }; + 76EB04B818170B33006006FC /* LoggingUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoggingUtil.h; sourceTree = ""; }; + 76EB04B918170B33006006FC /* LoggingUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoggingUtil.m; sourceTree = ""; }; + 76EB04BB18170B33006006FC /* ConditionLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConditionLogger.h; sourceTree = ""; }; + 76EB04BC18170B33006006FC /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; + 76EB04BD18170B33006006FC /* OccurrenceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OccurrenceLogger.h; sourceTree = ""; }; + 76EB04BF18170B33006006FC /* AnonymousConditionLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnonymousConditionLogger.h; sourceTree = ""; }; + 76EB04C018170B33006006FC /* AnonymousConditionLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnonymousConditionLogger.m; sourceTree = ""; }; + 76EB04C118170B33006006FC /* AnonymousOccurrenceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnonymousOccurrenceLogger.h; sourceTree = ""; }; + 76EB04C218170B33006006FC /* AnonymousOccurrenceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnonymousOccurrenceLogger.m; sourceTree = ""; }; + 76EB04C318170B33006006FC /* AnonymousValueLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnonymousValueLogger.h; sourceTree = ""; }; + 76EB04C418170B33006006FC /* AnonymousValueLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnonymousValueLogger.m; sourceTree = ""; }; + 76EB04C518170B33006006FC /* DiscardingLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiscardingLog.h; sourceTree = ""; }; + 76EB04C618170B33006006FC /* DiscardingLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiscardingLog.m; sourceTree = ""; }; + 76EB04C718170B33006006FC /* ValueLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValueLogger.h; sourceTree = ""; }; + 76EB04C918170B33006006FC /* ArrayUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayUtil.h; sourceTree = ""; }; + 76EB04CA18170B33006006FC /* ArrayUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayUtil.m; sourceTree = ""; }; + 76EB04CD18170B33006006FC /* BloomFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BloomFilter.h; sourceTree = ""; }; + 76EB04CE18170B33006006FC /* BloomFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BloomFilter.m; sourceTree = ""; }; + 76EB04D018170B33006006FC /* CyclicalBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CyclicalBuffer.h; sourceTree = ""; }; + 76EB04D118170B33006006FC /* CyclicalBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CyclicalBuffer.m; sourceTree = ""; }; + 76EB04D218170B33006006FC /* PriorityQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PriorityQueue.h; sourceTree = ""; }; + 76EB04D318170B33006006FC /* PriorityQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PriorityQueue.m; sourceTree = ""; }; + 76EB04D418170B33006006FC /* Queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Queue.h; sourceTree = ""; }; + 76EB04D518170B33006006FC /* Queue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Queue.m; sourceTree = ""; }; + 76EB04D718170B33006006FC /* BadArgument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BadArgument.h; sourceTree = ""; }; + 76EB04D818170B33006006FC /* BadArgument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BadArgument.m; sourceTree = ""; }; + 76EB04D918170B33006006FC /* BadState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BadState.h; sourceTree = ""; }; + 76EB04DA18170B33006006FC /* BadState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BadState.m; sourceTree = ""; }; + 76EB04DB18170B33006006FC /* Constraints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constraints.h; sourceTree = ""; }; + 76EB04DC18170B33006006FC /* OperationFailed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OperationFailed.h; sourceTree = ""; }; + 76EB04DD18170B33006006FC /* OperationFailed.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OperationFailed.m; sourceTree = ""; }; + 76EB04DE18170B33006006FC /* SecurityFailure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecurityFailure.h; sourceTree = ""; }; + 76EB04DF18170B33006006FC /* SecurityFailure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurityFailure.m; sourceTree = ""; }; + 76EB04E018170B33006006FC /* Conversions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Conversions.h; sourceTree = ""; }; + 76EB04E118170B33006006FC /* Conversions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Conversions.m; sourceTree = ""; }; + 76EB04E218170B33006006FC /* Crc32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crc32.h; sourceTree = ""; }; + 76EB04E318170B33006006FC /* Crc32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Crc32.m; sourceTree = ""; }; + 76EB04E618170B33006006FC /* DataUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataUtil.h; sourceTree = ""; }; + 76EB04E718170B33006006FC /* DataUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataUtil.m; sourceTree = ""; }; + 76EB04E818170B33006006FC /* DictionaryUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DictionaryUtil.h; sourceTree = ""; }; + 76EB04E918170B33006006FC /* DictionaryUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictionaryUtil.m; sourceTree = ""; }; + 76EB04EA18170B33006006FC /* FunctionalUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FunctionalUtil.h; sourceTree = ""; }; + 76EB04EB18170B33006006FC /* FunctionalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtil.m; sourceTree = ""; }; + 76EB04EC18170B33006006FC /* NumberUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NumberUtil.h; sourceTree = ""; }; + 76EB04ED18170B33006006FC /* NumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NumberUtil.m; sourceTree = ""; }; + 76EB04EE18170B33006006FC /* Operation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Operation.h; sourceTree = ""; }; + 76EB04EF18170B33006006FC /* Operation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Operation.m; sourceTree = ""; }; + 76EB04F118170B33006006FC /* Terminable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Terminable.h; sourceTree = ""; }; + 76EB04F318170B33006006FC /* AnonymousTerminator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnonymousTerminator.h; sourceTree = ""; }; + 76EB04F418170B33006006FC /* AnonymousTerminator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnonymousTerminator.m; sourceTree = ""; }; + 76EB04F518170B33006006FC /* StringUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringUtil.h; sourceTree = ""; }; + 76EB04F618170B33006006FC /* StringUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringUtil.m; sourceTree = ""; }; + 76EB04F718170B33006006FC /* ThreadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadManager.h; sourceTree = ""; }; + 76EB04F818170B33006006FC /* ThreadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadManager.m; sourceTree = ""; }; + 76EB04F918170B33006006FC /* TimeUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeUtil.h; sourceTree = ""; }; + 76EB04FA18170B33006006FC /* TimeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeUtil.m; sourceTree = ""; }; + 76EB04FB18170B33006006FC /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = ""; }; + 76EB04FC18170B33006006FC /* Zid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Zid.h; sourceTree = ""; }; + 76EB04FD18170B33006006FC /* Zid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Zid.m; sourceTree = ""; }; + 76EB050118170B33006006FC /* ContactBrowseViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactBrowseViewController.h; sourceTree = ""; }; + 76EB050218170B33006006FC /* ContactBrowseViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactBrowseViewController.m; sourceTree = ""; }; + 76EB050318170B33006006FC /* ContactDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailViewController.h; sourceTree = ""; }; + 76EB050418170B33006006FC /* ContactDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactDetailViewController.m; sourceTree = ""; }; + 76EB050518170B33006006FC /* DialerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DialerViewController.h; sourceTree = ""; }; + 76EB050618170B33006006FC /* DialerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DialerViewController.m; sourceTree = ""; }; + 76EB050718170B33006006FC /* FavouritesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FavouritesViewController.h; sourceTree = ""; }; + 76EB050818170B33006006FC /* FavouritesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FavouritesViewController.m; sourceTree = ""; }; + 76EB050918170B33006006FC /* InboxFeedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InboxFeedViewController.h; sourceTree = ""; }; + 76EB050A18170B33006006FC /* InboxFeedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InboxFeedViewController.m; sourceTree = ""; }; + 76EB050B18170B33006006FC /* InCallViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InCallViewController.h; sourceTree = ""; }; + 76EB050C18170B33006006FC /* InCallViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InCallViewController.m; sourceTree = ""; }; + 76EB050D18170B33006006FC /* LeftSideMenuViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeftSideMenuViewController.h; sourceTree = ""; }; + 76EB050E18170B33006006FC /* LeftSideMenuViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeftSideMenuViewController.m; sourceTree = ""; }; + 76EB050F18170B33006006FC /* NextResponderScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NextResponderScrollView.h; sourceTree = ""; }; + 76EB051018170B33006006FC /* NextResponderScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NextResponderScrollView.m; sourceTree = ""; }; + 76EB051118170B33006006FC /* CallLogViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallLogViewController.h; sourceTree = ""; }; + 76EB051218170B33006006FC /* CallLogViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallLogViewController.m; sourceTree = ""; }; + 76EB051518170B33006006FC /* RegisterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegisterViewController.h; sourceTree = ""; }; + 76EB051618170B33006006FC /* RegisterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegisterViewController.m; sourceTree = ""; }; + 76EB051718170B33006006FC /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = ""; }; + 76EB051818170B33006006FC /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = ""; }; + 76EB051918170B33006006FC /* TabBarParentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TabBarParentViewController.h; sourceTree = ""; }; + 76EB051A18170B33006006FC /* TabBarParentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TabBarParentViewController.m; sourceTree = ""; }; + 76EB051B18170B33006006FC /* TabBarParentViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = TabBarParentViewController.xib; path = ../TabBarParentViewController.xib; sourceTree = ""; }; + 76EB051D18170B33006006FC /* CallAudioManagerDemo.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallAudioManagerDemo.xib; sourceTree = ""; }; + 76EB051E18170B33006006FC /* ContactBrowseViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactBrowseViewController.xib; sourceTree = ""; }; + 76EB051F18170B33006006FC /* ContactDetailTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactDetailTableViewCell.xib; sourceTree = ""; }; + 76EB052018170B33006006FC /* ContactDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactDetailViewController.xib; sourceTree = ""; }; + 76EB052118170B33006006FC /* DialerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DialerViewController.xib; sourceTree = ""; }; + 76EB052218170B33006006FC /* DowngradeCallViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DowngradeCallViewController.xib; sourceTree = ""; }; + 76EB052318170B33006006FC /* FavouritesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FavouritesViewController.xib; sourceTree = ""; }; + 76EB052418170B33006006FC /* InboxFeedViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InboxFeedViewController.xib; sourceTree = ""; }; + 76EB052518170B33006006FC /* InCallViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InCallViewController.xib; sourceTree = ""; }; + 76EB052618170B33006006FC /* LeftSideMenuViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LeftSideMenuViewController.xib; sourceTree = ""; }; + 76EB052718170B33006006FC /* CallLogViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallLogViewController.xib; sourceTree = ""; }; + 76EB052918170B33006006FC /* RegisterViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RegisterViewController.xib; sourceTree = ""; }; + 76EB052A18170B33006006FC /* SettingsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsViewController.xib; sourceTree = ""; }; + 76EB052C18170B33006006FC /* ContactDetailTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailTableViewCell.h; sourceTree = ""; }; + 76EB052D18170B33006006FC /* ContactDetailTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactDetailTableViewCell.m; sourceTree = ""; }; + 76EB052E18170B33006006FC /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = ""; }; + 76EB052F18170B33006006FC /* ContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactTableViewCell.m; sourceTree = ""; }; + 76EB053418170B33006006FC /* InboxFeedTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InboxFeedTableViewCell.h; sourceTree = ""; }; + 76EB053518170B33006006FC /* InboxFeedTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InboxFeedTableViewCell.m; sourceTree = ""; }; + 76EB053618170B33006006FC /* CallLogTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallLogTableViewCell.h; sourceTree = ""; }; + 76EB053718170B33006006FC /* CallLogTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallLogTableViewCell.m; sourceTree = ""; }; + 76EB053A18170B33006006FC /* ContactTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactTableViewCell.xib; sourceTree = ""; }; + 76EB053C18170B33006006FC /* InboxFeedTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InboxFeedTableViewCell.xib; sourceTree = ""; }; + 76EB053F18170B33006006FC /* CallLogTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallLogTableViewCell.xib; sourceTree = ""; }; + 8313AE91B4954215858A5662 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + A15706EC17F0CD6D007C2BD6 /* AsyncUtilTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncUtilTest.h; sourceTree = ""; }; + A15706ED17F0CD6D007C2BD6 /* AsyncUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncUtilTest.m; sourceTree = ""; }; + A15706EE17F0CD6D007C2BD6 /* FutureSourceTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FutureSourceTest.h; sourceTree = ""; }; + A15706EF17F0CD6D007C2BD6 /* FutureSourceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FutureSourceTest.m; sourceTree = ""; }; + A15706F017F0CD6D007C2BD6 /* ObservableTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObservableTest.h; sourceTree = ""; }; + A15706F117F0CD6D007C2BD6 /* ObservableTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObservableTest.m; sourceTree = ""; }; + A15706F317F0CD6D007C2BD6 /* AudioFrameTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioFrameTest.h; sourceTree = ""; }; + A15706F417F0CD6D007C2BD6 /* AudioFrameTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioFrameTest.m; sourceTree = ""; }; + A15706F517F0CD6D007C2BD6 /* AudioRemoteIOTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioRemoteIOTest.h; sourceTree = ""; }; + A15706F617F0CD6D007C2BD6 /* AudioRemoteIOTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioRemoteIOTest.m; sourceTree = ""; }; + A15706F717F0CD6D007C2BD6 /* AudioStretcherTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStretcherTest.h; sourceTree = ""; }; + A15706F817F0CD6D007C2BD6 /* AudioStretcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioStretcherTest.m; sourceTree = ""; }; + A15706F917F0CD6D007C2BD6 /* JitterQueueTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitterQueueTest.h; sourceTree = ""; }; + A15706FA17F0CD6D007C2BD6 /* JitterQueueTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitterQueueTest.m; sourceTree = ""; }; + A15706FB17F0CD6D007C2BD6 /* SpeexCodecTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpeexCodecTest.h; sourceTree = ""; }; + A15706FC17F0CD6D007C2BD6 /* SpeexCodecTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpeexCodecTest.m; sourceTree = ""; }; + A157070217F0CD6D007C2BD6 /* DnsManagerTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DnsManagerTest.h; sourceTree = ""; }; + A157070317F0CD6D007C2BD6 /* DnsManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DnsManagerTest.m; sourceTree = ""; }; + A157070517F0CD6D007C2BD6 /* HttpRequestResponseTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpRequestResponseTest.h; sourceTree = ""; }; + A157070617F0CD6D007C2BD6 /* HttpRequestResponseTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HttpRequestResponseTest.m; sourceTree = ""; }; + A157070717F0CD6D007C2BD6 /* IpAddressTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IpAddressTest.h; sourceTree = ""; }; + A157070817F0CD6D007C2BD6 /* IpAddressTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IpAddressTest.m; sourceTree = ""; }; + A157070917F0CD6D007C2BD6 /* IpEndPointTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IpEndPointTest.h; sourceTree = ""; }; + A157070A17F0CD6D007C2BD6 /* IpEndPointTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IpEndPointTest.m; sourceTree = ""; }; + A157070C17F0CD6D007C2BD6 /* RtpPacketTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RtpPacketTests.h; sourceTree = ""; }; + A157070D17F0CD6D007C2BD6 /* RtpPacketTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RtpPacketTests.m; sourceTree = ""; }; + A157070F17F0CD6D007C2BD6 /* SecureStreamTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecureStreamTest.h; sourceTree = ""; }; + A157071017F0CD6D007C2BD6 /* SecureStreamTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecureStreamTest.m; sourceTree = ""; }; + A157071117F0CD6D007C2BD6 /* SequenceCounterTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SequenceCounterTest.h; sourceTree = ""; }; + A157071217F0CD6D007C2BD6 /* SequenceCounterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SequenceCounterTest.m; sourceTree = ""; }; + A157071417F0CD6D007C2BD6 /* DH3KAgreerTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DH3KAgreerTest.h; sourceTree = ""; }; + A157071517F0CD6D007C2BD6 /* DH3KAgreerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DH3KAgreerTest.m; sourceTree = ""; }; + A157071617F0CD6D007C2BD6 /* HandshakePacketTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HandshakePacketTest.h; sourceTree = ""; }; + A157071717F0CD6D007C2BD6 /* HandshakePacketTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HandshakePacketTest.m; sourceTree = ""; }; + A157071817F0CD6D007C2BD6 /* HashChainTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashChainTest.h; sourceTree = ""; }; + A157071917F0CD6D007C2BD6 /* HashChainTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HashChainTest.m; sourceTree = ""; }; + A157071A17F0CD6D007C2BD6 /* MasterSecretTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterSecretTest.h; sourceTree = ""; }; + A157071B17F0CD6D007C2BD6 /* MasterSecretTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterSecretTest.m; sourceTree = ""; }; + A157071C17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShortAuthenticationStringGeneratorTest.h; sourceTree = ""; }; + A157071D17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShortAuthenticationStringGeneratorTest.m; sourceTree = ""; }; + A157071F17F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PregeneratedKeyAgreementParticipantProtocol.h; sourceTree = ""; }; + A157072017F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PregeneratedKeyAgreementParticipantProtocol.m; sourceTree = ""; }; + A157072117F0CD6D007C2BD6 /* ZrtpTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpTest.h; sourceTree = ""; }; + A157072217F0CD6D007C2BD6 /* ZrtpTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpTest.m; sourceTree = ""; }; + A157072417F0CD6D007C2BD6 /* LowLatencyConnectorTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LowLatencyConnectorTest.h; sourceTree = ""; }; + A157072517F0CD6D007C2BD6 /* LowLatencyConnectorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LowLatencyConnectorTest.m; sourceTree = ""; }; + A157072717F0CD6D007C2BD6 /* NetworkStreamTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkStreamTest.h; sourceTree = ""; }; + A157072817F0CD6D007C2BD6 /* NetworkStreamTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkStreamTest.m; sourceTree = ""; }; + A157072917F0CD6D007C2BD6 /* SecureEndPointTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecureEndPointTest.h; sourceTree = ""; }; + A157072A17F0CD6D007C2BD6 /* SecureEndPointTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecureEndPointTest.m; sourceTree = ""; }; + A157072C17F0CD6D007C2BD6 /* UdpSocketTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UdpSocketTest.h; sourceTree = ""; }; + A157072D17F0CD6D007C2BD6 /* UdpSocketTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UdpSocketTest.m; sourceTree = ""; }; + A157072F17F0CD6D007C2BD6 /* PhoneNumberTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumberTest.h; sourceTree = ""; }; + A157073017F0CD6D007C2BD6 /* PhoneNumberTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberTest.m; sourceTree = ""; }; + A157073217F0CD6D007C2BD6 /* SessionDescriptorTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SessionDescriptorTest.h; sourceTree = ""; }; + A157073317F0CD6D007C2BD6 /* SessionDescriptorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SessionDescriptorTest.m; sourceTree = ""; }; + A157073517F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DecayingSampleEstimatorTest.h; sourceTree = ""; }; + A157073617F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecayingSampleEstimatorTest.m; sourceTree = ""; }; + A157073717F0CD6D007C2BD6 /* EventWindowTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventWindowTest.h; sourceTree = ""; }; + A157073817F0CD6D007C2BD6 /* EventWindowTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EventWindowTest.m; sourceTree = ""; }; + A157073A17F0CD6D007C2BD6 /* SignalTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SignalTests-Info.plist"; sourceTree = ""; }; + A157073B17F0CD6D007C2BD6 /* TestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtil.h; sourceTree = ""; }; + A157073C17F0CD6D007C2BD6 /* TestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestUtil.m; sourceTree = ""; }; + A157073E17F0CD6D007C2BD6 /* BloomFilterTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BloomFilterTest.h; sourceTree = ""; }; + A157073F17F0CD6D007C2BD6 /* BloomFilterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BloomFilterTest.m; sourceTree = ""; }; + A157074017F0CD6D007C2BD6 /* CancelTokenTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CancelTokenTest.h; sourceTree = ""; }; + A157074117F0CD6D007C2BD6 /* CancelTokenTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CancelTokenTest.m; sourceTree = ""; }; + A157074217F0CD6D007C2BD6 /* ConversionsTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversionsTest.h; sourceTree = ""; }; + A157074317F0CD6D007C2BD6 /* ConversionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversionsTest.m; sourceTree = ""; }; + A157074417F0CD6D007C2BD6 /* Crc32Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crc32Test.h; sourceTree = ""; }; + A157074517F0CD6D007C2BD6 /* Crc32Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Crc32Test.m; sourceTree = ""; }; + A157074617F0CD6D007C2BD6 /* CryptoUtilTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoUtilTest.h; sourceTree = ""; }; + A157074717F0CD6D007C2BD6 /* CryptoUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoUtilTest.m; sourceTree = ""; }; + A157074817F0CD6D007C2BD6 /* CyclicalBufferTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CyclicalBufferTest.h; sourceTree = ""; }; + A157074917F0CD6D007C2BD6 /* CyclicalBufferTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CyclicalBufferTest.m; sourceTree = ""; }; + A157074A17F0CD6D007C2BD6 /* ExceptionsTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionsTest.h; sourceTree = ""; }; + A157074B17F0CD6D007C2BD6 /* ExceptionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionsTest.m; sourceTree = ""; }; + A157074C17F0CD6D007C2BD6 /* FunctionalUtilTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FunctionalUtilTest.h; sourceTree = ""; }; + A157074D17F0CD6D007C2BD6 /* FunctionalUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtilTest.m; sourceTree = ""; }; + A157074E17F0CD6D007C2BD6 /* PriorityQueueTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PriorityQueueTest.h; sourceTree = ""; }; + A157074F17F0CD6D007C2BD6 /* PriorityQueueTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PriorityQueueTest.m; sourceTree = ""; }; + A157075017F0CD6D007C2BD6 /* QueueTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QueueTest.h; sourceTree = ""; }; + A157075117F0CD6D007C2BD6 /* QueueTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueueTest.m; sourceTree = ""; }; + A157075217F0CD6D007C2BD6 /* UtilTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UtilTest.h; sourceTree = ""; }; + A157075317F0CD6D007C2BD6 /* UtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UtilTest.m; sourceTree = ""; }; + A163E8AA16F3F6A90094D68B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + A1C32D4D17A0652C000A904E /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + A1C32D4F17A06537000A904E /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; + A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + B657DDC91911A40500F45B0C /* Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Signal.entitlements; sourceTree = ""; }; + B67EBF5C19194AC60084CCFD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = SettingsBundle/Settings.bundle; sourceTree = SOURCE_ROOT; }; + B68DF7B51918D7FC00C7BAB9 /* KeyChainStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeyChainStorage.h; path = ../environment/KeyChainStorage.h; sourceTree = ""; }; + B68DF7B61918D7FC00C7BAB9 /* KeyChainStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KeyChainStorage.m; path = ../environment/KeyChainStorage.m; sourceTree = ""; }; + B6B6C3C61919440C00C0B76B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + B6B6C3C81919441D00C0B76B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + B6B6C3C91919448900C0B76B /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + B6B6C3CA191944A500C0B76B /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + B6B6C3CB191944EE00C0B76B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + B6B6C3CC1919454200C0B76B /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + B6B6C3CD1919455400C0B76B /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + B6B6C3CE1919456C00C0B76B /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/Localizable.strings"; sourceTree = ""; }; + B6BAB53D1918CA4100E4DF53 /* KeychainWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeychainWrapper.h; path = ../storage/KeychainWrapper.h; sourceTree = ""; }; + B6BAB53E1918CA4100E4DF53 /* KeychainWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KeychainWrapper.m; path = ../storage/KeychainWrapper.m; sourceTree = ""; }; + B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; + B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; + B942EB0C183A9633000887BB /* SearchBarTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchBarTitleView.h; sourceTree = ""; }; + B942EB0D183A9633000887BB /* SearchBarTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchBarTitleView.m; sourceTree = ""; }; + B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Bd.otf"; sourceTree = ""; }; + B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = ""; }; + B97940261832BD2400BD66CB /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIUtil.m; sourceTree = ""; }; + B97CBFA518860EA3008E0DE9 /* CountryCodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryCodeViewController.h; sourceTree = ""; }; + B97CBFA618860EA3008E0DE9 /* CountryCodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryCodeViewController.m; sourceTree = ""; }; + B97CBFA718860EA3008E0DE9 /* CountryCodeViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CountryCodeViewController.xib; path = ../CountryCodeViewController.xib; sourceTree = ""; }; + B97CBFAC1886100E008E0DE9 /* CountryCodeTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryCodeTableViewCell.h; sourceTree = ""; }; + B97CBFAD1886100E008E0DE9 /* CountryCodeTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryCodeTableViewCell.m; sourceTree = ""; }; + B97CBFB018861023008E0DE9 /* CountryCodeTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CountryCodeTableViewCell.xib; sourceTree = ""; }; + B9A578AF183D60ED00C17105 /* FavouriteTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FavouriteTableViewCell.h; sourceTree = ""; }; + B9A578B0183D60ED00C17105 /* FavouriteTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FavouriteTableViewCell.m; sourceTree = ""; }; + B9A578B3183D610300C17105 /* FavouriteTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FavouriteTableViewCell.xib; sourceTree = ""; }; + B9B89C52185A2B5F00A24465 /* LeftSideMenuCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeftSideMenuCell.h; sourceTree = ""; }; + B9B89C53185A2B5F00A24465 /* LeftSideMenuCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeftSideMenuCell.m; sourceTree = ""; }; + B9B89C56185A2B7000A24465 /* LeftSideMenuCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LeftSideMenuCell.xib; sourceTree = ""; }; + B9CA51B718809ACA007E204E /* InviteContactsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteContactsViewController.h; sourceTree = ""; }; + B9CA51B818809ACA007E204E /* InviteContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteContactsViewController.m; sourceTree = ""; }; + B9CA51B918809ACA007E204E /* InviteContactsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = InviteContactsViewController.xib; path = ../InviteContactsViewController.xib; sourceTree = ""; }; + B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; + B9EB5AC41884D370007CBB57 /* UnseenWhisperUserCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnseenWhisperUserCell.h; sourceTree = ""; }; + B9EB5AC51884D370007CBB57 /* UnseenWhisperUserCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnseenWhisperUserCell.m; sourceTree = ""; }; + B9EB5AC81884D387007CBB57 /* UnseenWhisperUserCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UnseenWhisperUserCell.xib; sourceTree = ""; }; + C71793B33D9C45079F74487E /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; + D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; + D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + D221A089169C9E5E00537ABF /* Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Signal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D221A08D169C9E5E00537ABF /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + D221A08F169C9E5E00537ABF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D221A091169C9E5E00537ABF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + D221A095169C9E5E00537ABF /* Signal-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Signal-Info.plist"; sourceTree = ""; }; + D221A099169C9E5E00537ABF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + D221A09B169C9E5E00537ABF /* Signal-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Prefix.pch"; sourceTree = ""; }; + D221A0AA169C9E5F00537ABF /* SignalTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SignalTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + D221A0AB169C9E5F00537ABF /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + D221A0E7169DFFC500537ABF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = ""; }; + D24B5BD4169F568C00681372 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = ../../../../../../System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; + D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + E108ED12187E34FD0045AEA3 /* iTunesArtwork.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iTunesArtwork.png; sourceTree = ""; }; + E108ED13187E34FD0045AEA3 /* iTunesArtwork@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@2x.png"; sourceTree = ""; }; + E1370B3018A0660300826894 /* archive_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = archive_icon.png; sourceTree = ""; }; + E1370B3118A0660300826894 /* archive_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "archive_icon@2x.png"; sourceTree = ""; }; + E1370B3218A0660300826894 /* backspace.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backspace.png; sourceTree = ""; }; + E1370B3318A0660300826894 /* backspace@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "backspace@2x.png"; sourceTree = ""; }; + E1370B3418A0660300826894 /* checkbox_checkmark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = checkbox_checkmark.png; sourceTree = ""; }; + E1370B3518A0660300826894 /* checkbox_checkmark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checkbox_checkmark@2x.png"; sourceTree = ""; }; + E1370B3618A0660300826894 /* checkbox_empty.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = checkbox_empty.png; sourceTree = ""; }; + E1370B3718A0660300826894 /* checkbox_empty@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checkbox_empty@2x.png"; sourceTree = ""; }; + E1370B3818A0660300826894 /* contact_default_feed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = contact_default_feed.png; sourceTree = ""; }; + E1370B3918A0660300826894 /* contacts_arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = contacts_arrow.png; sourceTree = ""; }; + E1370B3A18A0660300826894 /* contacts_arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "contacts_arrow@2x.png"; sourceTree = ""; }; + E1370B3B18A0660300826894 /* DefaultContactImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DefaultContactImage.png; sourceTree = ""; }; + E1370B3C18A0660300826894 /* dismiss_notification_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = dismiss_notification_icon.png; sourceTree = ""; }; + E1370B3D18A0660300826894 /* dismiss_notification_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dismiss_notification_icon@2x.png"; sourceTree = ""; }; + E1370B3E18A0660300826894 /* drop_down_arrow_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = drop_down_arrow_icon.png; sourceTree = ""; }; + E1370B3F18A0660300826894 /* drop_down_arrow_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "drop_down_arrow_icon@2x.png"; sourceTree = ""; }; + E1370B4018A0660300826894 /* expanded_cell_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = expanded_cell_icon.png; sourceTree = ""; }; + E1370B4118A0660300826894 /* expanded_cell_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "expanded_cell_icon@2x.png"; sourceTree = ""; }; + E1370B4218A0660300826894 /* favourite_false_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = favourite_false_icon.png; sourceTree = ""; }; + E1370B4318A0660300826894 /* favourite_false_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "favourite_false_icon@2x.png"; sourceTree = ""; }; + E1370B4418A0660300826894 /* favourite_true_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = favourite_true_icon.png; sourceTree = ""; }; + E1370B4518A0660300826894 /* favourite_true_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "favourite_true_icon@2x.png"; sourceTree = ""; }; + E1370B4618A0660300826894 /* forward_button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = forward_button.png; sourceTree = ""; }; + E1370B4718A0660300826894 /* forward_button@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "forward_button@2x.png"; sourceTree = ""; }; + E1370B4818A0660300826894 /* home_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = home_icon.png; sourceTree = ""; }; + E1370B4918A0660300826894 /* icon_contacts.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_contacts.png; sourceTree = ""; }; + E1370B4A18A0660300826894 /* icon_favourites.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_favourites.png; sourceTree = ""; }; + E1370B4B18A0660300826894 /* icon_keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_keypad.png; sourceTree = ""; }; + E1370B4C18A0660300826894 /* icon_recents.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_recents.png; sourceTree = ""; }; + E1370B4D18A0660300826894 /* in_call_phone_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = in_call_phone_icon.png; sourceTree = ""; }; + E1370B4E18A0660300826894 /* in_call_phone_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "in_call_phone_icon@2x.png"; sourceTree = ""; }; + E1370B4F18A0660300826894 /* in_call_phrase_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = in_call_phrase_icon.png; sourceTree = ""; }; + E1370B5018A0660300826894 /* in_call_phrase_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "in_call_phrase_icon@2x.png"; sourceTree = ""; }; + E1370B5118A0660300826894 /* incoming_call_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = incoming_call_icon.png; sourceTree = ""; }; + E1370B5218A0660300826894 /* incoming_call_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "incoming_call_icon@2x.png"; sourceTree = ""; }; + E1370B5318A0660300826894 /* menu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icon.png; sourceTree = ""; }; + E1370B5418A0660300826894 /* menu_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icon@2x.png"; sourceTree = ""; }; + E1370B5518A0660300826894 /* message_bubble.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = message_bubble.png; sourceTree = ""; }; + E1370B5618A0660300826894 /* message_bubble@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "message_bubble@2x.png"; sourceTree = ""; }; + E1370B5718A0660300826894 /* mute_icon_selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mute_icon_selected.png; sourceTree = ""; }; + E1370B5818A0660300826894 /* mute_icon_selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "mute_icon_selected@2x.png"; sourceTree = ""; }; + E1370B5918A0660300826894 /* mute_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mute_icon.png; sourceTree = ""; }; + E1370B5A18A0660300826894 /* mute_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "mute_icon@2x.png"; sourceTree = ""; }; + E1370B5B18A0660300826894 /* notification_detail_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = notification_detail_icon.png; sourceTree = ""; }; + E1370B5C18A0660300826894 /* notification_detail_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "notification_detail_icon@2x.png"; sourceTree = ""; }; + E1370B5D18A0660300826894 /* notification_mini_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = notification_mini_icon.png; sourceTree = ""; }; + E1370B5E18A0660300826894 /* notification_mini_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "notification_mini_icon@2x.png"; sourceTree = ""; }; + E1370B5F18A0660300826894 /* outgoing_call_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = outgoing_call_icon.png; sourceTree = ""; }; + E1370B6018A0660300826894 /* outgoing_call_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "outgoing_call_icon@2x.png"; sourceTree = ""; }; + E1370B6118A0660300826894 /* search_cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = search_cancel.png; sourceTree = ""; }; + E1370B6218A0660300826894 /* search_cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "search_cancel@2x.png"; sourceTree = ""; }; + E1370B6318A0660300826894 /* search_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = search_icon.png; sourceTree = ""; }; + E1370B6418A0660300826894 /* search_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "search_icon@2x.png"; sourceTree = ""; }; + E1370B6518A0660300826894 /* send_code_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = send_code_icon.png; sourceTree = ""; }; + E1370B6618A0660300826894 /* send_code_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "send_code_icon@2x.png"; sourceTree = ""; }; + E1370B6718A0660300826894 /* speaker_icon_selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = speaker_icon_selected.png; sourceTree = ""; }; + E1370B6818A0660300826894 /* speaker_icon_selected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "speaker_icon_selected@2x.png"; sourceTree = ""; }; + E1370B6918A0660300826894 /* speaker_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = speaker_icon.png; sourceTree = ""; }; + E1370B6A18A0660300826894 /* speaker_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "speaker_icon@2x.png"; sourceTree = ""; }; + E1370B6B18A0660300826894 /* spinner_connecting_flash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spinner_connecting_flash.png; sourceTree = ""; }; + E1370B6C18A0660300826894 /* spinner_connecting_flash@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spinner_connecting_flash@2x.png"; sourceTree = ""; }; + E1370B6D18A0660300826894 /* spinner_connecting.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spinner_connecting.png; sourceTree = ""; }; + E1370B6E18A0660300826894 /* spinner_connecting@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spinner_connecting@2x.png"; sourceTree = ""; }; + E1370B6F18A0660300826894 /* spinner_error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spinner_error.png; sourceTree = ""; }; + E1370B7018A0660300826894 /* spinner_error@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spinner_error@2x.png"; sourceTree = ""; }; + E1370B7118A0660300826894 /* spinner_ringing.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spinner_ringing.png; sourceTree = ""; }; + E1370B7218A0660300826894 /* spinner_ringing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spinner_ringing@2x.png"; sourceTree = ""; }; + E1370B7318A0660300826894 /* tab_icon_contacts.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_icon_contacts.png; sourceTree = ""; }; + E1370B7418A0660300826894 /* tab_icon_contacts@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_icon_contacts@2x.png"; sourceTree = ""; }; + E1370B7518A0660300826894 /* tab_icon_favourites.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_icon_favourites.png; sourceTree = ""; }; + E1370B7618A0660300826894 /* tab_icon_favourites@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_icon_favourites@2x.png"; sourceTree = ""; }; + E1370B7718A0660300826894 /* tab_icon_inbox.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_icon_inbox.png; sourceTree = ""; }; + E1370B7818A0660300826894 /* tab_icon_inbox@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_icon_inbox@2x.png"; sourceTree = ""; }; + E1370B7918A0660300826894 /* tab_icon_keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_icon_keypad.png; sourceTree = ""; }; + E1370B7A18A0660300826894 /* tab_icon_keypad@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_icon_keypad@2x.png"; sourceTree = ""; }; + E1370B7B18A0660300826894 /* tab_icon_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_icon_menu.png; sourceTree = ""; }; + E1370B7C18A0660300826894 /* tab_icon_menu@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_icon_menu@2x.png"; sourceTree = ""; }; + E1370B7D18A0660300826894 /* trash_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = trash_icon.png; sourceTree = ""; }; + E1370B7E18A0660300826894 /* trash_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "trash_icon@2x.png"; sourceTree = ""; }; + E1370B7F18A0660300826894 /* volume_high.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_high.png; sourceTree = ""; }; + E1370B8018A0660300826894 /* volume_high@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "volume_high@2x.png"; sourceTree = ""; }; + E1370B8118A0660300826894 /* volume_low.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_low.png; sourceTree = ""; }; + E1370B8218A0660300826894 /* volume_low@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "volume_low@2x.png"; sourceTree = ""; }; + E1370B8318A0660300826894 /* whisper_notification_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = whisper_notification_icon.png; sourceTree = ""; }; + E1370B8418A0660300826894 /* whisper_notification_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "whisper_notification_icon@2x.png"; sourceTree = ""; }; + E1370BDA18A066F600826894 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + E1370BDB18A066F600826894 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; + E1370BDC18A066F600826894 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; + E16E5BE818AAC40200B7C403 /* EC25KeyAgreementParticipant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EC25KeyAgreementParticipant.h; sourceTree = ""; }; + E16E5BE918AAC40200B7C403 /* EC25KeyAgreementParticipant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EC25KeyAgreementParticipant.m; sourceTree = ""; }; + E16E5BEA18AAC40200B7C403 /* EC25KeyAgreementProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EC25KeyAgreementProtocol.h; sourceTree = ""; }; + E16E5BEB18AAC40200B7C403 /* EC25KeyAgreementProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EC25KeyAgreementProtocol.m; sourceTree = ""; }; + E16E5BEC18AAC40200B7C403 /* EvpKeyAgreement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvpKeyAgreement.h; sourceTree = ""; }; + E16E5BED18AAC40200B7C403 /* EvpKeyAgreement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EvpKeyAgreement.m; sourceTree = ""; }; + E16E5BF818AAF02100B7C403 /* EC25AgreerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EC25AgreerTest.m; sourceTree = ""; }; + E16E5C1218AEDB5A00B7C403 /* message_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = message_icon.png; sourceTree = ""; }; + E16E5C1318AEDB5A00B7C403 /* phone_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone_icon.png; sourceTree = ""; }; + E18AB3F418A05734001A532A /* AppIcon29x29.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = AppIcon29x29.jpg; sourceTree = ""; }; + E18AB3F518A05734001A532A /* AppIcon29x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon29x29.png; sourceTree = ""; }; + E18AB3F618A05734001A532A /* AppIcon29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon29x29@2x.png"; sourceTree = ""; }; + E18AB3F718A05734001A532A /* AppIcon40x40.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon40x40.png; sourceTree = ""; }; + E18AB3F818A05734001A532A /* AppIcon40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon40x40@2x.png"; sourceTree = ""; }; + E18AB3F918A05734001A532A /* AppIcon60x60.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon60x60.png; sourceTree = ""; }; + E18AB3FA18A05734001A532A /* AppIcon60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon60x60@2x.png"; sourceTree = ""; }; + E18AB3FB18A05734001A532A /* AppIcon76x76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon76x76.png; sourceTree = ""; }; + E18AB3FC18A05734001A532A /* AppIcon76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon76x76@2x.png"; sourceTree = ""; }; + E18AB40718A05754001A532A /* busy.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = busy.mp3; sourceTree = ""; }; + E18AB40818A05754001A532A /* completed.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = completed.mp3; sourceTree = ""; }; + E18AB40918A05754001A532A /* failure.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = failure.mp3; sourceTree = ""; }; + E18AB40A18A05754001A532A /* handshake.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = handshake.mp3; sourceTree = ""; }; + E18AB40B18A05754001A532A /* outring.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = outring.mp3; sourceTree = ""; }; + E18AB40C18A05754001A532A /* r.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = r.caf; sourceTree = ""; }; + E18AB40D18A05754001A532A /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = sonarping.mp3; sourceTree = ""; }; + E19167A218A9687800B7A468 /* DH3KKeyAgreementParticipant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DH3KKeyAgreementParticipant.h; sourceTree = ""; }; + E19167A318A9687800B7A468 /* DH3KKeyAgreementParticipant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DH3KKeyAgreementParticipant.m; sourceTree = ""; }; + E197B5E818BBEC1A00F073E5 /* AudioPacker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioPacker.h; sourceTree = ""; }; + E197B5E918BBEC1A00F073E5 /* AudioPacker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioPacker.m; sourceTree = ""; }; + E197B5EA18BBEC1A00F073E5 /* AudioSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioSocket.h; sourceTree = ""; }; + E197B5EB18BBEC1A00F073E5 /* AudioSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioSocket.m; sourceTree = ""; }; + E197B5EC18BBEC1A00F073E5 /* CallAudioManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallAudioManager.h; sourceTree = ""; }; + E197B5ED18BBEC1A00F073E5 /* CallAudioManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallAudioManager.m; sourceTree = ""; }; + E197B5EE18BBEC1A00F073E5 /* EncodedAudioFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncodedAudioFrame.h; sourceTree = ""; }; + E197B5EF18BBEC1A00F073E5 /* EncodedAudioFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EncodedAudioFrame.m; sourceTree = ""; }; + E197B5F018BBEC1A00F073E5 /* EncodedAudioPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncodedAudioPacket.h; sourceTree = ""; }; + E197B5F118BBEC1A00F073E5 /* EncodedAudioPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EncodedAudioPacket.m; sourceTree = ""; }; + E197B5F318BBEC1A00F073E5 /* AudioProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioProcessor.h; sourceTree = ""; }; + E197B5F418BBEC1A00F073E5 /* AudioProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioProcessor.m; sourceTree = ""; }; + E197B5F518BBEC1A00F073E5 /* AudioStretcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStretcher.h; sourceTree = ""; }; + E197B5F618BBEC1A00F073E5 /* AudioStretcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioStretcher.m; sourceTree = ""; }; + E197B5F718BBEC1A00F073E5 /* DesiredBufferDepthController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DesiredBufferDepthController.h; sourceTree = ""; }; + E197B5F818BBEC1A00F073E5 /* DesiredBufferDepthController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DesiredBufferDepthController.m; sourceTree = ""; }; + E197B5F918BBEC1A00F073E5 /* DropoutTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropoutTracker.h; sourceTree = ""; }; + E197B5FA18BBEC1A00F073E5 /* DropoutTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropoutTracker.m; sourceTree = ""; }; + E197B5FB18BBEC1A00F073E5 /* JitterQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitterQueue.h; sourceTree = ""; }; + E197B5FC18BBEC1A00F073E5 /* JitterQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitterQueue.m; sourceTree = ""; }; + E197B5FD18BBEC1A00F073E5 /* StretchFactorController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StretchFactorController.h; sourceTree = ""; }; + E197B5FE18BBEC1A00F073E5 /* StretchFactorController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StretchFactorController.m; sourceTree = ""; }; + E197B60018BBEC1A00F073E5 /* AudioCallbackHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioCallbackHandler.h; sourceTree = ""; }; + E197B60118BBEC1A00F073E5 /* BufferDepthMeasure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferDepthMeasure.h; sourceTree = ""; }; + E197B60218BBEC1A00F073E5 /* JitterQueueNotificationReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitterQueueNotificationReceiver.h; sourceTree = ""; }; + E197B60418BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnonymousAudioCallbackHandler.h; sourceTree = ""; }; + E197B60518BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnonymousAudioCallbackHandler.m; sourceTree = ""; }; + E197B60618BBEC1A00F073E5 /* RemoteIOAudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteIOAudio.h; sourceTree = ""; }; + E197B60718BBEC1A00F073E5 /* RemoteIOAudio.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteIOAudio.m; sourceTree = ""; }; + E197B60818BBEC1A00F073E5 /* RemoteIOBufferListWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteIOBufferListWrapper.h; sourceTree = ""; }; + E197B60918BBEC1A00F073E5 /* RemoteIOBufferListWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteIOBufferListWrapper.m; sourceTree = ""; }; + E197B60A18BBEC1A00F073E5 /* SpeexCodec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpeexCodec.h; sourceTree = ""; }; + E197B60B18BBEC1A00F073E5 /* SpeexCodec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpeexCodec.m; sourceTree = ""; }; + E197B61C18BBEC6D00F073E5 /* AudioRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioRouter.h; sourceTree = ""; }; + E197B61D18BBEC6D00F073E5 /* AudioRouter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioRouter.m; sourceTree = ""; }; + E197B61F18BBF12700F073E5 /* AppAudioManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppAudioManager.h; sourceTree = ""; }; + E197B62018BBF12700F073E5 /* AppAudioManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppAudioManager.m; sourceTree = ""; }; + E197B62218BBF5BB00F073E5 /* SoundPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundPlayer.h; sourceTree = ""; }; + E197B62318BBF5BB00F073E5 /* SoundPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundPlayer.m; sourceTree = ""; }; + E197B62518BBF63B00F073E5 /* SoundBoard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundBoard.h; sourceTree = ""; }; + E197B62618BBF63B00F073E5 /* SoundBoard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundBoard.m; sourceTree = ""; }; + E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + E1B3DC731885EFA100B7F794 /* NotificationManifest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManifest.h; sourceTree = ""; }; + E1C407C117F0C246007BEE65 /* whisperReal.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = whisperReal.der; sourceTree = ""; }; + E1CD329418BCFF9900B1A496 /* SoundInstance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundInstance.h; sourceTree = ""; }; + E1CD329518BCFF9900B1A496 /* SoundInstance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundInstance.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D221A086169C9E5E00537ABF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */, + 70B800AF190C548D0042E3F0 /* libspeex.a in Frameworks */, + 70B800A6190C53180042E3F0 /* libspandsp.a in Frameworks */, + B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */, + 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */, + 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */, + A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */, + A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */, + A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */, + A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */, + D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */, + D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */, + D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */, + D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */, + D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */, + D221A092169C9E5E00537ABF /* CoreGraphics.framework in Frameworks */, + D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */, + D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */, + F995AC2FFD6D4442B012604A /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D221A0A6169C9E5F00537ABF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E1368CBE18A1C36B00109378 /* MessageUI.framework in Frameworks */, + A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */, + A1A018531805C60D00A052A6 /* CoreGraphics.framework in Frameworks */, + A1A018521805C5E800A052A6 /* QuartzCore.framework in Frameworks */, + A123C14916F902EE000AE905 /* Security.framework in Frameworks */, + A194D3BA17A08CD5004BD3A9 /* AddressBookUI.framework in Frameworks */, + A194D3B917A08CD1004BD3A9 /* AddressBook.framework in Frameworks */, + D202868416DBE108009068E9 /* AVFoundation.framework in Frameworks */, + D202868316DBE0FC009068E9 /* CoreTelephony.framework in Frameworks */, + D202868216DBE0F4009068E9 /* SystemConfiguration.framework in Frameworks */, + D202868116DBE0E7009068E9 /* CFNetwork.framework in Frameworks */, + D221A0AC169C9E5F00537ABF /* SenTestingKit.framework in Frameworks */, + D221A0AD169C9E5F00537ABF /* UIKit.framework in Frameworks */, + D221A0AE169C9E5F00537ABF /* Foundation.framework in Frameworks */, + AA0C8E498E2046B0B81EEE6E /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 70B8009F190C529C0042E3F0 /* Products */ = { + isa = PBXGroup; + children = ( + 70B800A3190C529C0042E3F0 /* libspandsp.a */, + ); + name = Products; + sourceTree = ""; + }; + 70B800A8190C54790042E3F0 /* Products */ = { + isa = PBXGroup; + children = ( + 70B800AC190C54790042E3F0 /* libspeex.a */, + ); + name = Products; + sourceTree = ""; + }; + 70B800C8190C55320042E3F0 /* Libraries */ = { + isa = PBXGroup; + children = ( + 70B800E1190C55660042E3F0 /* ProtocolBuffers */, + ); + name = Libraries; + sourceTree = ""; + }; + 70B800E1190C55660042E3F0 /* ProtocolBuffers */ = { + isa = PBXGroup; + children = ( + 70B800E2190C55660042E3F0 /* AbstractMessage.h */, + 70B800E3190C55660042E3F0 /* AbstractMessage.m */, + 70B800E4190C55660042E3F0 /* AbstractMessage_Builder.h */, + 70B800E5190C55660042E3F0 /* AbstractMessage_Builder.m */, + 70B800E6190C55660042E3F0 /* Bootstrap.h */, + 70B800E7190C55660042E3F0 /* CodedInputStream.h */, + 70B800E8190C55660042E3F0 /* CodedInputStream.m */, + 70B800E9190C55660042E3F0 /* CodedOutputStream.h */, + 70B800EA190C55660042E3F0 /* CodedOutputStream.m */, + 70B800EB190C55660042E3F0 /* ConcreteExtensionField.h */, + 70B800EC190C55660042E3F0 /* ConcreteExtensionField.m */, + 70B800ED190C55660042E3F0 /* ExtendableMessage.h */, + 70B800EE190C55660042E3F0 /* ExtendableMessage.m */, + 70B800EF190C55660042E3F0 /* ExtendableMessage_Builder.h */, + 70B800F0190C55660042E3F0 /* ExtendableMessage_Builder.m */, + 70B800F1190C55660042E3F0 /* ExtensionField.h */, + 70B800F2190C55660042E3F0 /* ExtensionRegistry.h */, + 70B800F3190C55660042E3F0 /* ExtensionRegistry.m */, + 70B800F4190C55660042E3F0 /* Field.h */, + 70B800F5190C55660042E3F0 /* Field.m */, + 70B800F6190C55660042E3F0 /* ForwardDeclarations.h */, + 70B800F7190C55660042E3F0 /* GeneratedMessage.h */, + 70B800F8190C55660042E3F0 /* GeneratedMessage.m */, + 70B800F9190C55660042E3F0 /* GeneratedMessage_Builder.h */, + 70B800FA190C55660042E3F0 /* GeneratedMessage_Builder.m */, + 70B800FB190C55660042E3F0 /* Message.h */, + 70B800FC190C55660042E3F0 /* Message_Builder.h */, + 70B800FD190C55660042E3F0 /* MutableExtensionRegistry.h */, + 70B800FE190C55660042E3F0 /* MutableExtensionRegistry.m */, + 70B800FF190C55660042E3F0 /* MutableField.h */, + 70B80100190C55660042E3F0 /* MutableField.m */, + 70B80101190C55660042E3F0 /* ProtocolBuffers.h */, + 70B80102190C55660042E3F0 /* TextFormat.h */, + 70B80103190C55660042E3F0 /* TextFormat.m */, + 70B80104190C55660042E3F0 /* UnknownFieldSet.h */, + 70B80105190C55660042E3F0 /* UnknownFieldSet.m */, + 70B80106190C55660042E3F0 /* UnknownFieldSet_Builder.h */, + 70B80107190C55660042E3F0 /* UnknownFieldSet_Builder.m */, + 70B80108190C55660042E3F0 /* Utilities.h */, + 70B80109190C55660042E3F0 /* Utilities.m */, + 70B8010A190C55660042E3F0 /* WireFormat.h */, + 70B8010B190C55660042E3F0 /* WireFormat.m */, + ); + name = ProtocolBuffers; + path = Libraries/ProtocolBuffers; + sourceTree = ""; + }; + 70DBA29918CFE98500771DAD /* crypto */ = { + isa = PBXGroup; + children = ( + 7038632318F70C0700D4A43F /* CryptoTools.h */, + 7038632418F70C0700D4A43F /* CryptoTools.m */, + 701231B318ECAA4500D456C4 /* EvpMessageDigest.h */, + 701231B418ECAA4500D456C4 /* EvpMessageDigest.m */, + 7038632518F70C0700D4A43F /* EvpSymetricUtil.h */, + 7038632618F70C0700D4A43F /* EvpSymetricUtil.m */, + 70E803ED18F6DD1400BF77BC /* EvpUtil.h */, + ); + path = crypto; + sourceTree = ""; + }; + 765052A318294C9F008313E1 /* Fonts */ = { + isa = PBXGroup; + children = ( + B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */, + 765052B1182BF839008313E1 /* HelveticaNeueLTStd-Th.otf */, + 765052A518294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf */, + 765052A618294C9F008313E1 /* HelveticaNeueLTStd-Md.otf */, + ); + path = Fonts; + sourceTree = ""; + }; + 76919BF51805D169008C664A /* contact */ = { + isa = PBXGroup; + children = ( + 76919BF61805D190008C664A /* ContactManagerTest.m */, + ); + name = contact; + sourceTree = ""; + }; + 76AD2C7B17FB4604000246B0 /* Images */ = { + isa = PBXGroup; + children = ( + E1370B3018A0660300826894 /* archive_icon.png */, + E1370B3118A0660300826894 /* archive_icon@2x.png */, + E1370B3218A0660300826894 /* backspace.png */, + E1370B3318A0660300826894 /* backspace@2x.png */, + E1370B3418A0660300826894 /* checkbox_checkmark.png */, + E1370B3518A0660300826894 /* checkbox_checkmark@2x.png */, + E1370B3618A0660300826894 /* checkbox_empty.png */, + E1370B3718A0660300826894 /* checkbox_empty@2x.png */, + E1370B3818A0660300826894 /* contact_default_feed.png */, + E1370B3918A0660300826894 /* contacts_arrow.png */, + E1370B3A18A0660300826894 /* contacts_arrow@2x.png */, + E1370B3B18A0660300826894 /* DefaultContactImage.png */, + E1370B3C18A0660300826894 /* dismiss_notification_icon.png */, + E1370B3D18A0660300826894 /* dismiss_notification_icon@2x.png */, + E1370B3E18A0660300826894 /* drop_down_arrow_icon.png */, + E1370B3F18A0660300826894 /* drop_down_arrow_icon@2x.png */, + E1370B4018A0660300826894 /* expanded_cell_icon.png */, + E1370B4118A0660300826894 /* expanded_cell_icon@2x.png */, + E1370B4218A0660300826894 /* favourite_false_icon.png */, + E1370B4318A0660300826894 /* favourite_false_icon@2x.png */, + E1370B4418A0660300826894 /* favourite_true_icon.png */, + E1370B4518A0660300826894 /* favourite_true_icon@2x.png */, + E1370B4618A0660300826894 /* forward_button.png */, + E1370B4718A0660300826894 /* forward_button@2x.png */, + E1370B4818A0660300826894 /* home_icon.png */, + E1370B4918A0660300826894 /* icon_contacts.png */, + E1370B4A18A0660300826894 /* icon_favourites.png */, + E1370B4B18A0660300826894 /* icon_keypad.png */, + E1370B4C18A0660300826894 /* icon_recents.png */, + E1370B4D18A0660300826894 /* in_call_phone_icon.png */, + E1370B4E18A0660300826894 /* in_call_phone_icon@2x.png */, + E1370B4F18A0660300826894 /* in_call_phrase_icon.png */, + E1370B5018A0660300826894 /* in_call_phrase_icon@2x.png */, + E1370B5118A0660300826894 /* incoming_call_icon.png */, + E1370B5218A0660300826894 /* incoming_call_icon@2x.png */, + E1370B5318A0660300826894 /* menu_icon.png */, + E1370B5418A0660300826894 /* menu_icon@2x.png */, + E1370B5518A0660300826894 /* message_bubble.png */, + E1370B5618A0660300826894 /* message_bubble@2x.png */, + E16E5C1218AEDB5A00B7C403 /* message_icon.png */, + E1370B5918A0660300826894 /* mute_icon.png */, + E1370B5A18A0660300826894 /* mute_icon@2x.png */, + E1370B5718A0660300826894 /* mute_icon_selected.png */, + E1370B5818A0660300826894 /* mute_icon_selected@2x.png */, + E1370B5B18A0660300826894 /* notification_detail_icon.png */, + E1370B5C18A0660300826894 /* notification_detail_icon@2x.png */, + E1370B5D18A0660300826894 /* notification_mini_icon.png */, + E1370B5E18A0660300826894 /* notification_mini_icon@2x.png */, + E1370B5F18A0660300826894 /* outgoing_call_icon.png */, + E1370B6018A0660300826894 /* outgoing_call_icon@2x.png */, + E16E5C1318AEDB5A00B7C403 /* phone_icon.png */, + E1370B6118A0660300826894 /* search_cancel.png */, + E1370B6218A0660300826894 /* search_cancel@2x.png */, + E1370B6318A0660300826894 /* search_icon.png */, + E1370B6418A0660300826894 /* search_icon@2x.png */, + E1370B6518A0660300826894 /* send_code_icon.png */, + E1370B6618A0660300826894 /* send_code_icon@2x.png */, + E1370B6918A0660300826894 /* speaker_icon.png */, + E1370B6A18A0660300826894 /* speaker_icon@2x.png */, + E1370B6718A0660300826894 /* speaker_icon_selected.png */, + E1370B6818A0660300826894 /* speaker_icon_selected@2x.png */, + E1370B6D18A0660300826894 /* spinner_connecting.png */, + E1370B6E18A0660300826894 /* spinner_connecting@2x.png */, + E1370B6B18A0660300826894 /* spinner_connecting_flash.png */, + E1370B6C18A0660300826894 /* spinner_connecting_flash@2x.png */, + E1370B6F18A0660300826894 /* spinner_error.png */, + E1370B7018A0660300826894 /* spinner_error@2x.png */, + E1370B7118A0660300826894 /* spinner_ringing.png */, + E1370B7218A0660300826894 /* spinner_ringing@2x.png */, + E1370B7318A0660300826894 /* tab_icon_contacts.png */, + E1370B7418A0660300826894 /* tab_icon_contacts@2x.png */, + E1370B7518A0660300826894 /* tab_icon_favourites.png */, + E1370B7618A0660300826894 /* tab_icon_favourites@2x.png */, + E1370B7718A0660300826894 /* tab_icon_inbox.png */, + E1370B7818A0660300826894 /* tab_icon_inbox@2x.png */, + E1370B7918A0660300826894 /* tab_icon_keypad.png */, + E1370B7A18A0660300826894 /* tab_icon_keypad@2x.png */, + E1370B7B18A0660300826894 /* tab_icon_menu.png */, + E1370B7C18A0660300826894 /* tab_icon_menu@2x.png */, + E1370B7D18A0660300826894 /* trash_icon.png */, + E1370B7E18A0660300826894 /* trash_icon@2x.png */, + E1370B7F18A0660300826894 /* volume_high.png */, + E1370B8018A0660300826894 /* volume_high@2x.png */, + E1370B8118A0660300826894 /* volume_low.png */, + E1370B8218A0660300826894 /* volume_low@2x.png */, + E1370B8318A0660300826894 /* whisper_notification_icon.png */, + E1370B8418A0660300826894 /* whisper_notification_icon@2x.png */, + ); + path = Images; + sourceTree = ""; + }; + 76EB03C118170B33006006FC /* src */ = { + isa = PBXGroup; + children = ( + 76EB03C218170B33006006FC /* AppDelegate.h */, + 76EB03C318170B33006006FC /* AppDelegate.m */, + 76EB03C418170B33006006FC /* async */, + 76EB03D918170B33006006FC /* audio */, + 76EB03FE18170B33006006FC /* call */, + 76EB040318170B33006006FC /* contact */, + 70DBA29918CFE98500771DAD /* crypto */, + 76EB041118170B33006006FC /* environment */, + 76EB041D18170B33006006FC /* network */, + E1B3DC731885EFA100B7F794 /* NotificationManifest.h */, + 70BAFD5B190584BE00FA5E0B /* NotificationTracker.h */, + 70BAFD5C190584BE00FA5E0B /* NotificationTracker.m */, + 76EB048818170B33006006FC /* phone */, + 76EB04B118170B33006006FC /* profiling */, + B6BAB53C1918CA1300E4DF53 /* storage */, + 76EB04C818170B33006006FC /* util */, + 76EB04FE18170B33006006FC /* view controllers */, + 76EB052B18170B33006006FC /* views */, + ); + path = src; + sourceTree = ""; + }; + 76EB03C418170B33006006FC /* async */ = { + isa = PBXGroup; + children = ( + 76EB03C518170B33006006FC /* AsyncUtil.h */, + 76EB03C618170B33006006FC /* AsyncUtil.m */, + 76EB03C718170B33006006FC /* AsyncUtilHelperRacingOperation.h */, + 76EB03C818170B33006006FC /* AsyncUtilHelperRacingOperation.m */, + 76EB03C918170B33006006FC /* CancelledToken.h */, + 76EB03CA18170B33006006FC /* CancelledToken.m */, + 76EB03CB18170B33006006FC /* CancelTokenSource.h */, + 76EB03CC18170B33006006FC /* CancelTokenSource.m */, + 76EB03CD18170B33006006FC /* Future.h */, + 76EB03CE18170B33006006FC /* Future.m */, + 76EB03CF18170B33006006FC /* FutureSource.h */, + 76EB03D018170B33006006FC /* FutureSource.m */, + 76EB03D118170B33006006FC /* FutureUtil.h */, + 76EB03D218170B33006006FC /* FutureUtil.m */, + 76EB03D318170B33006006FC /* ObservableValue.h */, + 76EB03D418170B33006006FC /* ObservableValue.m */, + 76EB03D518170B33006006FC /* protocols */, + 76EB03D718170B33006006FC /* TimeoutFailure.h */, + 76EB03D818170B33006006FC /* TimeoutFailure.m */, + ); + path = async; + sourceTree = ""; + }; + 76EB03D518170B33006006FC /* protocols */ = { + isa = PBXGroup; + children = ( + 76EB03D618170B33006006FC /* CancelToken.h */, + ); + path = protocols; + sourceTree = ""; + }; + 76EB03D918170B33006006FC /* audio */ = { + isa = PBXGroup; + children = ( + E197B61F18BBF12700F073E5 /* AppAudioManager.h */, + E197B62018BBF12700F073E5 /* AppAudioManager.m */, + E197B61C18BBEC6D00F073E5 /* AudioRouter.h */, + E197B61D18BBEC6D00F073E5 /* AudioRouter.m */, + E197B5E718BBEC1A00F073E5 /* incall_audio */, + E197B62518BBF63B00F073E5 /* SoundBoard.h */, + E197B62618BBF63B00F073E5 /* SoundBoard.m */, + E1CD329418BCFF9900B1A496 /* SoundInstance.h */, + E1CD329518BCFF9900B1A496 /* SoundInstance.m */, + E197B62218BBF5BB00F073E5 /* SoundPlayer.h */, + E197B62318BBF5BB00F073E5 /* SoundPlayer.m */, + ); + path = audio; + sourceTree = ""; + }; + 76EB03FE18170B33006006FC /* call */ = { + isa = PBXGroup; + children = ( + 76EB03FF18170B33006006FC /* RecentCall.h */, + 76EB040018170B33006006FC /* RecentCall.m */, + 76EB040118170B33006006FC /* RecentCallManager.h */, + 76EB040218170B33006006FC /* RecentCallManager.m */, + ); + path = call; + sourceTree = ""; + }; + 76EB040318170B33006006FC /* contact */ = { + isa = PBXGroup; + children = ( + 76EB040418170B33006006FC /* Contact.h */, + 76EB040518170B33006006FC /* Contact.m */, + 76EB040818170B33006006FC /* ContactsManager.h */, + 76EB040918170B33006006FC /* ContactsManager.m */, + ); + path = contact; + sourceTree = ""; + }; + 76EB041118170B33006006FC /* environment */ = { + isa = PBXGroup; + children = ( + 76EB041218170B33006006FC /* Environment.h */, + 76EB041318170B33006006FC /* Environment.m */, + 76EB041418170B33006006FC /* LocalizableText.h */, + 76EB041518170B33006006FC /* LocalizableText.m */, + 76EB041618170B33006006FC /* PreferencesUtil.h */, + 76EB041718170B33006006FC /* PreferencesUtil.m */, + 76EB041818170B33006006FC /* PropertyListPreferences.h */, + 76EB041918170B33006006FC /* PropertyListPreferences.m */, + 76EB041A18170B33006006FC /* Release.h */, + 76EB041B18170B33006006FC /* Release.m */, + ); + path = environment; + sourceTree = ""; + }; + 76EB041D18170B33006006FC /* network */ = { + isa = PBXGroup; + children = ( + 76EB041E18170B33006006FC /* dns */, + 76EB042318170B33006006FC /* failures */, + 76EB042818170B33006006FC /* http */, + 76EB043518170B33006006FC /* IpAddress.h */, + 76EB043618170B33006006FC /* IpAddress.m */, + 76EB043718170B33006006FC /* IpEndPoint.h */, + 76EB043818170B33006006FC /* IpEndPoint.m */, + 76EB043918170B33006006FC /* NetworkEndPoint.h */, + 76EB043A18170B33006006FC /* PacketHandler.h */, + 76EB043B18170B33006006FC /* PacketHandler.m */, + 76EB043C18170B33006006FC /* rtp */, + 76EB047718170B33006006FC /* tcp */, + 76EB048518170B33006006FC /* udp */, + ); + path = network; + sourceTree = ""; + }; + 76EB041E18170B33006006FC /* dns */ = { + isa = PBXGroup; + children = ( + 76EB041F18170B33006006FC /* DnsManager.h */, + 76EB042018170B33006006FC /* DnsManager.m */, + 76EB042118170B33006006FC /* HostNameEndPoint.h */, + 76EB042218170B33006006FC /* HostNameEndPoint.m */, + ); + path = dns; + sourceTree = ""; + }; + 76EB042318170B33006006FC /* failures */ = { + isa = PBXGroup; + children = ( + 76EB042418170B33006006FC /* IgnoredPacketFailure.h */, + 76EB042518170B33006006FC /* IgnoredPacketFailure.m */, + 76EB042618170B33006006FC /* UnrecognizedRequestFailure.h */, + 76EB042718170B33006006FC /* UnrecognizedRequestFailure.m */, + ); + path = failures; + sourceTree = ""; + }; + 76EB042818170B33006006FC /* http */ = { + isa = PBXGroup; + children = ( + 76EB042918170B33006006FC /* HttpManager.h */, + 76EB042A18170B33006006FC /* HttpManager.m */, + 76EB042B18170B33006006FC /* HttpRequest.h */, + 76EB042C18170B33006006FC /* HttpRequest.m */, + 76EB042D18170B33006006FC /* HttpRequestOrResponse.h */, + 76EB042E18170B33006006FC /* HttpRequestOrResponse.m */, + 76EB042F18170B33006006FC /* HttpRequestUtil.h */, + 76EB043018170B33006006FC /* HttpRequestUtil.m */, + 76EB043118170B33006006FC /* HttpResponse.h */, + 76EB043218170B33006006FC /* HttpResponse.m */, + 76EB043318170B33006006FC /* HttpSocket.h */, + 76EB043418170B33006006FC /* HttpSocket.m */, + ); + path = http; + sourceTree = ""; + }; + 76EB043C18170B33006006FC /* rtp */ = { + isa = PBXGroup; + children = ( + 76EB043D18170B33006006FC /* RtpPacket.h */, + 76EB043E18170B33006006FC /* RtpPacket.m */, + 76EB043F18170B33006006FC /* RtpSocket.h */, + 76EB044018170B33006006FC /* RtpSocket.m */, + 76EB044118170B33006006FC /* srtp */, + 76EB044818170B33006006FC /* zrtp */, + ); + path = rtp; + sourceTree = ""; + }; + 76EB044118170B33006006FC /* srtp */ = { + isa = PBXGroup; + children = ( + 76EB044218170B33006006FC /* SequenceCounter.h */, + 76EB044318170B33006006FC /* SequenceCounter.m */, + 76EB044418170B33006006FC /* SrtpSocket.h */, + 76EB044518170B33006006FC /* SrtpSocket.m */, + 76EB044618170B33006006FC /* SrtpStream.h */, + 76EB044718170B33006006FC /* SrtpStream.m */, + ); + path = srtp; + sourceTree = ""; + }; + 76EB044818170B33006006FC /* zrtp */ = { + isa = PBXGroup; + children = ( + 76EB044918170B33006006FC /* agreement */, + 76EB044E18170B33006006FC /* HashChain.h */, + 76EB044F18170B33006006FC /* HashChain.m */, + 76EB045018170B33006006FC /* MasterSecret.h */, + 76EB045118170B33006006FC /* MasterSecret.m */, + 76EB045218170B33006006FC /* NegotiationFailed.h */, + 76EB045318170B33006006FC /* NegotiationFailed.m */, + 76EB045418170B33006006FC /* packets */, + 76EB046518170B33006006FC /* protocols */, + 76EB046918170B33006006FC /* RecipientUnavailable.h */, + 76EB046A18170B33006006FC /* RecipientUnavailable.m */, + 76EB046B18170B33006006FC /* ShortAuthenticationStringGenerator.h */, + 76EB046C18170B33006006FC /* ShortAuthenticationStringGenerator.m */, + 76EB046D18170B33006006FC /* ZrtpHandshakeResult.h */, + 76EB046E18170B33006006FC /* ZrtpHandshakeResult.m */, + 76EB046F18170B33006006FC /* ZrtpHandshakeSocket.h */, + 76EB047018170B33006006FC /* ZrtpHandshakeSocket.m */, + 76EB047118170B33006006FC /* ZrtpInitiator.h */, + 76EB047218170B33006006FC /* ZrtpInitiator.m */, + 76EB047318170B33006006FC /* ZrtpManager.h */, + 76EB047418170B33006006FC /* ZrtpManager.m */, + 76EB047518170B33006006FC /* ZrtpResponder.h */, + 76EB047618170B33006006FC /* ZrtpResponder.m */, + ); + path = zrtp; + sourceTree = ""; + }; + 76EB044918170B33006006FC /* agreement */ = { + isa = PBXGroup; + children = ( + E19167A218A9687800B7A468 /* DH3KKeyAgreementParticipant.h */, + E19167A318A9687800B7A468 /* DH3KKeyAgreementParticipant.m */, + 76EB044C18170B33006006FC /* DH3KKeyAgreementProtocol.h */, + 76EB044D18170B33006006FC /* DH3KKeyAgreementProtocol.m */, + E16E5BE818AAC40200B7C403 /* EC25KeyAgreementParticipant.h */, + E16E5BE918AAC40200B7C403 /* EC25KeyAgreementParticipant.m */, + E16E5BEA18AAC40200B7C403 /* EC25KeyAgreementProtocol.h */, + E16E5BEB18AAC40200B7C403 /* EC25KeyAgreementProtocol.m */, + E16E5BEC18AAC40200B7C403 /* EvpKeyAgreement.h */, + E16E5BED18AAC40200B7C403 /* EvpKeyAgreement.m */, + ); + path = agreement; + sourceTree = ""; + }; + 76EB045418170B33006006FC /* packets */ = { + isa = PBXGroup; + children = ( + 76EB045518170B33006006FC /* CommitPacket.h */, + 76EB045618170B33006006FC /* CommitPacket.m */, + 76EB045718170B33006006FC /* ConfirmAckPacket.h */, + 76EB045818170B33006006FC /* ConfirmAckPacket.m */, + 76EB045918170B33006006FC /* ConfirmPacket.h */, + 76EB045A18170B33006006FC /* ConfirmPacket.m */, + 76EB045B18170B33006006FC /* DhPacket.h */, + 76EB045C18170B33006006FC /* DhPacket.m */, + 76EB045D18170B33006006FC /* DhPacketSharedSecretHashes.h */, + 76EB045E18170B33006006FC /* DhPacketSharedSecretHashes.m */, + 76EB045F18170B33006006FC /* HandshakePacket.h */, + 76EB046018170B33006006FC /* HandshakePacket.m */, + 76EB046118170B33006006FC /* HelloAckPacket.h */, + 76EB046218170B33006006FC /* HelloAckPacket.m */, + 76EB046318170B33006006FC /* HelloPacket.h */, + 76EB046418170B33006006FC /* HelloPacket.m */, + ); + path = packets; + sourceTree = ""; + }; + 76EB046518170B33006006FC /* protocols */ = { + isa = PBXGroup; + children = ( + 76EB046618170B33006006FC /* KeyAgreementParticipant.h */, + 76EB046718170B33006006FC /* KeyAgreementProtocol.h */, + 76EB046818170B33006006FC /* ZrtpRole.h */, + ); + path = protocols; + sourceTree = ""; + }; + 76EB047718170B33006006FC /* tcp */ = { + isa = PBXGroup; + children = ( + 76EB047818170B33006006FC /* LowLatencyCandidate.h */, + 76EB047918170B33006006FC /* LowLatencyCandidate.m */, + 76EB047A18170B33006006FC /* LowLatencyConnector.h */, + 76EB047B18170B33006006FC /* LowLatencyConnector.m */, + 76EB047C18170B33006006FC /* StreamPair.h */, + 76EB047D18170B33006006FC /* StreamPair.m */, + 76EB047E18170B33006006FC /* tls */, + ); + path = tcp; + sourceTree = ""; + }; + 76EB047E18170B33006006FC /* tls */ = { + isa = PBXGroup; + children = ( + 76EB047F18170B33006006FC /* Certificate.h */, + 76EB048018170B33006006FC /* Certificate.m */, + 76EB048118170B33006006FC /* NetworkStream.h */, + 76EB048218170B33006006FC /* NetworkStream.m */, + 76EB048318170B33006006FC /* SecureEndPoint.h */, + 76EB048418170B33006006FC /* SecureEndPoint.m */, + ); + path = tls; + sourceTree = ""; + }; + 76EB048518170B33006006FC /* udp */ = { + isa = PBXGroup; + children = ( + 76EB048618170B33006006FC /* UdpSocket.h */, + 76EB048718170B33006006FC /* UdpSocket.m */, + ); + path = udp; + sourceTree = ""; + }; + 76EB048818170B33006006FC /* phone */ = { + isa = PBXGroup; + children = ( + 76EB048918170B33006006FC /* callstate */, + 76EB049418170B33006006FC /* PhoneManager.h */, + 76EB049518170B33006006FC /* PhoneManager.m */, + 76EB049618170B33006006FC /* PhoneNumber.h */, + 76EB049718170B33006006FC /* PhoneNumber.m */, + 76EB049818170B33006006FC /* signaling */, + ); + path = phone; + sourceTree = ""; + }; + 76EB048918170B33006006FC /* callstate */ = { + isa = PBXGroup; + children = ( + 76EB048A18170B33006006FC /* CallController.h */, + 76EB048B18170B33006006FC /* CallController.m */, + 76EB048C18170B33006006FC /* CallFailedServerMessage.h */, + 76EB048D18170B33006006FC /* CallFailedServerMessage.m */, + 76EB048E18170B33006006FC /* CallProgress.h */, + 76EB048F18170B33006006FC /* CallProgress.m */, + 76EB049018170B33006006FC /* CallState.h */, + 76EB049118170B33006006FC /* CallState.m */, + 76EB049218170B33006006FC /* CallTermination.h */, + 76EB049318170B33006006FC /* CallTermination.m */, + ); + path = callstate; + sourceTree = ""; + }; + 76EB049818170B33006006FC /* signaling */ = { + isa = PBXGroup; + children = ( + 76EB049918170B33006006FC /* CallConnectResult.h */, + 76EB049A18170B33006006FC /* CallConnectResult.m */, + 76EB049B18170B33006006FC /* CallConnectUtil.h */, + 76EB049C18170B33006006FC /* CallConnectUtil.m */, + 76EB049D18170B33006006FC /* CallConnectUtil_Initiator.h */, + 76EB049E18170B33006006FC /* CallConnectUtil_Initiator.m */, + 76EB049F18170B33006006FC /* CallConnectUtil_Responder.h */, + 76EB04A018170B33006006FC /* CallConnectUtil_Responder.m */, + 76EB04A118170B33006006FC /* CallConnectUtil_Server.h */, + 76EB04A218170B33006006FC /* CallConnectUtil_Server.m */, + 76EB04A318170B33006006FC /* InitiateSignal.pb.h */, + 76EB04A418170B33006006FC /* InitiateSignal.pb.m */, + 76EB04A518170B33006006FC /* InitiateSignal.proto */, + 76EB04A618170B33006006FC /* InitiatorSessionDescriptor.h */, + 76EB04A718170B33006006FC /* InitiatorSessionDescriptor.m */, + 76EB04A818170B33006006FC /* number directory */, + 76EB04AD18170B33006006FC /* ResponderSessionDescriptor.h */, + 76EB04AE18170B33006006FC /* ResponderSessionDescriptor.m */, + 76EB04AF18170B33006006FC /* SignalUtil.h */, + 76EB04B018170B33006006FC /* SignalUtil.m */, + ); + path = signaling; + sourceTree = ""; + }; + 76EB04A818170B33006006FC /* number directory */ = { + isa = PBXGroup; + children = ( + 76EB04A918170B33006006FC /* PhoneNumberDirectoryFilter.h */, + 76EB04AA18170B33006006FC /* PhoneNumberDirectoryFilter.m */, + 76EB04AB18170B33006006FC /* PhoneNumberDirectoryFilterManager.h */, + 76EB04AC18170B33006006FC /* PhoneNumberDirectoryFilterManager.m */, + ); + path = "number directory"; + sourceTree = ""; + }; + 76EB04B118170B33006006FC /* profiling */ = { + isa = PBXGroup; + children = ( + 76EB04B218170B33006006FC /* CategorizingLogger.h */, + 76EB04B318170B33006006FC /* CategorizingLogger.m */, + 76EB04B418170B33006006FC /* DecayingSampleEstimator.h */, + 76EB04B518170B33006006FC /* DecayingSampleEstimator.m */, + 76EB04B618170B33006006FC /* EventWindow.h */, + 76EB04B718170B33006006FC /* EventWindow.m */, + 76EB04B818170B33006006FC /* LoggingUtil.h */, + 76EB04B918170B33006006FC /* LoggingUtil.m */, + 76EB04BA18170B33006006FC /* protocols */, + ); + path = profiling; + sourceTree = ""; + }; + 76EB04BA18170B33006006FC /* protocols */ = { + isa = PBXGroup; + children = ( + 76EB04BB18170B33006006FC /* ConditionLogger.h */, + 76EB04BC18170B33006006FC /* Logging.h */, + 76EB04BD18170B33006006FC /* OccurrenceLogger.h */, + 76EB04BE18170B33006006FC /* utilities */, + 76EB04C718170B33006006FC /* ValueLogger.h */, + ); + path = protocols; + sourceTree = ""; + }; + 76EB04BE18170B33006006FC /* utilities */ = { + isa = PBXGroup; + children = ( + 76EB04BF18170B33006006FC /* AnonymousConditionLogger.h */, + 76EB04C018170B33006006FC /* AnonymousConditionLogger.m */, + 76EB04C118170B33006006FC /* AnonymousOccurrenceLogger.h */, + 76EB04C218170B33006006FC /* AnonymousOccurrenceLogger.m */, + 76EB04C318170B33006006FC /* AnonymousValueLogger.h */, + 76EB04C418170B33006006FC /* AnonymousValueLogger.m */, + 76EB04C518170B33006006FC /* DiscardingLog.h */, + 76EB04C618170B33006006FC /* DiscardingLog.m */, + ); + path = utilities; + sourceTree = ""; + }; + 76EB04C818170B33006006FC /* util */ = { + isa = PBXGroup; + children = ( + 76EB04C918170B33006006FC /* ArrayUtil.h */, + 76EB04CA18170B33006006FC /* ArrayUtil.m */, + 76EB04CD18170B33006006FC /* BloomFilter.h */, + 76EB04CE18170B33006006FC /* BloomFilter.m */, + 76EB04CF18170B33006006FC /* collections */, + 76EB04D618170B33006006FC /* constraints */, + 76EB04E018170B33006006FC /* Conversions.h */, + 76EB04E118170B33006006FC /* Conversions.m */, + 76EB04E218170B33006006FC /* Crc32.h */, + 76EB04E318170B33006006FC /* Crc32.m */, + 76EB04E618170B33006006FC /* DataUtil.h */, + 76EB04E718170B33006006FC /* DataUtil.m */, + B90418E4183E9DD40038554A /* DateUtil.h */, + B90418E5183E9DD40038554A /* DateUtil.m */, + 76EB04E818170B33006006FC /* DictionaryUtil.h */, + 76EB04E918170B33006006FC /* DictionaryUtil.m */, + 76EB04EA18170B33006006FC /* FunctionalUtil.h */, + 76EB04EB18170B33006006FC /* FunctionalUtil.m */, + B6BAB53D1918CA4100E4DF53 /* KeychainWrapper.h */, + B6BAB53E1918CA4100E4DF53 /* KeychainWrapper.m */, + 76EB04EC18170B33006006FC /* NumberUtil.h */, + 76EB04ED18170B33006006FC /* NumberUtil.m */, + 76EB04EE18170B33006006FC /* Operation.h */, + 76EB04EF18170B33006006FC /* Operation.m */, + 7095B7AE18F46D35002C66E2 /* PhoneNumberUtil.h */, + 7095B7AF18F46D35002C66E2 /* PhoneNumberUtil.m */, + 76EB04F018170B33006006FC /* protocols */, + 76EB04F518170B33006006FC /* StringUtil.h */, + 76EB04F618170B33006006FC /* StringUtil.m */, + 76EB04F718170B33006006FC /* ThreadManager.h */, + 76EB04F818170B33006006FC /* ThreadManager.m */, + 76EB04F918170B33006006FC /* TimeUtil.h */, + 76EB04FA18170B33006006FC /* TimeUtil.m */, + B97940251832BD2400BD66CB /* UIUtil.h */, + B97940261832BD2400BD66CB /* UIUtil.m */, + 76EB04FB18170B33006006FC /* Util.h */, + 76EB04FC18170B33006006FC /* Zid.h */, + 76EB04FD18170B33006006FC /* Zid.m */, + 707E549018FF26E800C8649D /* SmsInvite.h */, + 707E549118FF26E800C8649D /* SmsInvite.m */, + ); + path = util; + sourceTree = ""; + }; + 76EB04CF18170B33006006FC /* collections */ = { + isa = PBXGroup; + children = ( + 76EB04D018170B33006006FC /* CyclicalBuffer.h */, + 76EB04D118170B33006006FC /* CyclicalBuffer.m */, + 76EB04D218170B33006006FC /* PriorityQueue.h */, + 76EB04D318170B33006006FC /* PriorityQueue.m */, + 76EB04D418170B33006006FC /* Queue.h */, + 76EB04D518170B33006006FC /* Queue.m */, + ); + path = collections; + sourceTree = ""; + }; + 76EB04D618170B33006006FC /* constraints */ = { + isa = PBXGroup; + children = ( + 76EB04D718170B33006006FC /* BadArgument.h */, + 76EB04D818170B33006006FC /* BadArgument.m */, + 76EB04D918170B33006006FC /* BadState.h */, + 76EB04DA18170B33006006FC /* BadState.m */, + 76EB04DB18170B33006006FC /* Constraints.h */, + 76EB04DC18170B33006006FC /* OperationFailed.h */, + 76EB04DD18170B33006006FC /* OperationFailed.m */, + 76EB04DE18170B33006006FC /* SecurityFailure.h */, + 76EB04DF18170B33006006FC /* SecurityFailure.m */, + ); + path = constraints; + sourceTree = ""; + }; + 76EB04F018170B33006006FC /* protocols */ = { + isa = PBXGroup; + children = ( + 76EB04F118170B33006006FC /* Terminable.h */, + 76EB04F218170B33006006FC /* utilities */, + ); + path = protocols; + sourceTree = ""; + }; + 76EB04F218170B33006006FC /* utilities */ = { + isa = PBXGroup; + children = ( + 76EB04F318170B33006006FC /* AnonymousTerminator.h */, + 76EB04F418170B33006006FC /* AnonymousTerminator.m */, + ); + path = utilities; + sourceTree = ""; + }; + 76EB04FE18170B33006006FC /* view controllers */ = { + isa = PBXGroup; + children = ( + 76EB051118170B33006006FC /* CallLogViewController.h */, + 76EB051218170B33006006FC /* CallLogViewController.m */, + 76EB050118170B33006006FC /* ContactBrowseViewController.h */, + 76EB050218170B33006006FC /* ContactBrowseViewController.m */, + 76EB050318170B33006006FC /* ContactDetailViewController.h */, + 76EB050418170B33006006FC /* ContactDetailViewController.m */, + B97CBFA518860EA3008E0DE9 /* CountryCodeViewController.h */, + B97CBFA618860EA3008E0DE9 /* CountryCodeViewController.m */, + 76EB050518170B33006006FC /* DialerViewController.h */, + 76EB050618170B33006006FC /* DialerViewController.m */, + 76EB050718170B33006006FC /* FavouritesViewController.h */, + 76EB050818170B33006006FC /* FavouritesViewController.m */, + 76EB050918170B33006006FC /* InboxFeedViewController.h */, + 76EB050A18170B33006006FC /* InboxFeedViewController.m */, + 76EB050B18170B33006006FC /* InCallViewController.h */, + 76EB050C18170B33006006FC /* InCallViewController.m */, + 707E548A18FF0B8A00C8649D /* InviteContactModal.h */, + 707E548B18FF0B8A00C8649D /* InviteContactModal.m */, + B9CA51B718809ACA007E204E /* InviteContactsViewController.h */, + B9CA51B818809ACA007E204E /* InviteContactsViewController.m */, + 76EB050D18170B33006006FC /* LeftSideMenuViewController.h */, + 76EB050E18170B33006006FC /* LeftSideMenuViewController.m */, + 76EB050F18170B33006006FC /* NextResponderScrollView.h */, + 76EB051018170B33006006FC /* NextResponderScrollView.m */, + 76B8189B182C39460088060E /* PreferenceListViewController.h */, + 76B8189C182C39460088060E /* PreferenceListViewController.m */, + 76EB051518170B33006006FC /* RegisterViewController.h */, + 76EB051618170B33006006FC /* RegisterViewController.m */, + 76EB051718170B33006006FC /* SettingsViewController.h */, + 76EB051818170B33006006FC /* SettingsViewController.m */, + 76EB051918170B33006006FC /* TabBarParentViewController.h */, + 76EB051A18170B33006006FC /* TabBarParentViewController.m */, + 76EB051C18170B33006006FC /* xibs */, + ); + path = "view controllers"; + sourceTree = ""; + }; + 76EB051C18170B33006006FC /* xibs */ = { + isa = PBXGroup; + children = ( + 76EB051D18170B33006006FC /* CallAudioManagerDemo.xib */, + 76EB052718170B33006006FC /* CallLogViewController.xib */, + 76EB051E18170B33006006FC /* ContactBrowseViewController.xib */, + 76EB051F18170B33006006FC /* ContactDetailTableViewCell.xib */, + 76EB052018170B33006006FC /* ContactDetailViewController.xib */, + B97CBFA718860EA3008E0DE9 /* CountryCodeViewController.xib */, + 76EB052118170B33006006FC /* DialerViewController.xib */, + 76EB052218170B33006006FC /* DowngradeCallViewController.xib */, + 76EB052318170B33006006FC /* FavouritesViewController.xib */, + 76EB052418170B33006006FC /* InboxFeedViewController.xib */, + 76EB052518170B33006006FC /* InCallViewController.xib */, + B9CA51B918809ACA007E204E /* InviteContactsViewController.xib */, + 76EB052618170B33006006FC /* LeftSideMenuViewController.xib */, + 76B8189D182C39460088060E /* PreferenceListViewController.xib */, + 76EB052918170B33006006FC /* RegisterViewController.xib */, + 76EB052A18170B33006006FC /* SettingsViewController.xib */, + 76EB051B18170B33006006FC /* TabBarParentViewController.xib */, + ); + path = xibs; + sourceTree = ""; + }; + 76EB052B18170B33006006FC /* views */ = { + isa = PBXGroup; + children = ( + 76EB053618170B33006006FC /* CallLogTableViewCell.h */, + 76EB053718170B33006006FC /* CallLogTableViewCell.m */, + 76EB052C18170B33006006FC /* ContactDetailTableViewCell.h */, + 76EB052D18170B33006006FC /* ContactDetailTableViewCell.m */, + 76EB052E18170B33006006FC /* ContactTableViewCell.h */, + 76EB052F18170B33006006FC /* ContactTableViewCell.m */, + B97CBFAC1886100E008E0DE9 /* CountryCodeTableViewCell.h */, + B97CBFAD1886100E008E0DE9 /* CountryCodeTableViewCell.m */, + 765052AD182AC9B5008313E1 /* DialerButtonView.h */, + 765052AE182AC9B5008313E1 /* DialerButtonView.m */, + B9A578AF183D60ED00C17105 /* FavouriteTableViewCell.h */, + B9A578B0183D60ED00C17105 /* FavouriteTableViewCell.m */, + 76C87F11181EE11C00C4ACAB /* InboxFeedFooterCell.h */, + 76C87F12181EE11C00C4ACAB /* InboxFeedFooterCell.m */, + 76EB053418170B33006006FC /* InboxFeedTableViewCell.h */, + 76EB053518170B33006006FC /* InboxFeedTableViewCell.m */, + 70377AA71916BA0500CAF501 /* InteractiveLabel.h */, + 70377AA81916BA0500CAF501 /* InteractiveLabel.m */, + B9B89C52185A2B5F00A24465 /* LeftSideMenuCell.h */, + B9B89C53185A2B5F00A24465 /* LeftSideMenuCell.m */, + 7650529F182945EF008313E1 /* LocalizableCustomFontLabel.h */, + 765052A0182945EF008313E1 /* LocalizableCustomFontLabel.m */, + 76D713E5182D3E3F00C9C9C8 /* PreferenceListTableViewCell.h */, + 76D713E6182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m */, + B942EB0C183A9633000887BB /* SearchBarTitleView.h */, + B942EB0D183A9633000887BB /* SearchBarTitleView.m */, + 762D9DCD18281C7400A5E418 /* SettingsTableHeaderView.h */, + 762D9DCE18281C7400A5E418 /* SettingsTableHeaderView.m */, + B9EB5AC41884D370007CBB57 /* UnseenWhisperUserCell.h */, + B9EB5AC51884D370007CBB57 /* UnseenWhisperUserCell.m */, + 76EB053818170B33006006FC /* xibs */, + ); + path = views; + sourceTree = ""; + }; + 76EB053818170B33006006FC /* xibs */ = { + isa = PBXGroup; + children = ( + 76EB053F18170B33006006FC /* CallLogTableViewCell.xib */, + 76EB053A18170B33006006FC /* ContactTableViewCell.xib */, + B97CBFB018861023008E0DE9 /* CountryCodeTableViewCell.xib */, + B9A578B3183D610300C17105 /* FavouriteTableViewCell.xib */, + 76C87F15181EE2EB00C4ACAB /* InboxFeedFooterCell.xib */, + 76EB053C18170B33006006FC /* InboxFeedTableViewCell.xib */, + B9B89C56185A2B7000A24465 /* LeftSideMenuCell.xib */, + 76D713E9182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib */, + B9EB5AC81884D387007CBB57 /* UnseenWhisperUserCell.xib */, + ); + path = xibs; + sourceTree = ""; + }; + A15706EA17F0CD6D007C2BD6 /* test */ = { + isa = PBXGroup; + children = ( + 76919BF51805D169008C664A /* contact */, + A15706EB17F0CD6D007C2BD6 /* async */, + A15706F217F0CD6D007C2BD6 /* audio */, + A157070017F0CD6D007C2BD6 /* network */, + A157072E17F0CD6D007C2BD6 /* phone */, + A157073417F0CD6D007C2BD6 /* profiling */, + A157073917F0CD6D007C2BD6 /* Supporting Files */, + A157073B17F0CD6D007C2BD6 /* TestUtil.h */, + A157073C17F0CD6D007C2BD6 /* TestUtil.m */, + A157073D17F0CD6D007C2BD6 /* util */, + ); + path = test; + sourceTree = ""; + }; + A15706EB17F0CD6D007C2BD6 /* async */ = { + isa = PBXGroup; + children = ( + A15706EC17F0CD6D007C2BD6 /* AsyncUtilTest.h */, + A15706ED17F0CD6D007C2BD6 /* AsyncUtilTest.m */, + A15706EE17F0CD6D007C2BD6 /* FutureSourceTest.h */, + A15706EF17F0CD6D007C2BD6 /* FutureSourceTest.m */, + A15706F017F0CD6D007C2BD6 /* ObservableTest.h */, + A15706F117F0CD6D007C2BD6 /* ObservableTest.m */, + ); + path = async; + sourceTree = ""; + }; + A15706F217F0CD6D007C2BD6 /* audio */ = { + isa = PBXGroup; + children = ( + A15706F317F0CD6D007C2BD6 /* AudioFrameTest.h */, + A15706F417F0CD6D007C2BD6 /* AudioFrameTest.m */, + A15706F517F0CD6D007C2BD6 /* AudioRemoteIOTest.h */, + A15706F617F0CD6D007C2BD6 /* AudioRemoteIOTest.m */, + A15706F717F0CD6D007C2BD6 /* AudioStretcherTest.h */, + A15706F817F0CD6D007C2BD6 /* AudioStretcherTest.m */, + A15706F917F0CD6D007C2BD6 /* JitterQueueTest.h */, + A15706FA17F0CD6D007C2BD6 /* JitterQueueTest.m */, + A15706FB17F0CD6D007C2BD6 /* SpeexCodecTest.h */, + A15706FC17F0CD6D007C2BD6 /* SpeexCodecTest.m */, + ); + path = audio; + sourceTree = ""; + }; + A157070017F0CD6D007C2BD6 /* network */ = { + isa = PBXGroup; + children = ( + A157070117F0CD6D007C2BD6 /* dns */, + A157070417F0CD6D007C2BD6 /* http */, + A157070717F0CD6D007C2BD6 /* IpAddressTest.h */, + A157070817F0CD6D007C2BD6 /* IpAddressTest.m */, + A157070917F0CD6D007C2BD6 /* IpEndPointTest.h */, + A157070A17F0CD6D007C2BD6 /* IpEndPointTest.m */, + A157070B17F0CD6D007C2BD6 /* rtp */, + A157072317F0CD6D007C2BD6 /* tcp */, + A157072B17F0CD6D007C2BD6 /* udp */, + ); + path = network; + sourceTree = ""; + }; + A157070117F0CD6D007C2BD6 /* dns */ = { + isa = PBXGroup; + children = ( + A157070217F0CD6D007C2BD6 /* DnsManagerTest.h */, + A157070317F0CD6D007C2BD6 /* DnsManagerTest.m */, + ); + path = dns; + sourceTree = ""; + }; + A157070417F0CD6D007C2BD6 /* http */ = { + isa = PBXGroup; + children = ( + A157070517F0CD6D007C2BD6 /* HttpRequestResponseTest.h */, + A157070617F0CD6D007C2BD6 /* HttpRequestResponseTest.m */, + ); + path = http; + sourceTree = ""; + }; + A157070B17F0CD6D007C2BD6 /* rtp */ = { + isa = PBXGroup; + children = ( + A157070C17F0CD6D007C2BD6 /* RtpPacketTests.h */, + A157070D17F0CD6D007C2BD6 /* RtpPacketTests.m */, + A157070E17F0CD6D007C2BD6 /* srtp */, + A157071317F0CD6D007C2BD6 /* zrtp */, + ); + path = rtp; + sourceTree = ""; + }; + A157070E17F0CD6D007C2BD6 /* srtp */ = { + isa = PBXGroup; + children = ( + A157070F17F0CD6D007C2BD6 /* SecureStreamTest.h */, + A157071017F0CD6D007C2BD6 /* SecureStreamTest.m */, + A157071117F0CD6D007C2BD6 /* SequenceCounterTest.h */, + A157071217F0CD6D007C2BD6 /* SequenceCounterTest.m */, + ); + path = srtp; + sourceTree = ""; + }; + A157071317F0CD6D007C2BD6 /* zrtp */ = { + isa = PBXGroup; + children = ( + A157071417F0CD6D007C2BD6 /* DH3KAgreerTest.h */, + A157071517F0CD6D007C2BD6 /* DH3KAgreerTest.m */, + A157071617F0CD6D007C2BD6 /* HandshakePacketTest.h */, + A157071717F0CD6D007C2BD6 /* HandshakePacketTest.m */, + A157071817F0CD6D007C2BD6 /* HashChainTest.h */, + A157071917F0CD6D007C2BD6 /* HashChainTest.m */, + A157071A17F0CD6D007C2BD6 /* MasterSecretTest.h */, + A157071B17F0CD6D007C2BD6 /* MasterSecretTest.m */, + A157071C17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.h */, + A157071D17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m */, + A157071E17F0CD6D007C2BD6 /* utilities */, + A157072117F0CD6D007C2BD6 /* ZrtpTest.h */, + A157072217F0CD6D007C2BD6 /* ZrtpTest.m */, + E16E5BF818AAF02100B7C403 /* EC25AgreerTest.m */, + ); + path = zrtp; + sourceTree = ""; + }; + A157071E17F0CD6D007C2BD6 /* utilities */ = { + isa = PBXGroup; + children = ( + A157071F17F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.h */, + A157072017F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m */, + ); + path = utilities; + sourceTree = ""; + }; + A157072317F0CD6D007C2BD6 /* tcp */ = { + isa = PBXGroup; + children = ( + A157072417F0CD6D007C2BD6 /* LowLatencyConnectorTest.h */, + A157072517F0CD6D007C2BD6 /* LowLatencyConnectorTest.m */, + A157072617F0CD6D007C2BD6 /* tls */, + ); + path = tcp; + sourceTree = ""; + }; + A157072617F0CD6D007C2BD6 /* tls */ = { + isa = PBXGroup; + children = ( + A157072717F0CD6D007C2BD6 /* NetworkStreamTest.h */, + A157072817F0CD6D007C2BD6 /* NetworkStreamTest.m */, + A157072917F0CD6D007C2BD6 /* SecureEndPointTest.h */, + A157072A17F0CD6D007C2BD6 /* SecureEndPointTest.m */, + ); + path = tls; + sourceTree = ""; + }; + A157072B17F0CD6D007C2BD6 /* udp */ = { + isa = PBXGroup; + children = ( + A157072C17F0CD6D007C2BD6 /* UdpSocketTest.h */, + A157072D17F0CD6D007C2BD6 /* UdpSocketTest.m */, + ); + path = udp; + sourceTree = ""; + }; + A157072E17F0CD6D007C2BD6 /* phone */ = { + isa = PBXGroup; + children = ( + A157072F17F0CD6D007C2BD6 /* PhoneNumberTest.h */, + A157073017F0CD6D007C2BD6 /* PhoneNumberTest.m */, + A157073117F0CD6D007C2BD6 /* signaling */, + ); + path = phone; + sourceTree = ""; + }; + A157073117F0CD6D007C2BD6 /* signaling */ = { + isa = PBXGroup; + children = ( + A157073217F0CD6D007C2BD6 /* SessionDescriptorTest.h */, + A157073317F0CD6D007C2BD6 /* SessionDescriptorTest.m */, + ); + path = signaling; + sourceTree = ""; + }; + A157073417F0CD6D007C2BD6 /* profiling */ = { + isa = PBXGroup; + children = ( + A157073517F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.h */, + A157073617F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.m */, + A157073717F0CD6D007C2BD6 /* EventWindowTest.h */, + A157073817F0CD6D007C2BD6 /* EventWindowTest.m */, + ); + path = profiling; + sourceTree = ""; + }; + A157073917F0CD6D007C2BD6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A157073A17F0CD6D007C2BD6 /* SignalTests-Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + A157073D17F0CD6D007C2BD6 /* util */ = { + isa = PBXGroup; + children = ( + A157073E17F0CD6D007C2BD6 /* BloomFilterTest.h */, + A157073F17F0CD6D007C2BD6 /* BloomFilterTest.m */, + A157074017F0CD6D007C2BD6 /* CancelTokenTest.h */, + A157074117F0CD6D007C2BD6 /* CancelTokenTest.m */, + A157074217F0CD6D007C2BD6 /* ConversionsTest.h */, + A157074317F0CD6D007C2BD6 /* ConversionsTest.m */, + A157074417F0CD6D007C2BD6 /* Crc32Test.h */, + A157074517F0CD6D007C2BD6 /* Crc32Test.m */, + A157074617F0CD6D007C2BD6 /* CryptoUtilTest.h */, + A157074717F0CD6D007C2BD6 /* CryptoUtilTest.m */, + A157074817F0CD6D007C2BD6 /* CyclicalBufferTest.h */, + A157074917F0CD6D007C2BD6 /* CyclicalBufferTest.m */, + A157074A17F0CD6D007C2BD6 /* ExceptionsTest.h */, + A157074B17F0CD6D007C2BD6 /* ExceptionsTest.m */, + A157074C17F0CD6D007C2BD6 /* FunctionalUtilTest.h */, + A157074D17F0CD6D007C2BD6 /* FunctionalUtilTest.m */, + A157074E17F0CD6D007C2BD6 /* PriorityQueueTest.h */, + A157074F17F0CD6D007C2BD6 /* PriorityQueueTest.m */, + A157075017F0CD6D007C2BD6 /* QueueTest.h */, + A157075117F0CD6D007C2BD6 /* QueueTest.m */, + A157075217F0CD6D007C2BD6 /* UtilTest.h */, + A157075317F0CD6D007C2BD6 /* UtilTest.m */, + ); + path = util; + sourceTree = ""; + }; + B6B6C3C419193F5B00C0B76B /* Translations */ = { + isa = PBXGroup; + children = ( + B6B6C3C51919440C00C0B76B /* Localizable.strings */, + ); + name = Translations; + sourceTree = ""; + }; + B6BAB53C1918CA1300E4DF53 /* storage */ = { + isa = PBXGroup; + children = ( + B68DF7B51918D7FC00C7BAB9 /* KeyChainStorage.h */, + B68DF7B61918D7FC00C7BAB9 /* KeyChainStorage.m */, + ); + path = storage; + sourceTree = ""; + }; + D221A07E169C9E5E00537ABF = { + isa = PBXGroup; + children = ( + D221A093169C9E5E00537ABF /* Signal */, + C71793B33D9C45079F74487E /* Pods.xcconfig */, + 70B800C8190C55320042E3F0 /* Libraries */, + D221A08C169C9E5E00537ABF /* Frameworks */, + D221A08A169C9E5E00537ABF /* Products */, + 70B8009E190C529C0042E3F0 /* spandsp.xcodeproj */, + 70B800A7190C54790042E3F0 /* speex.xcodeproj */, + ); + sourceTree = ""; + }; + D221A08A169C9E5E00537ABF /* Products */ = { + isa = PBXGroup; + children = ( + D221A089169C9E5E00537ABF /* Signal.app */, + D221A0AA169C9E5F00537ABF /* SignalTests.octest */, + ); + name = Products; + sourceTree = ""; + }; + D221A08C169C9E5E00537ABF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 70377AAA1918450100CAF501 /* MobileCoreServices.framework */, + B9EB5ABC1884C002007CBB57 /* MessageUI.framework */, + A1C32D4D17A0652C000A904E /* AddressBook.framework */, + A1C32D4F17A06537000A904E /* AddressBookUI.framework */, + A163E8AA16F3F6A90094D68B /* Security.framework */, + 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */, + 768A1A2A17FC9CD300E00ED8 /* libz.dylib */, + A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */, + E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */, + A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */, + D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */, + D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */, + D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */, + D24B5BD4169F568C00681372 /* AudioToolbox.framework */, + D221A0E7169DFFC500537ABF /* AVFoundation.framework */, + D221A08D169C9E5E00537ABF /* UIKit.framework */, + D221A08F169C9E5E00537ABF /* Foundation.framework */, + D221A091169C9E5E00537ABF /* CoreGraphics.framework */, + D221A0AB169C9E5F00537ABF /* SenTestingKit.framework */, + 8313AE91B4954215858A5662 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + D221A093169C9E5E00537ABF /* Signal */ = { + isa = PBXGroup; + children = ( + B657DDC91911A40500F45B0C /* Signal.entitlements */, + 765052A318294C9F008313E1 /* Fonts */, + 76AD2C7B17FB4604000246B0 /* Images */, + B67EBF5C19194AC60084CCFD /* Settings.bundle */, + 76EB03C118170B33006006FC /* src */, + D221A094169C9E5E00537ABF /* Supporting Files */, + A15706EA17F0CD6D007C2BD6 /* test */, + ); + path = Signal; + sourceTree = ""; + }; + D221A094169C9E5E00537ABF /* Supporting Files */ = { + isa = PBXGroup; + children = ( + B6B6C3C419193F5B00C0B76B /* Translations */, + E18AB40618A05754001A532A /* AudioFiles */, + E1370BDA18A066F600826894 /* Default-568h@2x.png */, + E1370BDB18A066F600826894 /* Default.png */, + E1370BDC18A066F600826894 /* Default@2x.png */, + E18AB3F318A05734001A532A /* Icons */, + E108ED12187E34FD0045AEA3 /* iTunesArtwork.png */, + E108ED13187E34FD0045AEA3 /* iTunesArtwork@2x.png */, + D221A099169C9E5E00537ABF /* main.m */, + D221A095169C9E5E00537ABF /* Signal-Info.plist */, + D221A09B169C9E5E00537ABF /* Signal-Prefix.pch */, + E1C407C117F0C246007BEE65 /* whisperReal.der */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + E18AB3F318A05734001A532A /* Icons */ = { + isa = PBXGroup; + children = ( + E18AB3F418A05734001A532A /* AppIcon29x29.jpg */, + E18AB3F518A05734001A532A /* AppIcon29x29.png */, + E18AB3F618A05734001A532A /* AppIcon29x29@2x.png */, + E18AB3F718A05734001A532A /* AppIcon40x40.png */, + E18AB3F818A05734001A532A /* AppIcon40x40@2x.png */, + E18AB3F918A05734001A532A /* AppIcon60x60.png */, + E18AB3FA18A05734001A532A /* AppIcon60x60@2x.png */, + E18AB3FB18A05734001A532A /* AppIcon76x76.png */, + E18AB3FC18A05734001A532A /* AppIcon76x76@2x.png */, + ); + path = Icons; + sourceTree = ""; + }; + E18AB40618A05754001A532A /* AudioFiles */ = { + isa = PBXGroup; + children = ( + 70B8FEE11909FE360042E3F0 /* 171756__nenadsimic__picked-coin-echo-2.wav */, + E18AB40718A05754001A532A /* busy.mp3 */, + E18AB40818A05754001A532A /* completed.mp3 */, + E18AB40918A05754001A532A /* failure.mp3 */, + E18AB40A18A05754001A532A /* handshake.mp3 */, + E18AB40B18A05754001A532A /* outring.mp3 */, + E18AB40C18A05754001A532A /* r.caf */, + E18AB40D18A05754001A532A /* sonarping.mp3 */, + ); + path = AudioFiles; + sourceTree = ""; + }; + E197B5E718BBEC1A00F073E5 /* incall_audio */ = { + isa = PBXGroup; + children = ( + E197B5E818BBEC1A00F073E5 /* AudioPacker.h */, + E197B5E918BBEC1A00F073E5 /* AudioPacker.m */, + E197B5EA18BBEC1A00F073E5 /* AudioSocket.h */, + E197B5EB18BBEC1A00F073E5 /* AudioSocket.m */, + E197B5EC18BBEC1A00F073E5 /* CallAudioManager.h */, + E197B5ED18BBEC1A00F073E5 /* CallAudioManager.m */, + E197B5EE18BBEC1A00F073E5 /* EncodedAudioFrame.h */, + E197B5EF18BBEC1A00F073E5 /* EncodedAudioFrame.m */, + E197B5F018BBEC1A00F073E5 /* EncodedAudioPacket.h */, + E197B5F118BBEC1A00F073E5 /* EncodedAudioPacket.m */, + E197B5F218BBEC1A00F073E5 /* processing */, + E197B5FF18BBEC1A00F073E5 /* protocols */, + E197B60618BBEC1A00F073E5 /* RemoteIOAudio.h */, + E197B60718BBEC1A00F073E5 /* RemoteIOAudio.m */, + E197B60818BBEC1A00F073E5 /* RemoteIOBufferListWrapper.h */, + E197B60918BBEC1A00F073E5 /* RemoteIOBufferListWrapper.m */, + E197B60A18BBEC1A00F073E5 /* SpeexCodec.h */, + E197B60B18BBEC1A00F073E5 /* SpeexCodec.m */, + ); + path = incall_audio; + sourceTree = ""; + }; + E197B5F218BBEC1A00F073E5 /* processing */ = { + isa = PBXGroup; + children = ( + E197B5F318BBEC1A00F073E5 /* AudioProcessor.h */, + E197B5F418BBEC1A00F073E5 /* AudioProcessor.m */, + E197B5F518BBEC1A00F073E5 /* AudioStretcher.h */, + E197B5F618BBEC1A00F073E5 /* AudioStretcher.m */, + E197B5F718BBEC1A00F073E5 /* DesiredBufferDepthController.h */, + E197B5F818BBEC1A00F073E5 /* DesiredBufferDepthController.m */, + E197B5F918BBEC1A00F073E5 /* DropoutTracker.h */, + E197B5FA18BBEC1A00F073E5 /* DropoutTracker.m */, + E197B5FB18BBEC1A00F073E5 /* JitterQueue.h */, + E197B5FC18BBEC1A00F073E5 /* JitterQueue.m */, + E197B5FD18BBEC1A00F073E5 /* StretchFactorController.h */, + E197B5FE18BBEC1A00F073E5 /* StretchFactorController.m */, + ); + path = processing; + sourceTree = ""; + }; + E197B5FF18BBEC1A00F073E5 /* protocols */ = { + isa = PBXGroup; + children = ( + E197B60018BBEC1A00F073E5 /* AudioCallbackHandler.h */, + E197B60118BBEC1A00F073E5 /* BufferDepthMeasure.h */, + E197B60218BBEC1A00F073E5 /* JitterQueueNotificationReceiver.h */, + E197B60318BBEC1A00F073E5 /* utilities */, + ); + path = protocols; + sourceTree = ""; + }; + E197B60318BBEC1A00F073E5 /* utilities */ = { + isa = PBXGroup; + children = ( + E197B60418BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.h */, + E197B60518BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m */, + ); + path = utilities; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D221A088169C9E5E00537ABF /* Signal */ = { + isa = PBXNativeTarget; + buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */; + buildPhases = ( + 493CFB695DB448D8A3C1AE06 /* Check Pods Manifest.lock */, + D221A085169C9E5E00537ABF /* Sources */, + D221A086169C9E5E00537ABF /* Frameworks */, + D221A087169C9E5E00537ABF /* Resources */, + DC8D50B785074F8FA3DBAACE /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 70B800AE190C54870042E3F0 /* PBXTargetDependency */, + 70B800A5190C52F80042E3F0 /* PBXTargetDependency */, + ); + name = Signal; + productName = RedPhone; + productReference = D221A089169C9E5E00537ABF /* Signal.app */; + productType = "com.apple.product-type.application"; + }; + D221A0A9169C9E5F00537ABF /* SignalTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D221A0BF169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "SignalTests" */; + buildPhases = ( + EE182B0CAFB74DD9A2D39FD3 /* Check Pods Manifest.lock */, + D221A0A5169C9E5F00537ABF /* Sources */, + D221A0A6169C9E5F00537ABF /* Frameworks */, + D221A0A7169C9E5F00537ABF /* Resources */, + D221A0A8169C9E5F00537ABF /* ShellScript */, + BA4E2805598B464FB7B24430 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + D221A0B0169C9E5F00537ABF /* PBXTargetDependency */, + ); + name = SignalTests; + productName = RedPhoneTests; + productReference = D221A0AA169C9E5F00537ABF /* SignalTests.octest */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D221A080169C9E5E00537ABF /* Project object */ = { + isa = PBXProject; + attributes = { + LastTestingUpgradeCheck = 0510; + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Open Whisper Systems"; + TargetAttributes = { + D221A088169C9E5E00537ABF = { + DevelopmentTeam = U68MSDN6DR; + SystemCapabilities = { + com.apple.InAppPurchase = { + enabled = 0; + }; + com.apple.InterAppAudio = { + enabled = 0; + }; + }; + }; + }; + }; + buildConfigurationList = D221A083169C9E5E00537ABF /* Build configuration list for PBXProject "Signal" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + fr, + "ca-ES", + "cs-CZ", + de, + "ro-RO", + nl, + "sv-SE", + ); + mainGroup = D221A07E169C9E5E00537ABF; + productRefGroup = D221A08A169C9E5E00537ABF /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 70B8009F190C529C0042E3F0 /* Products */; + ProjectRef = 70B8009E190C529C0042E3F0 /* spandsp.xcodeproj */; + }, + { + ProductGroup = 70B800A8190C54790042E3F0 /* Products */; + ProjectRef = 70B800A7190C54790042E3F0 /* speex.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + D221A088169C9E5E00537ABF /* Signal */, + D221A0A9169C9E5F00537ABF /* SignalTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 70B800A3190C529C0042E3F0 /* libspandsp.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libspandsp.a; + remoteRef = 70B800A2190C529C0042E3F0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 70B800AC190C54790042E3F0 /* libspeex.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libspeex.a; + remoteRef = 70B800AB190C54790042E3F0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + D221A087169C9E5E00537ABF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E148750018A06966002CC4F3 /* CallAudioManagerDemo.xib in Resources */, + E148750118A06966002CC4F3 /* CallLogViewController.xib in Resources */, + E148750218A06966002CC4F3 /* ContactBrowseViewController.xib in Resources */, + E148750318A06966002CC4F3 /* ContactDetailTableViewCell.xib in Resources */, + E148750418A06966002CC4F3 /* ContactDetailViewController.xib in Resources */, + E148750518A06966002CC4F3 /* CountryCodeViewController.xib in Resources */, + E148750618A06966002CC4F3 /* DialerViewController.xib in Resources */, + E148750718A06966002CC4F3 /* DowngradeCallViewController.xib in Resources */, + E148750818A06966002CC4F3 /* FavouritesViewController.xib in Resources */, + E148750918A06966002CC4F3 /* InboxFeedViewController.xib in Resources */, + E148750A18A06966002CC4F3 /* InCallViewController.xib in Resources */, + E148750B18A06966002CC4F3 /* InviteContactsViewController.xib in Resources */, + E148750C18A06966002CC4F3 /* LeftSideMenuViewController.xib in Resources */, + E148750D18A06966002CC4F3 /* PreferenceListViewController.xib in Resources */, + E148750E18A06966002CC4F3 /* RegisterViewController.xib in Resources */, + E148750F18A06966002CC4F3 /* SettingsViewController.xib in Resources */, + E148751018A06966002CC4F3 /* TabBarParentViewController.xib in Resources */, + E14874F718A06951002CC4F3 /* CallLogTableViewCell.xib in Resources */, + E14874F818A06951002CC4F3 /* ContactTableViewCell.xib in Resources */, + E14874F918A06951002CC4F3 /* CountryCodeTableViewCell.xib in Resources */, + E14874FA18A06951002CC4F3 /* FavouriteTableViewCell.xib in Resources */, + E14874FB18A06951002CC4F3 /* InboxFeedFooterCell.xib in Resources */, + E14874FC18A06951002CC4F3 /* InboxFeedTableViewCell.xib in Resources */, + E14874FD18A06951002CC4F3 /* LeftSideMenuCell.xib in Resources */, + E14874FE18A06951002CC4F3 /* PreferenceListTableViewCell.xib in Resources */, + E14874FF18A06951002CC4F3 /* UnseenWhisperUserCell.xib in Resources */, + E14874A218A0692F002CC4F3 /* archive_icon.png in Resources */, + E14874A318A0692F002CC4F3 /* archive_icon@2x.png in Resources */, + E14874A418A0692F002CC4F3 /* backspace.png in Resources */, + E14874A518A0692F002CC4F3 /* backspace@2x.png in Resources */, + E14874A618A0692F002CC4F3 /* checkbox_checkmark.png in Resources */, + E14874A718A0692F002CC4F3 /* checkbox_checkmark@2x.png in Resources */, + E14874A818A0692F002CC4F3 /* checkbox_empty.png in Resources */, + E14874A918A0692F002CC4F3 /* checkbox_empty@2x.png in Resources */, + E14874AA18A0692F002CC4F3 /* contact_default_feed.png in Resources */, + E14874AB18A0692F002CC4F3 /* contacts_arrow.png in Resources */, + E14874AC18A0692F002CC4F3 /* contacts_arrow@2x.png in Resources */, + E14874AD18A0692F002CC4F3 /* DefaultContactImage.png in Resources */, + E14874AE18A0692F002CC4F3 /* dismiss_notification_icon.png in Resources */, + E14874AF18A0692F002CC4F3 /* dismiss_notification_icon@2x.png in Resources */, + E14874B018A0692F002CC4F3 /* drop_down_arrow_icon.png in Resources */, + E14874B118A0692F002CC4F3 /* drop_down_arrow_icon@2x.png in Resources */, + E14874B218A0692F002CC4F3 /* expanded_cell_icon.png in Resources */, + E14874B318A0692F002CC4F3 /* expanded_cell_icon@2x.png in Resources */, + E14874B418A0692F002CC4F3 /* favourite_false_icon.png in Resources */, + E14874B518A0692F002CC4F3 /* favourite_false_icon@2x.png in Resources */, + E14874B618A0692F002CC4F3 /* favourite_true_icon.png in Resources */, + E14874B718A0692F002CC4F3 /* favourite_true_icon@2x.png in Resources */, + E14874B818A0692F002CC4F3 /* forward_button.png in Resources */, + E14874B918A0692F002CC4F3 /* forward_button@2x.png in Resources */, + E14874BA18A0692F002CC4F3 /* home_icon.png in Resources */, + E14874BB18A0692F002CC4F3 /* icon_contacts.png in Resources */, + E14874BC18A0692F002CC4F3 /* icon_favourites.png in Resources */, + E14874BD18A0692F002CC4F3 /* icon_keypad.png in Resources */, + E14874BE18A0692F002CC4F3 /* icon_recents.png in Resources */, + E14874BF18A0692F002CC4F3 /* in_call_phone_icon.png in Resources */, + E14874C018A0692F002CC4F3 /* in_call_phone_icon@2x.png in Resources */, + E14874C118A0692F002CC4F3 /* in_call_phrase_icon.png in Resources */, + E16E5C1518AEDB5A00B7C403 /* phone_icon.png in Resources */, + E14874C218A0692F002CC4F3 /* in_call_phrase_icon@2x.png in Resources */, + E14874C318A0692F002CC4F3 /* incoming_call_icon.png in Resources */, + E14874C418A0692F002CC4F3 /* incoming_call_icon@2x.png in Resources */, + E14874C518A0692F002CC4F3 /* menu_icon.png in Resources */, + E14874C618A0692F002CC4F3 /* menu_icon@2x.png in Resources */, + E14874C718A0692F002CC4F3 /* message_bubble.png in Resources */, + E14874C818A0692F002CC4F3 /* message_bubble@2x.png in Resources */, + B6B6C3C71919440C00C0B76B /* Localizable.strings in Resources */, + E14874C918A0692F002CC4F3 /* mute_icon_selected.png in Resources */, + E14874CA18A0692F002CC4F3 /* mute_icon_selected@2x.png in Resources */, + E14874CB18A0692F002CC4F3 /* mute_icon.png in Resources */, + E14874CC18A0692F002CC4F3 /* mute_icon@2x.png in Resources */, + E14874CD18A0692F002CC4F3 /* notification_detail_icon.png in Resources */, + E14874CE18A0692F002CC4F3 /* notification_detail_icon@2x.png in Resources */, + E14874CF18A0692F002CC4F3 /* notification_mini_icon.png in Resources */, + E14874D018A0692F002CC4F3 /* notification_mini_icon@2x.png in Resources */, + E14874D118A0692F002CC4F3 /* outgoing_call_icon.png in Resources */, + E14874D218A0692F002CC4F3 /* outgoing_call_icon@2x.png in Resources */, + E14874D318A0692F002CC4F3 /* search_cancel.png in Resources */, + E14874D418A0692F002CC4F3 /* search_cancel@2x.png in Resources */, + E14874D518A0692F002CC4F3 /* search_icon.png in Resources */, + E14874D618A0692F002CC4F3 /* search_icon@2x.png in Resources */, + E14874D718A0692F002CC4F3 /* send_code_icon.png in Resources */, + E14874D818A0692F002CC4F3 /* send_code_icon@2x.png in Resources */, + E14874D918A0692F002CC4F3 /* speaker_icon_selected.png in Resources */, + E14874DA18A0692F002CC4F3 /* speaker_icon_selected@2x.png in Resources */, + E14874DB18A0692F002CC4F3 /* speaker_icon.png in Resources */, + E14874DC18A0692F002CC4F3 /* speaker_icon@2x.png in Resources */, + E14874DD18A0692F002CC4F3 /* spinner_connecting_flash.png in Resources */, + E14874DE18A0692F002CC4F3 /* spinner_connecting_flash@2x.png in Resources */, + E14874DF18A06930002CC4F3 /* spinner_connecting.png in Resources */, + E14874E018A06930002CC4F3 /* spinner_connecting@2x.png in Resources */, + E14874E118A06930002CC4F3 /* spinner_error.png in Resources */, + E14874E218A06930002CC4F3 /* spinner_error@2x.png in Resources */, + E14874E318A06930002CC4F3 /* spinner_ringing.png in Resources */, + E14874E418A06930002CC4F3 /* spinner_ringing@2x.png in Resources */, + E14874E518A06930002CC4F3 /* tab_icon_contacts.png in Resources */, + E14874E618A06930002CC4F3 /* tab_icon_contacts@2x.png in Resources */, + E14874E718A06930002CC4F3 /* tab_icon_favourites.png in Resources */, + E14874E818A06930002CC4F3 /* tab_icon_favourites@2x.png in Resources */, + E14874E918A06930002CC4F3 /* tab_icon_inbox.png in Resources */, + E14874EA18A06930002CC4F3 /* tab_icon_inbox@2x.png in Resources */, + E14874EB18A06930002CC4F3 /* tab_icon_keypad.png in Resources */, + E14874EC18A06930002CC4F3 /* tab_icon_keypad@2x.png in Resources */, + E14874ED18A06930002CC4F3 /* tab_icon_menu.png in Resources */, + E14874EE18A06930002CC4F3 /* tab_icon_menu@2x.png in Resources */, + E14874EF18A06930002CC4F3 /* trash_icon.png in Resources */, + E14874F018A06930002CC4F3 /* trash_icon@2x.png in Resources */, + E14874F118A06930002CC4F3 /* volume_high.png in Resources */, + E14874F218A06930002CC4F3 /* volume_high@2x.png in Resources */, + 70B8FEE21909FE360042E3F0 /* 171756__nenadsimic__picked-coin-echo-2.wav in Resources */, + E14874F318A06930002CC4F3 /* volume_low.png in Resources */, + E14874F418A06930002CC4F3 /* volume_low@2x.png in Resources */, + E14874F518A06930002CC4F3 /* whisper_notification_icon.png in Resources */, + E14874F618A06930002CC4F3 /* whisper_notification_icon@2x.png in Resources */, + E1370BF618A068A600826894 /* whisperReal.der in Resources */, + E1370BEA18A0689000826894 /* AppIcon29x29.jpg in Resources */, + E1370BEB18A0689000826894 /* AppIcon29x29.png in Resources */, + E1370BEC18A0689000826894 /* AppIcon29x29@2x.png in Resources */, + E1370BED18A0689000826894 /* AppIcon40x40.png in Resources */, + E1370BEE18A0689000826894 /* AppIcon40x40@2x.png in Resources */, + E16E5C1418AEDB5A00B7C403 /* message_icon.png in Resources */, + E1370BEF18A0689000826894 /* AppIcon60x60.png in Resources */, + E1370BF018A0689000826894 /* AppIcon60x60@2x.png in Resources */, + E1370BF118A0689000826894 /* AppIcon76x76.png in Resources */, + E1370BF218A0689000826894 /* AppIcon76x76@2x.png in Resources */, + E1370BE718A0688300826894 /* Default-568h@2x.png in Resources */, + E1370BE818A0688300826894 /* Default.png in Resources */, + E1370BE918A0688300826894 /* Default@2x.png in Resources */, + E1370BE018A0686600826894 /* busy.mp3 in Resources */, + E1370BE118A0686C00826894 /* completed.mp3 in Resources */, + E1370BE218A0686C00826894 /* failure.mp3 in Resources */, + E1370BE318A0686C00826894 /* handshake.mp3 in Resources */, + B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */, + E1370BE418A0686C00826894 /* outring.mp3 in Resources */, + E1370BE518A0686C00826894 /* r.caf in Resources */, + E1370BE618A0686C00826894 /* sonarping.mp3 in Resources */, + E148751218A06AFD002CC4F3 /* HelveticaNeueLTStd-Bd.otf in Resources */, + E148751318A06AFD002CC4F3 /* HelveticaNeueLTStd-Th.otf in Resources */, + E148751418A06AFD002CC4F3 /* HelveticaNeueLTStd-Lt.otf in Resources */, + E148751518A06AFD002CC4F3 /* HelveticaNeueLTStd-Md.otf in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D221A0A7169C9E5F00537ABF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B96A3100187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf in Resources */, + 76EB067518170B34006006FC /* FavouritesViewController.xib in Resources */, + 76EB069718170B34006006FC /* InboxFeedTableViewCell.xib in Resources */, + 765052AC18294C9F008313E1 /* HelveticaNeueLTStd-Md.otf in Resources */, + 76EB060118170B33006006FC /* InitiateSignal.proto in Resources */, + 76EB066718170B34006006FC /* TabBarParentViewController.xib in Resources */, + 76C87F17181EE2EB00C4ACAB /* InboxFeedFooterCell.xib in Resources */, + 765052AA18294C9F008313E1 /* HelveticaNeueLTStd-Lt.otf in Resources */, + B9CA51BD18809ACA007E204E /* InviteContactsViewController.xib in Resources */, + B97CBFAB18860EA3008E0DE9 /* CountryCodeViewController.xib in Resources */, + 76EB067B18170B34006006FC /* LeftSideMenuViewController.xib in Resources */, + B9EB5ACA1884D387007CBB57 /* UnseenWhisperUserCell.xib in Resources */, + 76EB067118170B34006006FC /* DialerViewController.xib in Resources */, + 76EB067918170B34006006FC /* InCallViewController.xib in Resources */, + 76EB068318170B34006006FC /* SettingsViewController.xib in Resources */, + 76EB068118170B34006006FC /* RegisterViewController.xib in Resources */, + B97CBFB218861023008E0DE9 /* CountryCodeTableViewCell.xib in Resources */, + B9A578B5183D610300C17105 /* FavouriteTableViewCell.xib in Resources */, + B9B89C58185A2B7000A24465 /* LeftSideMenuCell.xib in Resources */, + 76D713EB182D3E5100C9C9C8 /* PreferenceListTableViewCell.xib in Resources */, + 76EB066F18170B34006006FC /* ContactDetailViewController.xib in Resources */, + 76EB069318170B34006006FC /* ContactTableViewCell.xib in Resources */, + 76EB067718170B34006006FC /* InboxFeedViewController.xib in Resources */, + 76EB066918170B34006006FC /* CallAudioManagerDemo.xib in Resources */, + 76EB069D18170B34006006FC /* CallLogTableViewCell.xib in Resources */, + 76EB067D18170B34006006FC /* CallLogViewController.xib in Resources */, + B942EB10183AC467000887BB /* ContactBrowseViewController.xib in Resources */, + 765052B3182BF839008313E1 /* HelveticaNeueLTStd-Th.otf in Resources */, + 76B818A1182C39460088060E /* PreferenceListViewController.xib in Resources */, + 76EB066D18170B34006006FC /* ContactDetailTableViewCell.xib in Resources */, + 76EB067318170B34006006FC /* DowngradeCallViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 493CFB695DB448D8A3C1AE06 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + BA4E2805598B464FB7B24430 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D221A0A8169C9E5F00537ABF /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; + }; + DC8D50B785074F8FA3DBAACE /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + EE182B0CAFB74DD9A2D39FD3 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D221A085169C9E5E00537ABF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76EB064E18170B34006006FC /* ContactDetailViewController.m in Sources */, + 76EB063E18170B33006006FC /* Operation.m in Sources */, + 76EB05F618170B33006006FC /* CallConnectUtil.m in Sources */, + 76EB061218170B33006006FC /* LoggingUtil.m in Sources */, + 76EB060E18170B33006006FC /* DecayingSampleEstimator.m in Sources */, + 76EB05BA18170B33006006FC /* CommitPacket.m in Sources */, + 76EB060218170B33006006FC /* InitiatorSessionDescriptor.m in Sources */, + 76EB05FC18170B33006006FC /* CallConnectUtil_Server.m in Sources */, + 76EB062418170B33006006FC /* PriorityQueue.m in Sources */, + 76EB059818170B33006006FC /* HttpRequestOrResponse.m in Sources */, + 76EB061A18170B33006006FC /* DiscardingLog.m in Sources */, + 76EB068418170B34006006FC /* ContactDetailTableViewCell.m in Sources */, + 76EB066218170B34006006FC /* SettingsViewController.m in Sources */, + 76EB054818170B33006006FC /* CancelTokenSource.m in Sources */, + 76EB05AC18170B33006006FC /* SrtpSocket.m in Sources */, + 76EB062A18170B33006006FC /* BadState.m in Sources */, + B97940271832BD2400BD66CB /* UIUtil.m in Sources */, + 76EB05BE18170B33006006FC /* ConfirmPacket.m in Sources */, + 76EB058618170B33006006FC /* PreferencesUtil.m in Sources */, + 76EB05A818170B33006006FC /* RtpSocket.m in Sources */, + 70B80119190C55660042E3F0 /* TextFormat.m in Sources */, + E197B61818BBEC1A00F073E5 /* RemoteIOAudio.m in Sources */, + 70B8011C190C55660042E3F0 /* Utilities.m in Sources */, + 76EB059418170B33006006FC /* HttpManager.m in Sources */, + 76EB05EC18170B33006006FC /* CallState.m in Sources */, + 76EB05D218170B33006006FC /* ZrtpInitiator.m in Sources */, + 76EB05E018170B33006006FC /* NetworkStream.m in Sources */, + 76EB05D618170B33006006FC /* ZrtpResponder.m in Sources */, + 70B8010E190C55660042E3F0 /* CodedInputStream.m in Sources */, + 7095B7B018F46D35002C66E2 /* PhoneNumberUtil.m in Sources */, + 70B8010F190C55660042E3F0 /* CodedOutputStream.m in Sources */, + 70B8010C190C55660042E3F0 /* AbstractMessage.m in Sources */, + E197B61618BBEC1A00F073E5 /* StretchFactorController.m in Sources */, + 76EB065018170B34006006FC /* DialerViewController.m in Sources */, + 701231B518ECAA4500D456C4 /* EvpMessageDigest.m in Sources */, + 76EB062218170B33006006FC /* CyclicalBuffer.m in Sources */, + 76EB063C18170B33006006FC /* NumberUtil.m in Sources */, + 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */, + 70B80117190C55660042E3F0 /* MutableExtensionRegistry.m in Sources */, + 76EB060A18170B33006006FC /* SignalUtil.m in Sources */, + 76EB062818170B33006006FC /* BadArgument.m in Sources */, + 76EB062E18170B33006006FC /* SecurityFailure.m in Sources */, + 76EB05F218170B33006006FC /* PhoneNumber.m in Sources */, + E197B61718BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m in Sources */, + 76EB05BC18170B33006006FC /* ConfirmAckPacket.m in Sources */, + 76EB060C18170B33006006FC /* CategorizingLogger.m in Sources */, + 76EB058A18170B33006006FC /* Release.m in Sources */, + 76EB061018170B33006006FC /* EventWindow.m in Sources */, + E197B62718BBF63B00F073E5 /* SoundBoard.m in Sources */, + 76EB058418170B33006006FC /* LocalizableText.m in Sources */, + 76EB057A18170B33006006FC /* ContactsManager.m in Sources */, + E197B61918BBEC1A00F073E5 /* RemoteIOBufferListWrapper.m in Sources */, + 76EB05A618170B33006006FC /* RtpPacket.m in Sources */, + 76EB064218170B33006006FC /* StringUtil.m in Sources */, + 76EB065A18170B34006006FC /* NextResponderScrollView.m in Sources */, + 76EB062618170B33006006FC /* Queue.m in Sources */, + D221A09A169C9E5E00537ABF /* main.m in Sources */, + B6BAB53F1918CA4100E4DF53 /* KeychainWrapper.m in Sources */, + 76EB061618170B33006006FC /* AnonymousOccurrenceLogger.m in Sources */, + 76EB063018170B33006006FC /* Conversions.m in Sources */, + 76EB065618170B34006006FC /* InCallViewController.m in Sources */, + 76EB05FE18170B33006006FC /* InitiateSignal.pb.m in Sources */, + 76EB064C18170B34006006FC /* ContactBrowseViewController.m in Sources */, + 76EB05CA18170B33006006FC /* RecipientUnavailable.m in Sources */, + 70B80115190C55660042E3F0 /* GeneratedMessage.m in Sources */, + E197B61418BBEC1A00F073E5 /* DropoutTracker.m in Sources */, + 76EB062C18170B33006006FC /* OperationFailed.m in Sources */, + 707E549218FF26E800C8649D /* SmsInvite.m in Sources */, + 76EB05DA18170B33006006FC /* LowLatencyConnector.m in Sources */, + 76EB05EE18170B33006006FC /* CallTermination.m in Sources */, + E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */, + 76EB05B418170B33006006FC /* HashChain.m in Sources */, + 76EB05E418170B33006006FC /* UdpSocket.m in Sources */, + 76EB058218170B33006006FC /* Environment.m in Sources */, + 76EB064418170B33006006FC /* ThreadManager.m in Sources */, + 70B8011A190C55660042E3F0 /* UnknownFieldSet.m in Sources */, + 76B8189E182C39460088060E /* PreferenceListViewController.m in Sources */, + 70B80114190C55660042E3F0 /* Field.m in Sources */, + E197B61E18BBEC6D00F073E5 /* AudioRouter.m in Sources */, + E197B60D18BBEC1A00F073E5 /* AudioSocket.m in Sources */, + 76EB061418170B33006006FC /* AnonymousConditionLogger.m in Sources */, + 76EB054218170B33006006FC /* AsyncUtil.m in Sources */, + 76EB054A18170B33006006FC /* Future.m in Sources */, + 76EB05C018170B33006006FC /* DhPacket.m in Sources */, + 765052A1182945EF008313E1 /* LocalizableCustomFontLabel.m in Sources */, + 7038632818F70C0700D4A43F /* EvpSymetricUtil.m in Sources */, + 76EB066418170B34006006FC /* TabBarParentViewController.m in Sources */, + 76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */, + 76EB05A018170B33006006FC /* IpAddress.m in Sources */, + 70B8011B190C55660042E3F0 /* UnknownFieldSet_Builder.m in Sources */, + B9A578B1183D60EE00C17105 /* FavouriteTableViewCell.m in Sources */, + 76EB057618170B33006006FC /* Contact.m in Sources */, + 70B80111190C55660042E3F0 /* ExtendableMessage.m in Sources */, + 76EB054618170B33006006FC /* CancelledToken.m in Sources */, + E197B61118BBEC1A00F073E5 /* AudioProcessor.m in Sources */, + 76EB065818170B34006006FC /* LeftSideMenuViewController.m in Sources */, + 76EB05EA18170B33006006FC /* CallProgress.m in Sources */, + 76EB05C218170B33006006FC /* DhPacketSharedSecretHashes.m in Sources */, + 76EB063218170B33006006FC /* Crc32.m in Sources */, + E197B62418BBF5BB00F073E5 /* SoundPlayer.m in Sources */, + E197B61018BBEC1A00F073E5 /* EncodedAudioPacket.m in Sources */, + 76C87F13181EE11C00C4ACAB /* InboxFeedFooterCell.m in Sources */, + 76EB063618170B33006006FC /* DataUtil.m in Sources */, + 76EB059C18170B33006006FC /* HttpResponse.m in Sources */, + E197B60C18BBEC1A00F073E5 /* AudioPacker.m in Sources */, + 76EB055018170B33006006FC /* ObservableValue.m in Sources */, + E197B61218BBEC1A00F073E5 /* AudioStretcher.m in Sources */, + B68DF7B71918D7FC00C7BAB9 /* KeyChainStorage.m in Sources */, + 76EB05A218170B33006006FC /* IpEndPoint.m in Sources */, + 70B8010D190C55660042E3F0 /* AbstractMessage_Builder.m in Sources */, + E197B61A18BBEC1A00F073E5 /* SpeexCodec.m in Sources */, + 70B80118190C55660042E3F0 /* MutableField.m in Sources */, + 762D9DCF18281C7400A5E418 /* SettingsTableHeaderView.m in Sources */, + 76EB05F018170B33006006FC /* PhoneManager.m in Sources */, + E197B60F18BBEC1A00F073E5 /* EncodedAudioFrame.m in Sources */, + 70B8011D190C55660042E3F0 /* WireFormat.m in Sources */, + 76D713E7182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m in Sources */, + 76EB061818170B33006006FC /* AnonymousValueLogger.m in Sources */, + 76EB05E618170B33006006FC /* CallController.m in Sources */, + E16E5BEE18AAC40200B7C403 /* EC25KeyAgreementParticipant.m in Sources */, + 76EB057418170B33006006FC /* RecentCallManager.m in Sources */, + 76EB061C18170B33006006FC /* ArrayUtil.m in Sources */, + 76EB05C418170B33006006FC /* HandshakePacket.m in Sources */, + 76EB05AA18170B33006006FC /* SequenceCounter.m in Sources */, + 76EB055218170B33006006FC /* TimeoutFailure.m in Sources */, + 7038632718F70C0700D4A43F /* CryptoTools.m in Sources */, + 76EB058C18170B33006006FC /* DnsManager.m in Sources */, + 76EB059018170B33006006FC /* IgnoredPacketFailure.m in Sources */, + B942EB0E183A9633000887BB /* SearchBarTitleView.m in Sources */, + 765052AF182AC9B5008313E1 /* DialerButtonView.m in Sources */, + 70B80110190C55660042E3F0 /* ConcreteExtensionField.m in Sources */, + 70B80112190C55660042E3F0 /* ExtendableMessage_Builder.m in Sources */, + 76EB05D418170B33006006FC /* ZrtpManager.m in Sources */, + 76EB058E18170B33006006FC /* HostNameEndPoint.m in Sources */, + E19167A418A9687800B7A468 /* DH3KKeyAgreementParticipant.m in Sources */, + B9CA51BA18809ACA007E204E /* InviteContactsViewController.m in Sources */, + 76EB065218170B34006006FC /* FavouritesViewController.m in Sources */, + E16E5BF018AAC40200B7C403 /* EvpKeyAgreement.m in Sources */, + 70B80113190C55660042E3F0 /* ExtensionRegistry.m in Sources */, + B9EB5AC61884D370007CBB57 /* UnseenWhisperUserCell.m in Sources */, + 76EB05DC18170B33006006FC /* StreamPair.m in Sources */, + 70377AA91916BA0500CAF501 /* InteractiveLabel.m in Sources */, + 76EB064618170B33006006FC /* TimeUtil.m in Sources */, + 70BAFD5D190584BE00FA5E0B /* NotificationTracker.m in Sources */, + 76EB054E18170B33006006FC /* FutureUtil.m in Sources */, + 76EB059618170B33006006FC /* HttpRequest.m in Sources */, + 76EB05A418170B33006006FC /* PacketHandler.m in Sources */, + E197B62118BBF12700F073E5 /* AppAudioManager.m in Sources */, + 76EB068C18170B34006006FC /* InboxFeedTableViewCell.m in Sources */, + 76EB062018170B33006006FC /* BloomFilter.m in Sources */, + 76EB054C18170B33006006FC /* FutureSource.m in Sources */, + 76EB063818170B33006006FC /* DictionaryUtil.m in Sources */, + B9B89C54185A2B5F00A24465 /* LeftSideMenuCell.m in Sources */, + 76EB068E18170B34006006FC /* CallLogTableViewCell.m in Sources */, + 76EB05CE18170B33006006FC /* ZrtpHandshakeResult.m in Sources */, + 76EB05B618170B33006006FC /* MasterSecret.m in Sources */, + 76EB05F418170B33006006FC /* CallConnectResult.m in Sources */, + 76EB059E18170B33006006FC /* HttpSocket.m in Sources */, + E197B60E18BBEC1A00F073E5 /* CallAudioManager.m in Sources */, + 76EB065418170B34006006FC /* InboxFeedViewController.m in Sources */, + 76EB054018170B33006006FC /* AppDelegate.m in Sources */, + 76EB05D018170B33006006FC /* ZrtpHandshakeSocket.m in Sources */, + E197B61518BBEC1A00F073E5 /* JitterQueue.m in Sources */, + 76EB05C818170B33006006FC /* HelloPacket.m in Sources */, + 76EB059A18170B33006006FC /* HttpRequestUtil.m in Sources */, + 76EB057218170B33006006FC /* RecentCall.m in Sources */, + 76EB054418170B33006006FC /* AsyncUtilHelperRacingOperation.m in Sources */, + 76EB060418170B33006006FC /* PhoneNumberDirectoryFilter.m in Sources */, + B97CBFA818860EA3008E0DE9 /* CountryCodeViewController.m in Sources */, + 76EB059218170B33006006FC /* UnrecognizedRequestFailure.m in Sources */, + 76EB05F818170B33006006FC /* CallConnectUtil_Initiator.m in Sources */, + B97CBFAE1886100E008E0DE9 /* CountryCodeTableViewCell.m in Sources */, + 76EB05CC18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */, + E16E5BEF18AAC40200B7C403 /* EC25KeyAgreementProtocol.m in Sources */, + 76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */, + 707E548C18FF0B8A00C8649D /* InviteContactModal.m in Sources */, + 76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */, + 76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */, + 76EB066018170B34006006FC /* RegisterViewController.m in Sources */, + 76EB060618170B33006006FC /* PhoneNumberDirectoryFilterManager.m in Sources */, + 76EB060818170B33006006FC /* ResponderSessionDescriptor.m in Sources */, + B90418E6183E9DD40038554A /* DateUtil.m in Sources */, + 76EB05C618170B33006006FC /* HelloAckPacket.m in Sources */, + 76EB05E818170B33006006FC /* CallFailedServerMessage.m in Sources */, + 76EB05FA18170B33006006FC /* CallConnectUtil_Responder.m in Sources */, + 76EB05AE18170B33006006FC /* SrtpStream.m in Sources */, + E197B61318BBEC1A00F073E5 /* DesiredBufferDepthController.m in Sources */, + 76EB064818170B33006006FC /* Zid.m in Sources */, + 76EB05E218170B33006006FC /* SecureEndPoint.m in Sources */, + 76EB05DE18170B33006006FC /* Certificate.m in Sources */, + 76EB05B818170B33006006FC /* NegotiationFailed.m in Sources */, + 76EB05D818170B33006006FC /* LowLatencyCandidate.m in Sources */, + 76EB065C18170B34006006FC /* CallLogViewController.m in Sources */, + 70B80116190C55660042E3F0 /* GeneratedMessage_Builder.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D221A0A5169C9E5F00537ABF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76EB05ED18170B33006006FC /* CallState.m in Sources */, + 76EB064918170B33006006FC /* Zid.m in Sources */, + 76EB05AF18170B33006006FC /* SrtpStream.m in Sources */, + 76EB061318170B33006006FC /* LoggingUtil.m in Sources */, + 76EB064F18170B34006006FC /* ContactDetailViewController.m in Sources */, + 76EB05A718170B33006006FC /* RtpPacket.m in Sources */, + 76EB05BF18170B33006006FC /* ConfirmPacket.m in Sources */, + 76EB05E518170B33006006FC /* UdpSocket.m in Sources */, + 76EB05BD18170B33006006FC /* ConfirmAckPacket.m in Sources */, + 76B8189F182C39460088060E /* PreferenceListViewController.m in Sources */, + 76EB05DB18170B33006006FC /* LowLatencyConnector.m in Sources */, + 76EB060F18170B33006006FC /* DecayingSampleEstimator.m in Sources */, + 76EB055318170B33006006FC /* TimeoutFailure.m in Sources */, + 76EB057718170B33006006FC /* Contact.m in Sources */, + B9EB5AC71884D370007CBB57 /* UnseenWhisperUserCell.m in Sources */, + 76EB05FB18170B33006006FC /* CallConnectUtil_Responder.m in Sources */, + 76EB063718170B33006006FC /* DataUtil.m in Sources */, + 76EB059718170B33006006FC /* HttpRequest.m in Sources */, + 76EB05C918170B33006006FC /* HelloPacket.m in Sources */, + B90418E7183E9DD40038554A /* DateUtil.m in Sources */, + 76EB05CB18170B33006006FC /* RecipientUnavailable.m in Sources */, + 76EB061B18170B33006006FC /* DiscardingLog.m in Sources */, + B9B89C55185A2B5F00A24465 /* LeftSideMenuCell.m in Sources */, + 76EB05FD18170B33006006FC /* CallConnectUtil_Server.m in Sources */, + 76EB058B18170B33006006FC /* Release.m in Sources */, + A157075417F0CD6D007C2BD6 /* AsyncUtilTest.m in Sources */, + A157075517F0CD6D007C2BD6 /* FutureSourceTest.m in Sources */, + 76D713E8182D3E3F00C9C9C8 /* PreferenceListTableViewCell.m in Sources */, + 76EB05D318170B33006006FC /* ZrtpInitiator.m in Sources */, + B9A578B2183D60EE00C17105 /* FavouriteTableViewCell.m in Sources */, + 76EB061518170B33006006FC /* AnonymousConditionLogger.m in Sources */, + 76EB060718170B33006006FC /* PhoneNumberDirectoryFilterManager.m in Sources */, + 76EB05A118170B33006006FC /* IpAddress.m in Sources */, + A157075617F0CD6D007C2BD6 /* ObservableTest.m in Sources */, + 762D9DD018281C7400A5E418 /* SettingsTableHeaderView.m in Sources */, + 76EB05CD18170B33006006FC /* ShortAuthenticationStringGenerator.m in Sources */, + 76EB05B718170B33006006FC /* MasterSecret.m in Sources */, + 76EB05DF18170B33006006FC /* Certificate.m in Sources */, + 76EB05D918170B33006006FC /* LowLatencyCandidate.m in Sources */, + A157075717F0CD6D007C2BD6 /* AudioFrameTest.m in Sources */, + 76EB063F18170B33006006FC /* Operation.m in Sources */, + 76EB05AB18170B33006006FC /* SequenceCounter.m in Sources */, + 76EB054B18170B33006006FC /* Future.m in Sources */, + 76EB061D18170B33006006FC /* ArrayUtil.m in Sources */, + 76EB064D18170B34006006FC /* ContactBrowseViewController.m in Sources */, + 76EB05E318170B33006006FC /* SecureEndPoint.m in Sources */, + 76EB060D18170B33006006FC /* CategorizingLogger.m in Sources */, + A157075817F0CD6D007C2BD6 /* AudioRemoteIOTest.m in Sources */, + 76EB05DD18170B33006006FC /* StreamPair.m in Sources */, + 76EB065318170B34006006FC /* FavouritesViewController.m in Sources */, + 76EB059518170B33006006FC /* HttpManager.m in Sources */, + 76EB05A318170B33006006FC /* IpEndPoint.m in Sources */, + 76EB054118170B33006006FC /* AppDelegate.m in Sources */, + A157075917F0CD6D007C2BD6 /* AudioStretcherTest.m in Sources */, + 76EB05B918170B33006006FC /* NegotiationFailed.m in Sources */, + A157075A17F0CD6D007C2BD6 /* JitterQueueTest.m in Sources */, + 76EB060318170B33006006FC /* InitiatorSessionDescriptor.m in Sources */, + 76EB059D18170B33006006FC /* HttpResponse.m in Sources */, + A157075B17F0CD6D007C2BD6 /* SpeexCodecTest.m in Sources */, + A157075D17F0CD6D007C2BD6 /* DnsManagerTest.m in Sources */, + 76EB068518170B34006006FC /* ContactDetailTableViewCell.m in Sources */, + A157075E17F0CD6D007C2BD6 /* HttpRequestResponseTest.m in Sources */, + 76919BF71805D190008C664A /* ContactManagerTest.m in Sources */, + 76EB05D118170B33006006FC /* ZrtpHandshakeSocket.m in Sources */, + A157075F17F0CD6D007C2BD6 /* IpAddressTest.m in Sources */, + 76EB05F518170B33006006FC /* CallConnectResult.m in Sources */, + A157076017F0CD6D007C2BD6 /* IpEndPointTest.m in Sources */, + 765052A2182945EF008313E1 /* LocalizableCustomFontLabel.m in Sources */, + A157076117F0CD6D007C2BD6 /* RtpPacketTests.m in Sources */, + 76EB05C518170B33006006FC /* HandshakePacket.m in Sources */, + 76EB054D18170B33006006FC /* FutureSource.m in Sources */, + 76EB058F18170B33006006FC /* HostNameEndPoint.m in Sources */, + 76EB062118170B33006006FC /* BloomFilter.m in Sources */, + 76EB064518170B33006006FC /* ThreadManager.m in Sources */, + 76EB068718170B34006006FC /* ContactTableViewCell.m in Sources */, + 76EB063B18170B33006006FC /* FunctionalUtil.m in Sources */, + A157076217F0CD6D007C2BD6 /* SecureStreamTest.m in Sources */, + A157076317F0CD6D007C2BD6 /* SequenceCounterTest.m in Sources */, + A157076417F0CD6D007C2BD6 /* DH3KAgreerTest.m in Sources */, + 76EB062318170B33006006FC /* CyclicalBuffer.m in Sources */, + A157076517F0CD6D007C2BD6 /* HandshakePacketTest.m in Sources */, + 76EB054918170B33006006FC /* CancelTokenSource.m in Sources */, + 76EB066518170B34006006FC /* TabBarParentViewController.m in Sources */, + A157076617F0CD6D007C2BD6 /* HashChainTest.m in Sources */, + 76EB063118170B33006006FC /* Conversions.m in Sources */, + 76EB05F918170B33006006FC /* CallConnectUtil_Initiator.m in Sources */, + A157076717F0CD6D007C2BD6 /* MasterSecretTest.m in Sources */, + 76EB059B18170B33006006FC /* HttpRequestUtil.m in Sources */, + 76EB05CF18170B33006006FC /* ZrtpHandshakeResult.m in Sources */, + 76EB060518170B33006006FC /* PhoneNumberDirectoryFilter.m in Sources */, + 76EB054718170B33006006FC /* CancelledToken.m in Sources */, + 76EB065D18170B34006006FC /* CallLogViewController.m in Sources */, + A157076817F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m in Sources */, + 76EB065718170B34006006FC /* InCallViewController.m in Sources */, + A157076917F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m in Sources */, + 76EB062918170B33006006FC /* BadArgument.m in Sources */, + A157076A17F0CD6D007C2BD6 /* ZrtpTest.m in Sources */, + A157076B17F0CD6D007C2BD6 /* LowLatencyConnectorTest.m in Sources */, + 76EB061718170B33006006FC /* AnonymousOccurrenceLogger.m in Sources */, + 76EB05F118170B33006006FC /* PhoneManager.m in Sources */, + 76EB05F718170B33006006FC /* CallConnectUtil.m in Sources */, + 76EB066318170B34006006FC /* SettingsViewController.m in Sources */, + 76EB054518170B33006006FC /* AsyncUtilHelperRacingOperation.m in Sources */, + 76EB063318170B33006006FC /* Crc32.m in Sources */, + 76EB054F18170B33006006FC /* FutureUtil.m in Sources */, + 76EB062718170B33006006FC /* Queue.m in Sources */, + 76EB05C118170B33006006FC /* DhPacket.m in Sources */, + 76EB060B18170B33006006FC /* SignalUtil.m in Sources */, + A157076C17F0CD6D007C2BD6 /* NetworkStreamTest.m in Sources */, + 76EB065B18170B34006006FC /* NextResponderScrollView.m in Sources */, + 76EB05BB18170B33006006FC /* CommitPacket.m in Sources */, + A157076D17F0CD6D007C2BD6 /* SecureEndPointTest.m in Sources */, + A157076E17F0CD6D007C2BD6 /* UdpSocketTest.m in Sources */, + 76EB05C318170B33006006FC /* DhPacketSharedSecretHashes.m in Sources */, + 76EB05C718170B33006006FC /* HelloAckPacket.m in Sources */, + 76EB059918170B33006006FC /* HttpRequestOrResponse.m in Sources */, + A157076F17F0CD6D007C2BD6 /* PhoneNumberTest.m in Sources */, + 76EB05B518170B33006006FC /* HashChain.m in Sources */, + 76C87F14181EE11C00C4ACAB /* InboxFeedFooterCell.m in Sources */, + A157077017F0CD6D007C2BD6 /* SessionDescriptorTest.m in Sources */, + 76EB05B318170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */, + 76EB05AD18170B33006006FC /* SrtpSocket.m in Sources */, + 76EB058D18170B33006006FC /* DnsManager.m in Sources */, + A157077117F0CD6D007C2BD6 /* DecayingSampleEstimatorTest.m in Sources */, + A157077217F0CD6D007C2BD6 /* EventWindowTest.m in Sources */, + B97CBFAF1886100E008E0DE9 /* CountryCodeTableViewCell.m in Sources */, + A157077417F0CD6D007C2BD6 /* TestUtil.m in Sources */, + A157077517F0CD6D007C2BD6 /* BloomFilterTest.m in Sources */, + 76EB05E918170B33006006FC /* CallFailedServerMessage.m in Sources */, + 76EB058718170B33006006FC /* PreferencesUtil.m in Sources */, + A157077617F0CD6D007C2BD6 /* CancelTokenTest.m in Sources */, + 76EB064118170B33006006FC /* AnonymousTerminator.m in Sources */, + A157077717F0CD6D007C2BD6 /* ConversionsTest.m in Sources */, + 76EB058318170B33006006FC /* Environment.m in Sources */, + 76EB055118170B33006006FC /* ObservableValue.m in Sources */, + A157077817F0CD6D007C2BD6 /* Crc32Test.m in Sources */, + 76EB059318170B33006006FC /* UnrecognizedRequestFailure.m in Sources */, + B97CBFA918860EA3008E0DE9 /* CountryCodeViewController.m in Sources */, + 76EB060918170B33006006FC /* ResponderSessionDescriptor.m in Sources */, + 76EB062518170B33006006FC /* PriorityQueue.m in Sources */, + A157077917F0CD6D007C2BD6 /* CryptoUtilTest.m in Sources */, + 76EB066118170B34006006FC /* RegisterViewController.m in Sources */, + 76EB068D18170B34006006FC /* InboxFeedTableViewCell.m in Sources */, + 76EB05A918170B33006006FC /* RtpSocket.m in Sources */, + 765052B0182AC9B5008313E1 /* DialerButtonView.m in Sources */, + 76EB062B18170B33006006FC /* BadState.m in Sources */, + 76EB062F18170B33006006FC /* SecurityFailure.m in Sources */, + 76EB064318170B33006006FC /* StringUtil.m in Sources */, + 76EB057B18170B33006006FC /* ContactsManager.m in Sources */, + 76EB05E718170B33006006FC /* CallController.m in Sources */, + 76EB05A518170B33006006FC /* PacketHandler.m in Sources */, + A157077A17F0CD6D007C2BD6 /* CyclicalBufferTest.m in Sources */, + 76EB05D518170B33006006FC /* ZrtpManager.m in Sources */, + 76EB061118170B33006006FC /* EventWindow.m in Sources */, + 76EB05FF18170B33006006FC /* InitiateSignal.pb.m in Sources */, + 76EB057518170B33006006FC /* RecentCallManager.m in Sources */, + 76EB059F18170B33006006FC /* HttpSocket.m in Sources */, + 76EB062D18170B33006006FC /* OperationFailed.m in Sources */, + 76EB068F18170B34006006FC /* CallLogTableViewCell.m in Sources */, + B9CA51BB18809ACA007E204E /* InviteContactsViewController.m in Sources */, + 76EB05EB18170B33006006FC /* CallProgress.m in Sources */, + 76EB063918170B33006006FC /* DictionaryUtil.m in Sources */, + 76EB059118170B33006006FC /* IgnoredPacketFailure.m in Sources */, + 76EB05F318170B33006006FC /* PhoneNumber.m in Sources */, + A157077B17F0CD6D007C2BD6 /* ExceptionsTest.m in Sources */, + 76EB057318170B33006006FC /* RecentCall.m in Sources */, + 76EB058518170B33006006FC /* LocalizableText.m in Sources */, + 76EB061918170B33006006FC /* AnonymousValueLogger.m in Sources */, + 76EB054318170B33006006FC /* AsyncUtil.m in Sources */, + B942EB0F183A9633000887BB /* SearchBarTitleView.m in Sources */, + 76EB058918170B33006006FC /* PropertyListPreferences.m in Sources */, + 76EB063D18170B33006006FC /* NumberUtil.m in Sources */, + 76EB05E118170B33006006FC /* NetworkStream.m in Sources */, + A157077C17F0CD6D007C2BD6 /* FunctionalUtilTest.m in Sources */, + A157077D17F0CD6D007C2BD6 /* PriorityQueueTest.m in Sources */, + 76EB064718170B33006006FC /* TimeUtil.m in Sources */, + 76EB065918170B34006006FC /* LeftSideMenuViewController.m in Sources */, + 76EB065118170B34006006FC /* DialerViewController.m in Sources */, + A157077E17F0CD6D007C2BD6 /* QueueTest.m in Sources */, + E16E5BF918AAF02100B7C403 /* EC25AgreerTest.m in Sources */, + A157077F17F0CD6D007C2BD6 /* UtilTest.m in Sources */, + 76EB05D718170B33006006FC /* ZrtpResponder.m in Sources */, + 76EB05EF18170B33006006FC /* CallTermination.m in Sources */, + 76EB065518170B34006006FC /* InboxFeedViewController.m in Sources */, + B97940281832BD2400BD66CB /* UIUtil.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 70B800A5190C52F80042E3F0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = spandsp; + targetProxy = 70B800A4190C52F80042E3F0 /* PBXContainerItemProxy */; + }; + 70B800AE190C54870042E3F0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = speex; + targetProxy = 70B800AD190C54870042E3F0 /* PBXContainerItemProxy */; + }; + D221A0B0169C9E5F00537ABF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D221A088169C9E5E00537ABF /* Signal */; + targetProxy = D221A0AF169C9E5F00537ABF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + B6B6C3C51919440C00C0B76B /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + B6B6C3C61919440C00C0B76B /* en */, + B6B6C3C81919441D00C0B76B /* fr */, + B6B6C3C91919448900C0B76B /* ca-ES */, + B6B6C3CA191944A500C0B76B /* cs-CZ */, + B6B6C3CB191944EE00C0B76B /* de */, + B6B6C3CC1919454200C0B76B /* ro-RO */, + B6B6C3CD1919455400C0B76B /* nl */, + B6B6C3CE1919456C00C0B76B /* sv-SE */, + ); + name = Localizable.strings; + path = translations; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D221A0BA169C9E5F00537ABF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "compiler-default"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_RECEIVER_WEAK = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_TEST_COVERAGE_FILES = NO; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-fobjc-arc-exceptions"; + PROVISIONING_PROFILE = "25E52683-9ADD-415E-A1E0-63A1C7ECF872"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "25E52683-9ADD-415E-A1E0-63A1C7ECF872"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Debug; + }; + D221A0BB169C9E5F00537ABF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "compiler-default"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_RECEIVER_WEAK = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + COPY_PHASE_STRIP = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_GENERATE_TEST_COVERAGE_FILES = NO; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = ( + "-DNS_BLOCK_ASSERTIONS=1", + "-fobjc-arc-exceptions", + ); + PROVISIONING_PROFILE = "25E52683-9ADD-415E-A1E0-63A1C7ECF872"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "25E52683-9ADD-415E-A1E0-63A1C7ECF872"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D221A0BD169C9E5F00537ABF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + ); + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + HAVE_CONFIG_H, + ); + GCC_STRICT_ALIASING = NO; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${SRCROOT}/RedPhone/lib/speex/include\"", + "\"${SRCROOT}/RedPhone/lib/ogg/include\"", + "\"${SRCROOT}/RedPhone/lib/debug/include\"", + "\"$(SRCROOT)/libtommath\"", + "\"$(SRCROOT)/libtomcrypt/headers\"", + "\"$(SRCROOT)/spandsp/spandsp/spandsp\"", + "\"$(SRCROOT)/MMDrawerController\"", + "\"$(SRCROOT)/Libraries\"/**", + ); + INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)", + ); + LLVM_LTO = NO; + OTHER_LDFLAGS = "$(inherited)"; + PRODUCT_NAME = Signal; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = 1; + TEST_AFTER_BUILD = YES; + VALID_ARCHS = "arm64 armv7 armv7s i386"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + D221A0BE169C9E5F00537ABF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 1; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)\"", + ); + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = HAVE_CONFIG_H; + GCC_STRICT_ALIASING = NO; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${SRCROOT}/RedPhone/lib/speex/include\"", + "\"${SRCROOT}/RedPhone/lib/ogg/include\"", + "\"${SRCROOT}/RedPhone/lib/debug/include\"", + "\"$(SRCROOT)/libtommath\"", + "\"$(SRCROOT)/libtomcrypt/headers\"", + "\"$(SRCROOT)/spandsp/spandsp/spandsp\"", + "\"$(SRCROOT)/MMDrawerController\"", + "\"$(SRCROOT)/Libraries\"/**", + ); + INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)", + ); + LLVM_LTO = NO; + OTHER_LDFLAGS = "$(inherited)"; + PRODUCT_NAME = Signal; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = 1; + TEST_AFTER_BUILD = YES; + VALID_ARCHS = "arm64 armv7 armv7s i386"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + D221A0C0169C9E5F00537ABF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Signal.app/Signal"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_GENERATE_TEST_COVERAGE_FILES = NO; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; + GCC_VERSION = ""; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + HEADER_SEARCH_PATHS = ( + "${PODS_HEADERS_SEARCH_PATHS}", + "$(inherited)", + "\"${SRCROOT}/Signal/lib/speex/include\"", + "\"${SRCROOT}/Signal/lib/ogg/include\"", + "\"${SRCROOT}/Signal/lib/debug/include\"", + "\"$(SRCROOT)/libtommath\"", + "\"$(SRCROOT)/libtomcrypt/headers\"", + "\"$(SRCROOT)/spandsp/spandsp/spandsp\"", + "\"$(SRCROOT)/Libraries\"/**", + ); + INFOPLIST_FILE = "Signal/test/Supporting Files/SignalTests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "$(inherited)", + ); + PRODUCT_NAME = SignalTests; + TEST_HOST = "$(BUNDLE_LOADER)"; + VALID_ARCHS = "arm64 armv7s armv7 i386"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + D221A0C1169C9E5F00537ABF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C71793B33D9C45079F74487E /* Pods.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Signal.app/Signal"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_GENERATE_TEST_COVERAGE_FILES = NO; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; + GCC_VERSION = ""; + GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + HEADER_SEARCH_PATHS = ( + "${PODS_HEADERS_SEARCH_PATHS}", + "$(inherited)", + "\"${SRCROOT}/Signal/lib/speex/include\"", + "\"${SRCROOT}/Signal/lib/ogg/include\"", + "\"${SRCROOT}/Signal/lib/debug/include\"", + "\"$(SRCROOT)/libtommath\"", + "\"$(SRCROOT)/libtomcrypt/headers\"", + "\"$(SRCROOT)/spandsp/spandsp/spandsp\"", + "\"$(SRCROOT)/Libraries\"/**", + ); + INFOPLIST_FILE = "Signal/test/Supporting Files/SignalTests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "$(inherited)", + ); + PRODUCT_NAME = SignalTests; + TEST_HOST = "$(BUNDLE_LOADER)"; + VALID_ARCHS = "arm64 armv7s armv7 i386"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D221A083169C9E5E00537ABF /* Build configuration list for PBXProject "Signal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D221A0BA169C9E5F00537ABF /* Debug */, + D221A0BB169C9E5F00537ABF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D221A0BD169C9E5F00537ABF /* Debug */, + D221A0BE169C9E5F00537ABF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D221A0BF169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "SignalTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D221A0C0169C9E5F00537ABF /* Debug */, + D221A0C1169C9E5F00537ABF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D221A080169C9E5E00537ABF /* Project object */; +} diff --git a/Signal/AudioFiles/171756__nenadsimic__picked-coin-echo-2.wav b/Signal/AudioFiles/171756__nenadsimic__picked-coin-echo-2.wav new file mode 100644 index 0000000000000000000000000000000000000000..0e0637459b41e2062032e1ecabbc1291f765b026 GIT binary patch literal 454926 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�KH^Ua(jMRuOm#`61jNO&^b>-M_?cs^<$`>nC&lp;L9Wz#VHwodN zDKKxQSq;XU=EWVa#*zNnI!~p)Vg+3y|CpGz|FKN8^Qm6&w!?VUOFP>|9xCN=PR*r} zZtW38;LB6|I`eZ@E>nFog&|$}8O49Dch>~0H9k7oejY4mc2#hHLhO+V=KKd|z7LKU z{;B$&)hiNr8U5qrYASlv3+E`|=j8^;@{>Nt;8chx?B6mo)yS|&HE%hXddpV_Xl4va+{tXlPY20MVfV_t2y zXRyUdP+gK&*lG>wRpK4({!?XYR_WL}A!OY5rF_IHeDwAmk-gKhi4u-)0v!oW2i5o; zK)KQ@EF~|H+!D?p{IF=vve?2(^@ndINd@2I*K(C|+uC}2-#nmtcdutVZauJ>&po1u zy(znd@w}7i6$Ek+`_J}=m;QxBO+*ES8A7X|j^tR(UT|(V2=031)gOMg^>si?npG`& zYfV#>4EycelIhI;YxU=(Nkgfot&hR0RQ0NCtD-TDp=tvM&Lk{v)SOuM#H3s&y5lCH zr{X}L&01$Yek-GO;gk(euNv2`&cx%HSr^oW+7bpByAX~}bO_%u>D260Bi%{9?|+RDjP z$=)kecMYi(8%EGorn)UaQ-34bLjBy1jxS{;=L?Fm*{wQf+urnV&Xe{1-o%fb(|VM{)^?4qqKUBd9Q!aJ(@3f)}> z#w{-+YX7a&L}p7if<&0D;%ptk))}3s--d5EX6g4G(%v2u1Zy8s*b~ov% zPw6w3+MF9&U2C#khEqSahg@mtuxr|tRUAdja}L>D&oQX)F^Lo~I``cEjE3`Dz5Q>~ z$eY%vN5_#wpSHM#MxAj>FaggX>Psjym@z)LP#-^e6F|P ztda0Vbph=Ey25Y%1l}M^r1hM%euaFaErTYv8Fst;J^yd?Y%bLP*EyShzE6EP53%5D zaAiLd#BqbGZ#i+qMW)M*U3VnwtIN4THz^3u*W=hjitAy;EH90PlrM9EH_KskqPlgVI1h%hY!v=kb+PP;sltSk9jMzMX@l>ze=-?NPxaJ+Ea=q1(oyW+O? zMNr%+`m>VVx#W(7A1y+*gZz0-ZqtA8{1xfB##yd7o@VoP@&Z+rlDSm zV4*P{Wwk1&H{LyoFi6JP{c+@YdroIKA4_v`oRygpQ*% zyjI12TQ;`PmHWD9GlBB?o;hUe4IMg{cnL>Z2sJQ_kxN@6q#Z zY57S_7s}d4L%Li=dUIl@Hjehah0Hiz?c71G&?FWG(k)&ev+Da`H-NGjQ`fL)(TX@P zUTCEeI>bk}l|NNHqtgYp<0f zp6g&gn!6%R-VEBhLB)KkMI-V%cK8MeSLgD3m$SXb?ZWyzj`gp=>f0NJyN`DLkrSi7 z5%LhV)VrZyQ=|N7PX#}#=cZY^7dt@8Y!R=0ou68!IwJc>Z?b zKbeKLTSu#w)+RU#z4z*gX9Nf)uhfH#75^G227Q++wKkn5TbT@qM#m}nlu8i#l&rFL z?V{O4O3OpOwsGK1uidR)*?aB#BD_!Z(9V~&)}0kfqg0tG z8X|&<>;&D_L(+}q>gD_L+jA1fB6}nZ`iSFWEaH?^&Op`49-}DI{YQ%isZ?acX7fp` zLZonLx)IpP)E%VCC!!N%W1#WL=+R+{P8;`9<91Ce3tupudvr8J{@;XCr}l%kt>}hZ z+MA6~(sdJ!(+{OAX8G?*^BVZ6hYB0%-%@;iLw_w)|>R_FGg(VbBt?Zv8X`_RyNQfJNsq`x^m+sdQCU)j8nL&&^k*%8=3MX_mqVo4)hzRp$~E>m?BQ9JgN>HCZun>R05xjtnurS@QEI&JNO+LV;Ry3g?m zd+W8|?+Awkl<(WBe8;GVTdROsiqli7?@O@awz79k@P;!IlCCren^F)s`nRk9BW?7C z^RScnq};%$w-6Zjms+HO*nGj=4VEN(NMrrvhCXV(QPF+9tDU=re3XiR_HQOCV#`Ky zrA=ZRu{(R@)Y#y_=A+1euMVu9I6O~E6j8_V{n%4hxg%SXry69fz!xt{KCQmJ3C>1J z=C|zjoyE)H=gvQz^Yk1(`*x66GWhD+V9q!Q;&!ud^_%ewt zC%{P;x3ZR{Ol9Q`Ijf*|RXuWK;{4?n+mSDK1*bbm->&W#rV*uHZO1pwX77$(ktg$( z&M}qNR(+>3#ZimNh-)a-|CFV1Mbb*OQt?89A=eatjbkcKD~=Gb5njT*`ID`U(?J=- zj57-YV{>ofrdNWtuTC!}O|Y%LtQRa|uHQs9JF8B0DkKldEPs>UO_jghpuAnr`1uqf zW3e?O#T6V_Q~$7%ld`H_I{go|vUPO~;YCS0L3-`2O8Y2s{jZpM5pPwIq;RzA~8_#fe)3=8#WntZ)r>cJ+CPLX`rsC|b zoVCOs3>EG5Tl`217#vos_*q}_fVJ!w1(^_*=z)t;s3=mkIZ`_rvXu#YF1Ba8;@C> zK&zP}PpH>+rotYqd`;MZN6nY5?BFQNgak%^Jw0d5W{kS7o&wX;DDUDt{mE1U&cWkpnWMSkj;aT^5$dp*^3WxZVKV}c|gd}zNt0=iq z2<4$rl^|8Mq=4{*NTTqIx)b?lm{rZIejIwj%f-q4S>g`eAavpL83MM4&`TGUI0x^u zk+vAa9}AWI_D}TZA3lj08My(*(k>38&Rr{kN|(+P!Uz=e4gQVAM*2LvX*R_g9dnN~ zUnQ?6 z#o4P#Te{)vIB&==8~W$Bl58yu?5E%dBEAkGLHCz#v0`2MDe&bZh)18-*ox9{oEwzA zICErq8o!{^v|O9ZqP<<==83B%({+!cl!|w}sbXTjBH{hw&39$=H^j!*C79;hX61aa zjI~H59>sQojUns9ae|Na!oeB(X4!gQC)ss$tuT-u16&>l5t~9(-VKS`Wx@Q9kh?#{ zE0?*6bbj*;u%se(khSvu0rB&XrD-3cLNPI4dYLZ=WjedT%tsd2l4oZ5r@MH}PfER8 zlkoW}?pz?caS~%GE;*OCx!s0P@!n*E;k7%fZ-%!XY*4UaYfG&gy#_1HlMp>4s$P<0 z!9F}KT_}JD`8W-m8!sVLw~uTSRDKCBrwLsh*jj4jL<=m2@R1Y(2|>r#Q%c#4$tCw& znA7jbOdFKA2W{xHF#lz~tSre;RUslyaw1LYY9;$4i&s#E$hwUZ8Yc@s+l_T5nnBmO zW*f#?8&wXJ6F1f|T*2xW`v={GLd-ySuM39T3C`lNY3d@iY)JGAp)Uls(=g_x`ttN| zTDSMgSv`t$Cb7A8>FOg`&7m!2SM>D$hOjPL0DGib49bQ zh50y1AgHHs)+nFWZw&X5K+6~8Nxay};r%PGG3Rt=*ioDufpzA^?bGxv;`VyG)b4#0 z!Fr!Z9ar(h$$ ziSlY>qqJ*H`#v)1HWj}vN($jVxr)tyg}8G|Fl&$RqcTeOq0q+!6mOq^;o?Hz3!V!R z%jiGs+p(KZZ;(7V+law+i&I>ywGG}=u#R_(`UrVU3(wUh(ZRo%<$aL~N8z$}?A&Vt z%uo($5_6z;tFvuipT4lwO_Plvf$@3We|Z_XgZUDTL{jYt*wy=@cm{CrHhuYkdM zf$j$M=NbV5gXGkRF)y99jNI>5A*K~GJMbGO`kPnwxxOT#yFEvLgk*bMLW_?RiIVFU zhqEQbcE8~Q=fMT@0_&ojp?GW#eV@~d>dT&WJi->aux2($t2QD#KiC?bhtHiu!p zJY^?65}@4$H#5Y&e&bc_aD88JS>v$vEdDz;7D@tPiV@3)tM`WIR$Q;pyjym{>_}Vo zH0LbJGZboPa69FN^8G1@p@+C9Kg^>OdpwlyOxErYAI_Q=ezFN`b#B@AFc>nwlsU6w z|B;jlVH`=`+xfeNDdN2u&+=wtvRhau@Di`nP(BiTR4L52Xn{-L`Li~+riL-kx944p zLH_rN$f@0qdUDJg^0oY}#~xH-ACot2^Kp_wVC){rQ_8Ct9ClprNDZ2m0C#=IYg`4> z$ipaJrv)yc)CLy{MA@64Hr{lzp~j>nZ=#(&?}cebjt4GYnR$JH&o>lQz!8$WhgN(o z`Y;}Udv4#yTW~*laqTY;eUZd-5guef^gTp(0B;1nr`}8Rvck!OipjVy~E0Wy{ zHx^FSfFy|sww&Z|kVDu;qpeQxnC|kMZRkYHrS$Q(^0UU z9U(HzewMyzCix&2X|o|&*?@jfhgq8w`j)>n!>93q`uf$I>qA;shIrKYLTO9KZGhcLY82zG$sN_M`78aKVzF;H+q8A`*a^zv1A8I#xi{~ban?(7i<>b*97QPk{T-o^FFOwc zG3&G3mDeHxxkw*=G4T%c;3#@-fKTua-9Z^?_ixLQ5aP|m5r&gbH` zp`KJIXY1r6WT-u@Z51wFv{Q!UOWTA9%FD)1p-T06V-WaXWpK3!ymAI1{}Fuf3{l2! zccXjV!Ii!yK;O@xIX5%g6RDaInCv^cbEyiv7d*-o%eO}H9p^3lhb0E@FEwI$i+SXF zF_1IMt0@p2@XV4a`*al1_BOq*V&}{NMa&SDQbZppLj`Ye)O2OY{?HtxV9-P48wG*# zL7cQ8gPMk^nOZ+$1eVJqhy3Qwon7y7qu-jNt{mBphOpaGDR*||6aI0{^2KLjq5J!S zH=ZC*T*68Ihp*9wTg_u?qUlp`)N}W#VEMhM(`)aw*?upHz8MttONdi9X_xaL&rDbs z75TZAyYW?$L8suJ$s&Os=tKFCu+zM5PuA>{pb(y&Wfla-T|HUF;@l-h6Ss$Xx6PZV zxx@Ie09w44)c5jzL70f`El78TNJgJB;S6z927UoblFNoe9Ok3H6 zG2$p(r~72^e;~?v#-|GCu>o34y?iX0r=m>sx&T7&5=LYik65Q>G@;#s7_?vTalH+6 z7dXUy`C~9`hDvrK)82A9WiK~Rii`aTLUS3 znj@P)D)qgLEf9hc*kRiX3;U&_YqTZjt#Lb%yfKXe`nTwpGg0^NZt$-{gmbn8d^mR` zHwM$lTTga01n6(K*}9h~SEPA!&+K*hi8UPMX>t^La+5bC4%R4#3%JWgK1W9g?AlEs zf=?~U=&-;amVG5@sa@O6A2vTaz<$awE}ca4G8u~w{4fqATt=pfjfrs-*a_k-J&$mn zN6^dNM8XpE#=mXpd)&#KRejza8j9piq~hT$`(sr9 z&!ShOxw~9G$MX>R7XH}7=z=&tUsJ5FG{`9*>&|5my5SMTh40E3TCV&Hjp;#2a&b2OV^HstCy_b8hsxh-cPL9pSt zOi8&>V51HE_s5a)cMvZ>;s2>{kQb5X@vOrq;Z*wauLoQk)>is4rgH!(Ynmbs!B}3W zEq&#uN$-g~!Jp>=$utYRJcrVLihCS~c|+$R@bLw*mjcS51%bpKzI}K-k%!H6kK8U) zrs|imEBV-I3gMApL~W(Jm#??Px56xZt<3tH~h)K#>fanX_zR_!y4417I`vK&ukyQPLbsxC_=O}MTs8k z{o^nA@8<9bF)?@qrYskEbP3g)0QuyF>y{)c^YDnZ6Wn;XcUrbB!s!v#Y~(#EZ43V4 z$WC`JI(d!xZB>xy2yIFi_%w=)x53@k<-bJwvv|NsXfuyM#t*Dd4iDrL4*P}N5u zLmLS|bZ4&PrS={lSPhyfb8y~zBx^6kc=TGP(tfc6d1{Nn2W5RvqTdaH-PEC>h2T-oX%vD~(B7VoyvTDB z;>r_rm=bQIiPg%+ycVGq&7(zQxBmJ;TLP9&i*pV%Qv?%eN19m~ITWb^KK%>y@K1b7 z-W&xh;j$peu{ylxKg8Y|QuG4u43&n5VDrTncCSLlVmFQ0oWO(ROT0VB55T&eX)adq z(Pz{zjgmDaRs&k{s5!J^ieGOQW!41F$-u#FNr`C~%<-+xAIO`#^Bs3Ue%c!XP-eg@ zDvu1+_a1NCwe7!J!ZvLzOPpXr@-8b?&{rFFj*FTr!u*E=Sscbk;g%1JBYr7t?RtPR zn>Hi-Ib~)XcMh%E0s8Xec5M9Z%>TRkL_G9oMORzej>zHZgVCUbsGz! z(DE_jJehr3l6kd?7ODn$7q?Y#SF-#T<7%=5Dv0A6D)P(`ayuN;au)Sed!G}IyWUNb zF2IA@=klfDchgt6@*wiTE#E!{YLv%Pg)upgg3oV5-G!Bo?W87)v7w;&EBrc0)X5HT zD+jl4NVF(Gh1PF9Z9>JoUUkKT-OiD(6*6_6aF&_m115N%OO)U-sh9l>hZb>B7&p6H zxDsQ<4}Rds_HaLr5J}a) zB{PHLY6c43$byQ7qIX=dOR|jOpBU&W=@A55Ft|2w9j;=vQuT|ASYk-U(P<|^qw?g_ z1ThU2+Ris|p?GFyj5sVBWEq6t<-i|3g4^Tpy>_&;t9a_&Iqng-;>Oxe@_zf^mfQiB z^T)mZyL1N)p8pCdDM!R1gq^hp@gyM-XoWYe05NhMO2%UcXr!()*qmdV)O%?Du*K9H zkkvlYwXY-5S>ldYygy*hT zp+F!AF$zT=c!fFMOD>kcKw4JNt?;)m2{K8LALll31swL3U4CbV{y6wDe!DhNRIY)M z&@KENxo`4Nh;Pa|lz!G@bSXYj-})|?R_h?9Fk z>#O05mZO&oi48e2Pijk~@~-? zZTBaAwkkfdCu;Y;KA=B=@_N0eYlrepexRGb5-)e=K8_bavU!?*;e~FYUkB33*U*B< zEysM=g*alp6f~@9O9;7Z5xFz6bgzJKZCXjqL*zN`7 zHz2)S#GL9ObDFV9lx65X6xU7=Jh$)YM;Q)a&cq?+kvqgajMPQ?-wC0LuZ(tovEGHf za3`U&B5>P0NK-GcG$`?0_!@==Swna@5uV~8msiL=AJ|)qjK_VnsXnwt7Cqywzy^us z@>X!;>#kis->Nt4T_jk_8l{=SwKonPl@aCK}rMp;aJ8vL6! zqyHkN>Fjo~phVFUJNrM;^9c~ONu2H%L^zrg*@Q?xzhn0WiJT*vRl+4x3f*Wt zPORc2ZX<#APXJGnV=6!3x7lU;R104)19_LDLry~YexI!xjW{hzI?8|*F0LmU?x)j8%LUAVOtv?4=S3L)>JAfi5vL4c zVN68dZSK~O@qN{UIcp+yl~ID`40|rJYIQAT9TuUxAz{vgk=?l8yo>&iIUdZK9o&0y zijih1NS0+PJr=tav)7|99A*o-JcGmv!P1}X7EuwwyG(;p)SZZxHZH`fnkXU9jRjH5 znaou`_?s6yt%cZ>Tn3+$&@T^GvVy2;BiQUPMo$q@QO<)f$E2tb_mQZgH|u2`u>8_h z@(x%?WIO6MN5qh|?aO%a8fl?V4?ZvPx`*TaR7hob?*@r4&j2zS39$-6WNGXlsze8z zBYd8MdmbRjwC#h0DUY6T73W#$%FKV~G50qZ<&(I#PZ$Q0;?n+HzCvE-mpo0-o(< z%dm7WaK~18RyJ6M&k+Kb8O|c!gz;!&+V8WS-)!ui`+QMDO@<`{!^e^ZO_3F|#jWrSaUr z2n0BYC@;%%(_rJ-JIDnRk?#VCHNI^$TKxrwMsvIVq7TA%3y$*r4@2ku zPt_mC@w4x;uDwaJ37JKdlC-z9(^5(k4Gk$0)i6_~E(r_MT=s5H!jCtLBw23Szt$f})ifYs2|=Ww|}U)>96 zUfzh(5If;4VS-VichHg>c-9gP1qUkWgqBJ& ze($>X8QLc4{CkYm85z5Lj>)ja47`|;e1VD^IjvB`XdZ3yK)oj(m>R6KAPt^0%+Sh6 zKRT*uhWuGP@GuSj-PYrA9B59I6=yQuQ|V4GV!<87+ZjY!s@D4bbh*9u-9;?LDUgo9 z&Rfu*)5yiO)Tfnvr&(RAtC5a>9km!(wP?WEp3ClJR(_=YUL%_i5r?0sjP4+tDeb4L z*iHW85EsZZUywE#UCK#UNRhh_hjz?FcBu3k+<~gqM?Co4nM~Q@LE^tMFrWzkrJ=&J#(NdZwMcU@2UjC4QKPMqNMQWX3Nbpxws&X`@MYiV zb1kYX0}tIqlx-6GhEsh7YBSC0DedaMUd-(!DjVaV^faEwM6}S4Qh$qrYkN=-?uzLz z=mR>F`VNV?pIuZ(HWT9xto}uf>M8bIr_59|rtSoCFRK!-px<|dPg@~Zk=(!-UT7+h zKaMVM?)?}EDM5oea~P}a(T*A_O%o1Lrvg`s#RN6KR5d7z3M|k{4CG2pMfiUZYRsE; z0Cku)GISGJQPFoK7(}?983AB#<>-e6T-z;5!t7EzNSjnAWY^+Og5bIv-e{i>+UvEznK9er4JgZ+pC0&a~b(1G+T*Jn$iM^0 z(v;oZhI(f8r-y(&8NCrpfs-`@iiW_OKzc_7D^`UUCR2mIl(ePf(OxaT1+25b`tg(8 zoofm+{6PO*6uA`m(IlO*2P%Bnmoyc=A3RvE39jfE?n+?Z?PN3CnZhWg%y+;B4;2Gr z_EMX;;vgGRAyDxH<+f-|6+#b4vI!{VQeX3G(A=+oYzZjbINGz1dvk;vCoxaW#x9SL z`|hcoccCM!HO$Sq)K_Y$=fGS4;N?$%$rX6hZEzYf`eY3f0rW`{!S=wR$R@_HTY9*O z7S{4d6}k9n3i_WJQ)AW4GpyPKl>>2L{u69^8Em7?q^BUJy9U7oX!V={#dF}_9sR4l zfldpe`zsshn+FKYX+=FxIClD3<~PRZ1|}r{E%9ePbp(~I%->{H9|>dmTxr5l z;A*PKZ3BG771+E9PTMf}O%3$$8hTs`xy&6nqXUg7NRLLcjq&)mLON`*LS-OZ;Hj3F z&Q*ODYn@~tHY=$eOIff~fS^!}@WyyR_S3~{G6xQ5Xu>2$|x5mC(0q{rpNp&zsIutyGHT*d= zS)ZE|GbU{U=8u4ZKY;91NN*minWSQHgWDXfVvqyA_E7$?9U#@glw$5^iQID?w{)YV zG#WhD+JB4(Dx4iT_5nC?YRvj7X9Du{w*hTZ#V{#w=Ci8k7IX2W($VQmtBsHra=xbU z&RpQZ&k^m3>;=Jy0m|*%Ids(@oc)^26N6nacl;ffK?#&_c9UEswh%y0C{J(%`rSoS zP5^>RYDpj$KP(Fw00!?2W)wjDZ9}`~f=UHLvv@$e9`igNxbamucQ=%@Ske3<>w7_b zkD@=z6;Ezq*NTN|*SWRD_|%(h?BCJuFt%=F*sKvSm?UW&0mKMhGaYoU1kX(10-}_X zLOA~n74xOQ`Bmag+Q6Dn*e#PyI8TbyxY9VOCkW^25B^97zf|=1Zw0vf;}i{$whD2` zg0|W4?^Xl#K}z^Nw$DIi&LU20vVwON%R0eFgj{@qtfPT(KPE|Z-M$M9_s9VR zA`*iq(Y(=JNF{sbAq`&Krb~1liP-CH-LwjK8u$? zq2G|*M{w0mGS&x*ISfy5htl|hz%*{CS**~;8XGD-^JeQpgb5McGn6q5V3QY1Z{BC_ z)QGZ>x2UNLSP;-~_ z^H&mi0S12*+~xwCAmMCZ&gv=A(#h~+o1q;1{ZFFiA$%C_u`gOZ&L)A@(E z>CVj;5&_>i*&0+Ag0?>rRlJ1$o1#3H2-s%`2MoEl{%ExV$G3$}e`41ajzzDaCCf%u zS2Me&3`?4TgIR>%W=QKMU9}8q-;e$l!4m@&gv;QDVo~M-@X;F4?pk)|dUk3F{plNN zf-?DCl1V+xdlUKT#jH)Q%*TrJeL`!ufChhsMM3b;WJR-O84ncizSMim^rG$MfZUZDI+!*d@Y8sj)H?S*s@iyn-{%eA@>hbk z1OIvyyS=#2!9sF+0HZ-RQ`CXpe&sn*^74iQc~ieh&#erf|<3LD-JH z_Jm20uy0 zTPh5dF(*B!5o6k6h**7%zVcC063(4z9rJv`b$*pW7NEH*CrAa890UWl@OmEq+IwjC zGlkdRfE|AnVOrV71VDD|vnbYHRB zAh6{YcX$$H7m5BSgSaUQzdGP}bJ0!@NOwQq#1FVr#%3?3e{W#Fmy)w4$X_W_NF4FC zka9|pgCopLK65_?JgbTBPC#{Dp${F=mxp<)!jS{-!7Z^+OBFK31t{zwnbi!xSUPJR zZ4)gGaba8xiJy1bHN~v(E(jA`YdmB+Ne~(b@0umF?}CyI6~8!xJa;7UCo^*ecd3cO zE2sg0eh`cghfO9b?(4d7b+UogI1Qp z|LWK+p5UWidYw0z`H3D0!EgMabaG^4X|&4Ov6+tCqaDnhRPg>i_)NzgqUNmsM<+>=@z5qR|Vs;QD*= z*O!4S_C#DCV3CCOoB{Kz6mlcL6eC4XS0L)CFmsq)KLIJNXOCXt24AoZh0>9u?3pjJ zV+WaoL1V}Nu{yQ1X)2(eg{Zy-!nOEM2f17)k?x!Pi> zT?rR@M&k7WFqt|Q>I2kV!B0Hoj_Ly6)4B6&g@r$W122T(Z^0$k6n`uOzV)J}N`Q_! z*p&hp93xcc1IZpz{u{>Ol60?_!5-pP=h(|v;dmeJ@e8=+8dtGWv~D?&4T(njz{Q*S zgFnGrKHOa^@bY_kLK=5xg}mR3E4Vv)@hi8)Q=Xc_Ep{f?9O1%;kYzW3xG2Gli_o_; z(ZL;{-!s0!M!1*vj?Ks z8^^h7E5Ysc(4EEn$Tr9&SukxqWH1HQy$IH4q5=y5NhS7`vqqO>noV@uExceOO?)Bl zDl-+wftg#mk1xTVMS!kE_-`7h@{W&hhhpS{ybsV*H{i@V@X<|TK{#hCBCPjvqw}R; z4120g+VmeQw#7ecaqk~vFFS#mIarb#v@4P?Oo37t^Y6PrH$Gsu@_{d1z;G6KK@UF| z&-j~@!OLi-PAZR~SLLxU4zL?8aBL=b%Lv(K4F)~nXO%+SCgJ{U=u$d%^cNUuM}NdQ zmrbmlD~GGelPt#VHDhmr*ot{lM96LT<-VB%ky&sY5BlSd4n2a_HVacvK>PBrf}Nmp z1eTNye95Bc1hMuT$%GtwZi4L7VS1UAoUFq<3}vnNu@e_E%a;Hf26(wWknJXco*wMq z!aq45rayD3%Fv-lzIm9Jf+wn2>sf%|dHMV8tZR)N`AYvdC+(We)SqQC zPI0??!6`XF&sikB5Zq?Y1Mk4qA-r`yNXjsFAA#Z%sh0QL=}+YFOZJ2gzR-jDp+c;F zNW00&YGcNyjKgx+B@jO)5BO;&2vCK1xuWnq@OUe%G7o(04i+v3Qy){K%E0na*_|S$ zGf(pOER!df$1UVaX8>2faUS}h*J?of7XQF2@LrbSi8*X`mp|)2h}i>PYyx)2&~M#1 z&-291dF-pSF_zC(OeBk38Obwh-&Q801s<5oWpn(WL16JWQ7jHPnD8GB1B59Ou^0Tj z7kq07et~4MN4Zv3@?j#|_eL74&cXil)DZ5JIXj{Zysbckrvko5MY;{(t5y6R035m) zs|x61^0sq}X z-$FopBG%Ld+FwJS4uahTzIy}s-yW&T5q6uEy!{TN2;r_j7`0Hwf?(gg1^=_=k}?Da zUjaim-tzD8a~|JlGV;a&T+s_HSV~R&031>#)0BYdxv~@cnLF*cR7N@NrsxvtY9TKo zlvzG*2N+KP=krT~$E#ERz!*gAM29M&*k;yQAGYj}&931N>Je=d8R~`1=_!3cpKE8=1s2PWe#xT6fbHN@)u)**GTRTuJk(+r_U|A3a!3L zTAv1}mC{gv)kr5^Y12lJD0MAz%0_g*gvy(ZviI10MDV&8xU-4pi9-&tyyJREHwp%n z!C5=xe^4+)ft>r34Qj?4_R#e({j-3&1XDhj=*8{m!BF<$5uUFpxcx0!O~H+i5!ajO zq{r-37eozbHe^Cm7s!h>fgyY1)K&(T%6Foa=@y`H5@qrSQF384A`n$)F3Uxrod>G^ z;r}v%K?7{!FBoTuqKDAxD0-3^kS!-PCov68_-mYU+C?^=qh<9R_>8$)hMgYbZVezi zUqREFcr_?`?lSP83ORHE=zId(#?W2^KpHA9U(PN{khNyejkhT_hVu79Li?!o?%=gr z<^aZD+z;F+5Rkc0u8bG<4z75^nMJ|To-~jTuAD|?x^gOqWZAQr^{Xhw5h`>AH9{LD$eGZr!z&Ff>!d@UDHi%I&cp?*l96+!hA}!>zQ4s2AJLT?NYOlUvky&grCJlv3I0s(Qe^TN*AtBSB|?xDX7Lcg?V*p3=%@iJ zZ-xIl;`Ob-ySId7J|ir|^~b1%9c<`4%61y?W`HW6hs@!#^M3O8&xQ&x-kfs8P6Bc7 z5kED??hhPtfK$8(7BaZo5w<>z=!2-|yXkr{neYe9*PW6OZwN*-su1gbu3$N8`;-exhKpgU;XKH{A%EJ(+%jU!Hj}F}nbTJYYZ6vXrnI-f zu{itqEEeJerkBISN2Id?YFmrVh-C{?(7&E?Ln*i_j+*7bK53+8ZKve+?4DfWpgB9^ z1bOxi))PP34EPEbzGzU!l--THpdR)Ta^r9Er8h$2)hSTlSKJqhR4Ia{gV` zN|U_2jE)Xx9!{kQ30PQ1hW+BjpJvoQArD`JkPG&H0>Y~W%043X#z62vg}n=x~yLd@iQ6aAEAup*aQ=9unawZ7b@KkF-y_nK91dr z6+h)>rXin9@YZ4AodFfHn-T7!59}qCm7#2FVl4vA)1^+ABNI-r!4wuZ0N(xyY3QNy zgWv=PJGz@3$U|SMQnTZr+~{$dh`o-`OH-+|r!>$)yov+4x8$}Mp3e*>T^~V;02dkD zpM#ut1ZU)7&)mSNkI`e($>pD+kHO@JAa4F=`dA}1)W;}oB@~jN7cYr>k&x#WI&&iy zupU79NR|&bCCy$!1gbgSv8=r0uA^=AMXVUH zo~FxN*yZcUfGZ%KOxQ#K4Oi&5Te0)&fW7CCfg$$D^Rt=4PNBg;=$%*XNl}M*9Y}gJK zGiZo5aPk70>_|KkBczz}aR>WH$&@8*=OBA-8+qq1C|E`4e1P3E$ebA7-yQ7TqiDoj zX!drbVhb|)2=lZYJsL~jjYRE#k-w{<34;V!$rN>w3e%`RK3wrBa>Z@r&G^r~2CjNX z!vq>J2WZ(0t1d!r2ZG`*bbl7Diee9b5lJO*-W<|}2e$1c|6F9yOkl-va$h4L_&|ha zqA%`J+xPHDC+^%RS~eM4J0EngM+tZ4-#)D0i*X%88o%L|=|KBGBFm0hdx|!ALRMvi zD;kLiRoKy<`U;?t?(CXLXjlhi?hCq@VuQB;q6O1_NZRP21s){t2o$n}ysFEIhS|%; zl=DZxaz5dH0$Q?yq|*6|9T~k=R2~l0=p&cD!JZ9lSlRe{W3%#5#*Wmpg@2_JS7&ez zJLzL_l+GTu@i8&90(Mm-tqb9HT{^xLJu(-leG7*EL*6%m5=CB~3UeVB`>~mrn}{?O zQpSgXV+QoY2I|ifU_XZ=ZvYo(;_4;d6cwsG0xQ1CW?P}58PM}&XzymUI)>db5!2RU zqN9+6O~fxLz+Xa_ikZo6)PY}QDhYyvL|`vebB&yWAjc=ODd}){KNNlqydgtZk(@&Z zcK9s8?!=~)&>QQZAJeHS8&*2N&fQD4tAbB%;6nuLbc(2#U^o674{(wD)?io*Qo0-I zeatqrVr5mV{3<3ZAmir2FSV&x)47yJ$~T>Q7|Gq+N&NJKMJ+gL2HluWEA^l@tAGR< z*lvq>S%5cQV!|tw@FOpFJGC{p3fszz5)|>(7EYMZZ}_lx~vq z&+&r7<U{M9fUPMk`VcBgwdAa=E68;uf;;j&yk&Y(^gO|ljRwuPA ziyJm2yu8ujmH0^q{`_NPV;7cd$DaO%T&#!kqnPT6JdZc@VVtLV5uc`tja`xR)u98! zMCuqbUQ2}9k+!3#cbA+FM`tZ1t~}?vRnSk>G2gY|G-pV%0^RIP#TN5k+7QP)c>INU zyD`%Im55%&&2;8oRg>qv!I5kn{mxTKz+c4h%1%=M#baCC0L~n+I)vWQU>+Os@6V>5 zZsvX1DQhuD>0x5kct6CMX?j6@NQ4h+;oUcoRU%x2;}JRJKs|3s6MJMSEO-j<+QOy% z!1Ub-VhewlF@XkSr`qK!?4fI#%!r6Nk;NWuATl?iX5I1?$^zdEJo_THM@stzAT0*a z$xSTTi?w?(bG-QGcVyQ(c~4LrFhu$W$)c~UstXt6MHWloFA?(FZ&9toxT}I-(*kPZ zQnYeD@HZCrI*(N8&=(2bode|l9A1$bv1}7!qC%d_Zo|Avs zfi6|0B$3#k=UnbP@ZfEvtc2Bc;gt}?`Mr!MF+hj&5Wx8m_t z@Us8pN(=bb>j|Y%o}DHW1fvySL9bn0$Xx8rBWhw24{= z#OVq-*%?@OpDEf&*F6Rz<`TL-nBzlv)OX}h7OCft4eN70r@{RjknA2{C>_(CMQUjC zf1Z(l?Z)zdl2wx+!xwZ!3%$@6xbXy6B0-lZ+<%5(p)2usHBU93uCqq7>G7TeU?4+T z7iPXTzwisW@*!6DYP?MbCrqc-F5rBe=^vdGse*oKkncT@CLhCVJJ9AL%C!zTnFTzH zg!r$JZc}#WG+scF>fE>*e2wL0;~#P?(XE|2B%h+RmFU!e9J_ zo}HkNIx#msaHGo!$2*wvD*3$s1o@rv$k{xP2h=Gq#QG!{aDY>vhN*m|U99 z-oAIZ;x+_Iq;vzh<(0r8eKOS&)mSEX_QLA_$lEjc%P{Ik8&WeLpj*M*UZm+Yk(bHO zRK!nv2-JG;$#2lXWWuTxShkSyZlmycX!S1qTQlFVUuJ5;doYKX_8IdNv*~_Nt|eT( zhW+{q^CpSGL0-ofxye=R)=e_Q4SJbFM+Gzcwj&vz@vv{m{NM7l+q@@p2wfVxhcS_x zVAC$}HpgtX#HJl27H0DKFy;Ied)qFzdElRzJpH$P}?fG>b=1F)705K$ZINLBScnT zA0KrTytzTPA4X2QaP3b(^3LQ_6u@5ert4oIp|SXZi^#MZ{J|RZ^>6BPGg5tk6P*C9qTzZ67PZ5e4R}3||Cyln zT|syd75W)ip~LY3dejc=TANui&ei6!>qrm+3 z_R-3TMGPPLQHto)~8G^C0UDpw)b!I|4}= zCATc(?te<1)&mp2F*A*r4W7`F`9y{;G&_}eh+{<~)DjFxkY+K{I+$X~X+Iabt&5KZbLau-0Et<2hY%lB@*9p+|0WD~gZ*!5^JmPN zC7|FBd7z!=pMc9Zq5eC`yYsOVfz0Kja8V;@upOw6L7sxt!FiasJ-&ZFc4vfGa~l@L zQTvW_<^^DYIdwS>iX0{C_F#QVq{326e>EL&6>-YoHqPK?55iGPsEzBfot@On&*;r+ z+~zkN?M=(7IA$8R{|^1J5}ERf*mwv_cOyLgu%QSl=MgH>=a!`bho`{D>{;0cbXo+y zvJy*KNi3a;CchyQE`mNO+)s?zHXEXsk?YLRQ!~9ptWa@O}kSpuA4(#%PCKpk4 zJnXwERV~Hbe&Z(#(XBAK;}!Hli~W0pofQDa?4xSV@(N?|8RnRM0kOmvGfJbE#zK#( zfEjVXt~z*C7!_NGdS1Xow_==_TAl_kfN2MN;BP;mKZRDm4ZYn#-U{Olv=FR0`pk}g z_5psG!rA}jDi6XgapcNZnE43t=Mk2(0xuS!dgtk`Kftd8?9IJQ<7U+N7-15Ltq8+E zDr2=f$U(}T?u*0 z0^PA@Ttt9Lx{1(52&=`IO@YfF1M@xUQ7iQR1$x6mY$%x+FvPC45=rmjo^X1RGcfoB zi2p(py@<^nLRtj(hm(tjv1en{3tMP-BQX0F=l2wL%ApdCF`f}+7>a%3Q@7;s(@;9_ zAmDxg8u~~D|Ar1%684|4X}#lQJ7Tt*TBw0!C9!U^IJFu`-;oKB_(5zAB_EKW zs*mWQpWqS`_TCjXy$D))iBxfhKRzZH0WWr#7#>8!m(%`Y$b1X1r<{{!zzAopI8I!BtrVxfaJN?vV1`@L5&X&H{=! z12z^=KR5A$?$cX5kO~no{TlyWI2}J;gxHfFNun_eaoAvaTb1(Kg?~eA6wjY-O&tMF z4J&jKy=RPl15;)1ml&+o?4SM5KI!b>rSS^uMVT?F1=6FkxDjdJiQG9Jt`cNiP!RZ?-kunFF^sU!0Rcq?P%WF*H=LRxykj=hXFh`OcQaO_nb>ihhPbMx_Uk{Xe3wd|_Q*h&!Tft* z``>;yOXX^;d^4$}J+q4Etx%%T{#k~t%N(C2P)^MGohczrAg#A)gUVV{6xVmDexLnJ zMyfDCdo8!*>evyOSnZ>B z6P+RJX7cyzjrJeWm18pP$!eN=k-|&eJFYAL`25{^ROmRfx%WA=Z!L=2seX&7)6NyP zr1pLdBp)o-jGNhS8{oREL%P6K)A2msc9dUlKK9B;$HI-+l&GL`T$IS`Q1VgVxcG-} zm~g}Kf$Cx4X0Gt}deT!#`%?_yF@wp52QL&_@#;6EBT07Ds*@ z6`V43fT_U*rSxm+|KVlotV+ZBx>IUO&R(4eLK%v5y8N-}`6JATmv^Z(TmPK=K~-@1rvM~1%G6If4wXmQ zediDDt+ZG^VJMI_292cK9W`qzMwAXtyoYI)uPr_#QAr7Gxay|9C8_4sYe>Hc{jH8z z-6TB{=!Q0a((TsQPyBD#^?4tioYdB1x6r{ci7(vOytx;Z1P<)i(su1Bcydttpi0S% zAw3n7Z0$_`8mEE!ls>_rTddx&RMj%&Psfqdo_=mp=T{n=iw7v($+HftfVS1D(=;r? z%U79d?Aubd#9GyVxN+7KT5pf|ge5>dfs1=@+l|?C1B-$(my8uMVg1o%@ZA z%2{6}^s4pnkGjdDG2N2|Q@ejl$YJfJa*6j=t*c%trupStw+cHRwU<)bjs_oxPY89E zNlw2OYJ~J_Pb5!z>77k&zYZ@!Pxa=|Q@1q@C~a_qY^Lxc3>f7GC9OkD%OS<`LJEAd!1cI^H#8 zx!;)I-lGugv}P+C^i^W@SkcR}^44ywHl?o=7P{yTym+tDo!fSAgYw2D|AOAh{yejy zt_-h9v&(`;$>nA<Xsym|vqN0|&>FPFarH!%buNBK#V;CoR z77o09*<-Gs(Rr^V72~YtFpzX|(9yc5jBb>)pBO@s2Yv1LP-@!f^%E** zAGWe)8f%Pe#O6HS7YfOfwfvYc%e=p5WSZ!g>}Ra%d3LmKpPHj+=>3X`OEQRnNP$zH zinOn4#}&1*urjAd{2Djexd=h=j*)MB72Ke9+dxhh2>b?n!y+9)_aVu06Ge0BCMD&_ zG~l6%{6z)0WFoF?FU+9_jQ`V}VDYu`u3BVAt?4<`D2X&r?9;TJe*d?u{)(1U&~S=} z0Y9hP_OQ#QwSzo?rHd8+?9`6#2^vR=ihkS|t|ohk70Nz)TJjAATkiCSxCmc7>O8n) z_;Ri3i~9qHuPk*l25$OVt|Z9vweZz*$eZK+l8sEWiSXrN?Qh1VyF0Y-8O2#w)m%6H z{JB|nPh(Wp;-s=$GSN$UYecw? zOW5b3^P)>fyKS~8eckqFPct!6t8C-cRXNw=;@$q~Qo9@Thggq^@$uNCNB=GbnL9Tyev?|(ym;H!f(eKuo_vYXH*2j0Z|9-X0 zJlaG18DDKmdvK00mu%JCs_A1==#i?Hiqv?XQN9)ZOLv~g%DSU5we#QMS8z70^&>zbQ1VMXT+n9JnQN@OWi+q(sM5QDmeAem zRgM)GHVjlPc7KyKdgz7mu}8fJEnJM+2fN>z94nR2nXDw4&Q+4c(GmUaB`-&NG@_zQ zzg*Ju-|?YT!o7}Fg0ev6iST`Rc%Gkq!=r!MBeTHmoz=%@)@_lj{i1c`w1RGXO~7?P zX9lzJv5v#F+}r2G=_i{Uzo-^<)SUj0Og^paO$|FPvsmOHk(e3pv+hY#bMi*;)oMKR zf0E?^n%l!w<1~u*%Qb4ciWPV|SGMO}pU6LSpU}C@Tc994=L#lmhc0UU(|9}2^IJ!D zwU4EGzrzIEb`hHO{@>@bv@DI^H($B8r}p^5D$3^rNPUtOu8nEd5js@jeD-}Y}1=3gCNZ6$yX zNFO*UHhgKz%^qIVYZn{Tdv34m`m?N#KG*7aJlC{`NF59elo9Q$; z=CT$W2(q-yjbP(IGU~kYvtLyT6P2rV>L-Ymc(MOvht-J2x-+juYYue7snq?+iVs}7 zE#0TbD-Q3vV8&0ATDL2!f0cW9DI2bpuM=vy7b;N;n#HNwLs3O39U`-sUgcIKDuS+G zz$oPjHvZt^eHda{Pu&ei{*Rt&V~4Sz;kDd^G!lJT+q>hbvTw-GBMSv10sWV~HRJaG zpwl!u;=b>RQ^{(>1j^da&t(ZF0}BU6|38gCoIr4Ry054wV=pWz!p3LzH;` zF(r0oReC;TI2Xs?Ybt;LCeG42`l)!`LJh}t-;_e|r3E@g^GA=*HIIxcs>_nYZ^|qYfo3mM|NX}Z1F$OxSTPJw~_rCIZi#b zDCx|PSlumr;Qno>|LvL&#pd4?nW382rIz-U)8{-_k?T)ZI=6M`tdGT`LxM|hJHln^%JCJPvF6f4CI9Tz zA`jNr@qnpwgcV9Wg%v}6ZLJ$~7Cx7BrQLIz@~88kt%pt{4c(@a9Kb0?_?*Lv4MvR% z<{1ZEe5S{#YQnX0ceRIOKbL1n63xt;gNZOdm3pwdV!Bu1%Aw=tCV%D)330QaIJ%<| z`kbhxc0VsPRN?8nR=Xh0oXKS+Pq3Y<$h2n)MRZ%5^-!4Uz-BquT7QOq|y8yF> z^mR-s^?_QxY~w^us6ns#rE~92cKbecpAQw2Q*;NBT=+1%*+F?xGpUxLN|cOg?K6%Y z?20<&uC;kk_|&{@&FDK^&u*PKL0+srB6zj3GxoQdsjOUWit^*V?WY%tCLHW*c+VIP ziFerbS{(OW-#XB}$xdZ+-*m{K;~Tx`CUq}amQW=gHdAn!)`VWw3Nb9;3w7Y|Pxehb zUYe}ehk;?0r1?(jj z(A>Z;!dtZN9mmcb< zeR0|LfvHQ(^%+T3xZeFjQR?^K<;S$A0iR!N5X&AnoEQ-vs2wbMhBk%Catpb08=!-} z64Nz$qYd3DN>1zQ2B#@cQcz_cUt_`_3zq4&y#ERVOYjl}@glg{xI+7sX>r5&Ztt62 zr;gyK6b&+YL(df)A4kh(X{!I|=s7sU;cxHInJ*L1`jS^43(9L%3>H)imDTH{Wh0wa zhVT6H3K2abdmja%Cx&HRMI7wRF22>_ZS1xFW8Wz&J8rE6sMly%3_rE&&;Je#R8VhU z32$lk+#1v?%lVk`Oj+3V*GQe8CmKst95D;BLnq<=kJX=_@87=ImUSLTzhfyI8T^A< z5~<)wJ^p)+rp1x`a-L$v%ob)$wSP-J<1Oem8LFA3V3yfD2k3X*=(HlR*W-?pnbn}0 zlKJ(HF@-$w3>;$j4u>s)W|hgucXi4)=YE~2TBp}oU9W!0s&a)689b`f_er*Io{?_g znC&OSKVQ4d*1M(r?f0{>MV?6F+w~r-R`>QP5I18E34<-Q>d#Foj?7d3RrwcxqGVU~ zE60O`g4BhQ9^=(+Q<0J5z7x==UC>K6Rvm2EJDP=(-`)tm+NgT!RTzl0nG0Vdm332@ zPp|*-GI(-}&r->Cvy%IaV1)mCV|$5#AAeKt%k@qr3;N9b9p5X6zWOv&c<{Ards;nI zk^JhH$=YR!U%k?W$LIGw{m$3>I^>kne?QGB%~TSst`E%pS9xr7gP#iX zn~~Rt2DvAC-#+CmovOJ!yhM6N_2uaXFp^$#MAPscdOTr_vh5BEotYHeX)|Spb4~B# zrYXs}l*KRZ+!KCyPDk_@Un8#j;(z*1Y}RcDg_FhI#eS;L#JYexW1nwK8lE>4pl`h{ ztIzn|lm|g0N>_AdU6Y5jh|ZkD=kb*$IqRqxW;>l!Gw>+?RIaY?Us3)Ias3F4ren>T zlCC{cD`OLzgs!A|Z{Mtb*GT(QX@l5HGkhO%Rj+?lFsN!rtc+Fn`&v9fUG?m$27!^r z1KV-;|XhZYk;jT~F1cC~gy@2u&@DMLqIPh4-J~8B~J@+-*cFIbjY1ir!P3mV|1+8Ks##rGwgS_fEqZ6x7l|3Hy{w z&fzDenn?#n65>s^*$h@>na{Bu^-$N(nvA4G_5a9J9p?hN;zfDKTU@OaPO0?X%vU{| zSd*^JwwB3jK~tsHwT z-uS6jc~CL;N&D|HHsG7kXgQwQp0dj|)lFk8qgfw#)<1d3R?kmG#ncHCkoN4mnkCzct>%cKP4!U^1gd_6(PARVSl9an{{7^^V=Y!HV z6@Q|^&p~XMS93-rdpTgda(17Q`!pkRXzE>S>x+Xww^^=L!w+?cU;4|)44vZNTB)rc z3^%G2D%Gaisiyc>UkpJ%bm4cG01+*qeH^`iM6~L4=N-@4=Z6O4Rc+3d_xsn{=bjOM zE^az;0YiJb7m8KtWK|5JiNqEw9@ct2^>ch9Hj+QOreVoUAf5v&fD$gNKd_KTv@wD>_ zKWUf`7A8a}3x?_*bO@h*GwDnsfG(a{z$o8&PPz4vPxSm%zxlOWW{w9-_Rm#_sZ!J! zsP>Cixcsh>U7?X0`YpQ*k$+2I73{IL(KnxPV7975ct5VS!g<5s(aA+8)26Dw>b$=V zixNk^`cj^Oh_p=YpQz+}sV4fP043G%pJiigL{LLscP8gK^ST>_qXW}~xdZX(E^2%D zAF&yVyisSCjYkDCadPyFM4>CF;l8?xtJAMkdqwV-_Lzn8o^ibj+N^;K6NK{+kmsgo zHFR>)gDKqP<^Tl^D#s9jbuXW|gd)#X~+S^WR|G_`L+@Y};D2|g;QkRu64d{^! zxm6H(DqA6>c^oRU-1%j=QEVHoJhTR#Ul)ik3F55!qp+V}S7OLLIMQ<{N=XlC-n~(Y zu58wbkWaPi35yaxSguzbI&`PRdGVRi!b5f|UW_StS|kjL+rKHTDWksJ0GTKio^vIu zPzT-mvHQK|;?G5#B}oCbqF%ugZNpe+a2I4M_lW>UVir>86zF*cCAIyssVGDmU4FkU}zpl@`UiB)Xy z)SO5?<5uCz3ayPF5z&(=z3OQix8J`R*VuRK+vxy>%`-X%BygQjQUD;il~WBzN9P^4 z@ks7v7cKuYJ{Htt{z*YHvtCL24tM?f?)NaaN~g~xN!`fnhong9+pXqHM-=9`w;kOt ziMy^*^S!T^z0xFPf-o}uxN1yujYYD;)cJM#`d1~QwQ2$*Ew8nCJDk;@xRllSDIH=r zvrh63D0RjsqdD{W#bW$#3{UA~e}Mne)}ZnJF*8rK(JBMmLRaK>;Y9d;R*(Pii&t`I zgx#BMwDwed)Am+f+WqVFCrsItA7nTX(6`JrQn-3b_t(#nlYp(0;fUa`U9j@x6(`fY z63(5ZZZu9)?_9wwEyeJ=Eh_=_?K{h>_aU>(L{}#!Dv7**?|m=o;OlSj6U~UwX~&{`WDP^M-K5{@n^1oDN58-zr9;!=)y2LzS*OD&Mhfa7ccz**MJUh4$P|`V3Kekb+ zx3E5>LG?>>6*WuYF51@eLEfaU^R(;8se$=xKTfnM&geTfyl|_-^5-I(0hNbei5+R2 z2d$`I!pIyI&65109vzh^lUmKcD)K*T4sh{BjJo1ig0$eB2<6vvEiUv9y#Kl&hC6T( zaZMLW!lQZnzsScVv>4PNo~D9lxW;p*@8-)@-%nMgMXO}oteOkqCk)l+9-Ooj8nd>J zhwZck)kpLW**ed}UHjzc>Pb&;RBjb0Vi^tj`!wCxe5>)%iVP_zF+*5u@$dqwX%ovg zmFV)|K5Op#5xRsG(+@Q*<+Y4+d(8TM&^>oX_YXqLZSk#!DhJhT^}cBsJp8^`U2((T zrh^E(kr`QaU0|s{UE$DV=Pu)Q7lw~lJMdFQ!)y%|M$kr28+irqv=!m7*6!S|uOw<6 z(WOaBJP)((u{<H@+D28Yk<0n)JNs=;u&)&ZM}1n0M=J`x|4epz^{M?W);_D|ByTyo9mjsL6(0lRiRR zcu{qg`KZmLbx77o9Wo~*R8*;|yYC-6{J}t6ySm!h?;E@ngB)8-{FM6(>$VKA{*(>w zUkF#+5R`r!+gWOPa%O*Q`O+=m*opPlC66VkXH^%t08dtes3O48MAvh)uU8fXIIG+0 zmE~UGoq7D{_eN4~0e5+=zg=4r zt75aW+T)(G)8pC`KjfDn9Tr0zbFIIJQ%JWNSbN1^M@^6Or^YX-TebvETpB-|)omoGoCptI=}*U9_u-MNy*5>@;tVY`x(*E_s#?p&w& z{E<`USoc`DtxcynX7qmiV}@$Vxu13x+&8WL5B90*Wmm3Tsl@fEvl`^g{@UH;^9mA`24O!RtjGep>Y4P|f$bGmS?NF1FV?A^E;XO=*=f;ac-`C0gGpd^MmJBPzeM zSMB}2@-^OypQ9TllBJJ^RYE$){YuQa(Gv}h#>XfA_3{>_cZspP>b?g>3L3i7A_dnS zO@@goLECHU1J&_IWhJ#NK*nXyrl;`{^e*VGEGICfL%mL^uA0#j12`^b<=reoxv zsj{wV7T5T}HWpR3OiL>8Ia>8wOGUbeVv2oJke9M$S^Z{vg!4zVD38T|IJiq|T=Sx} z@Zm6=G|#Yd^kj)Ora`PTKZi=9$(vibryh*xgAOilMnanGebYuV-rM=BfMM5&bA15&I-RhAr+2@7 z#6ipLSRt9Fb;BWlM-+C#c*6FrcvhiCyjc2QChzH%L6<-CQ^=v6-{)zWPL@QP6l_O6 zMNd=-bbY7Nzu{!y$$B_r{f=u^Xqp594|VWSsXL%K^ASlk$46zq+* zOaI0z-nJqSsmsmU3jXb)0}pDOT*>?Iz1Fh!ytk1&yN~T}YyjUHU^hi)j4@$Bd^%(% zl>BeTU)L4hwOgx6$|rZL$&9#dg?h9`LlXeeCZ^i)U&8ARZeyE@6Xcja?={Sj_1+_dYJ!4e);8DWm!`o zXz(n0SoMSBp#;(Mx!SLLM8DQ*nS0Co#QeFoO)aIm?3BE+NkU!db~%SbJ*&;p!$p(Y zgV6p-F?wzQNL;*X+IYzutDM+LwSz`&P4wRn3_b!*#NkQDIj5iZ4IEUrO)d^uEWdtV zXT?H!tH!Q0C4q~9iGrn2xoTQm7=QC&%h!DV^T3sQi%YpEmX7wEzg)^+> zo02;;{FV2I)-Kw|%`58t62XoC&=b9G($~=3=+xNlMB6T{iBE!=@&EX6|BQW_@P&S% z-(l#ov7|IuId=Q6UV%c2dXo;OY7kOsw}Cde$iBT=5Sd}LCqgn?QR9H`u=LpczmLa$ zow8}#B-GoYSD>bJqPi+zH5*zyu;zkNUQAtqm5NeJmEkq+(6ye2~V_@3Q0uPQ_mA}aGgW70>~ApmU#5%I$XI^=EZ6!e*Dt!gW9Y4y z!)iA%;pUNfvsD7-R&83W$a&oyJ59N~zCK+Ad~h1PaE6~8Xcc2GTy|tS(lJr6#~A4r z9c@$}J|f1_)W}%*M2FT>9?DOzRu7(2yx82tc9F}M_APk_Xg&w}S5sP7IhloHLu;&; zw~sVN+qEwm@mygWok?4tqkSN>Ja9btBYS_~fW>k()x4i3UsalC)M(x3p70wuctxah zclx^7lGnZ(k1EE~{LOEgjXPUf9BCBU{!=S&1eza8vXm9KA8%N@Oo{!z;Vq-|{mJji z4rDM)lz$!ey(evaD|X>&4NVMmsVpkC8tJ-V?{P+)gQ>*wKr&8Zv5eXK9pIX%%sN=j zNmMKGEZ;Xr;nVI8<15gbgNz#^J%+0JpB3sI)?3#y`Y_*mJYINUwbuCrVMncoCxgW` z4(J|KNnKURU8a<0&{$K=os{?H$`3B$P zC39>P^pvR+9_YpU+~&3(xKu5>rEJR=UV2mK&FQ>nbv-V&)aG9K`zNK*%T?d}H<`G| z#Q5{X;R564j0ub9MgnWfD-s_6p>%jj6|a~(y6|t%LEhf>4%aX8mBHN)ve@liL&28? zs{f1>i${l??Vo#1#vU|G44Y7CHA-4ZUi8JL4>PuWYKX5e!1_HGRlH9%AO53cx4f~e z8In(<%EzUJjcVshh&FrP$pHR|ixvgfC!dxZNvcHpRq8ArcUMTqt&?oC;C?TuoVG%j z(_KZoi%oCq*ct^xm1jUxJ1{6}Qk-MXpDqyj8Z5IN-#%rbF-yAlH*eN6>8gkFzJ5r$ z`}ol$CH}0MS$lYgecO6-IL}uPy?IUPvw3N2p&hHJ7Yir3I%Xky6Lsq5T4Mr(pSr>F zO!5Fx9LDy#(wpeWKIA=c{HT(`irKR z?`*;B9__8aMGGcXj?b2c4ar6S;}vGq1KQk=W8H#3@>f*5UIwAJ+awjM;gC@>I1itt zp=Nz#vi81Nz)zuOrA`}1a5O?^`8s6L_mML#$V1gJ#XEAI$2%sH<(I{FuBud+MgOU9 zfPABwTWNx|0j94GN#kd#RT;_x7Do6Xfy*A9HC@6Zu*O~)r<~I#!r8-X{=PQkVljQ! zwB(N;=uPv68s{ct&I+J#8=Pl>jd^8&_(s4k2bQ zLL9@f4ORo2X0r#64Qya57MHgQClTAnl9w#Z*h3^=MG2JTE?G>)shK7POkRVGkDm~S z2Pzq-Gxtg)*Pl_hD!^;oBzQ)BpZA@qllD^9fCv$3B}C5Z2eS1iLYRG{{=6 z%D7KkgEj0j6{^TO7&@h0En~X1cr4M91^T{s1p0UNp4<_;N|o{-(1xd}f(78C3GwU$ za?!)>JtaJgt8K_AufU`$Hwbk34!`O4il(Gt@-tzVJVy48mr~Tb2?krLY z>VpsZkDlv=zw}Jltml+G`!{`%ouxMHHHm)SBLHtfQBUd7Snw%n{ycNmhslw?X>#v^|K_NnK(ml6#MZ8vL_ZPz zN0p9u2=1NNH!|@IQPc9t%XDCD- z21)HsgZI3cc?hnNpyEF=wI$T>vcJfRtEqBUSfHoDUkG__7HnFACVw2CeV4r(9#Fi* zx+Wei8Dw?b8oR}ZdVfbMi7kRJX$fX{a^rvhHNC>RRW&-|!|JM=2zZ3g2-fJ-!!1~9jj5uvS6Ul_M9tQk z#)&x5%3&>_P5+T6KC$;zcdM^ppRye|Rx4*~^7qh9c-~WD84f?~A<~voNGQ))N&H(* z7cCXN57CSt7H^DI^!W+0_X#T87~RE`qYGPX_OIWDbwzIE`X^TOLjL(p&@&cPaTyQP zSMPWtU8bX&AQF6!o_040e<0#Y`4W{^az~yZiStIx9oh0defB@NeCn^p7uMy#@gtWJ zO{o+=3WXIhHWi{mmFe@;r6HNBRW73SD(dHVQb&F<$v^3nTd8$xkhv&-x;aPkr}ymu z79TyqS7BecImup3{`Xz^pc1L+uK4AGnCGgse5J(ofr@LNF#Nh|`$8%!n!e-3(k$v# zxq%Jb8#%RtE8g?($wq8Jo?z8|mYe5P3Pl_js-QknDWGAvU6j64J$|jY^t9qzkeT0$ z?>@)a-o*#^v-XDk6NR#L7manTl!KNIZM^`_yNJfEB5x?ESbQdWf62ss@!DEV?@`gY zGb#~`_-Cu!jZS#@>F~%F*!AipZx*NUz@W}iHrO({u#a`^`eca-9AN?q<%#M!Y9~KR zA8b-=Q4{_nH6H6y>pn1{=H%_?;D4t;!#aUOF6)%m;0u4Qo66A9H&}a#*sGLEEmX{o zWgNUQ)oqfPtfR1pB7c!RHhOEkWG*MQVbHGujq(vW z4zL7?Q`>gauCemlGV$Ml($?*w=jYWu591|uvTGaEQBxp|Prf&X`Zi+EL_@3gu=hHT z9DL7tba~8_0S(lULN{Pg166V*9>pmhzAlP5r#%x8y%4MJeIp*$k*~Z69?qCDy92v* zPbGb2haMd~DaQ6TO+25^DNq_eY7HNnVvgR$-TYM-o|is#QVh|Ngajy7)RSZ#sJ4RO zwPWQC(16*b|6*3vs$rjAb}>ELD{Iy|NZ4JBN)A@@l!+Wg)rzHFZfa}Zisk32B~J>9 z4`~c1QnLoBn;wwYF2U|Yh+gDm%`=wL^wC|icKjkO_IvLq1m^ zKDJTif*!80hWlp*DQ|!*SVX6MBzNbc=Az-t-&viPMiU6Op7oet3^<$%eQpD4#qe7l zYRgIaX%EHTVhxL#A{Aea z2g)x1|2s#%d?8=84nGy3@OqiV%1$MUPkKZkb1@?E5w_|xbfI=~p*P!Ra=C7G@ubR6R7gL^(eo!cdek2F;`i`^47I!=jyq$qi5(nmvRVLTI7M}F}{w*H=Y zF?!(IJgxNim+G@ zww*C_B5Qv!ab9>;DcYeoGC7HCempq?W|PtUl?%Wu9KK*j4RrFZAD|x3g%ph}q&`&ejox=?ryv06#!Er)h}f{$1edQa5O z@5KwQDtZ4Ehjb`!ilMwN0fqw7fue1ef{m&|{V4Rp?a{7YZ1NLd<0^VgUb_Dtt)<5E zEdpHUp&|{UxLavoCO)FeW5r5)KC*xH(yr?yS75|TXY{}WnAkLx`4qJf@plcuTf7M_ zfxb@1F^1~jsBj?yzu~A_wp|)$uk>mYS?G-(YbRsufKxbevj%!Hgs7;Gx?Dp;;wCz6 zSVy-_%}4;2Szrr~0bMcZfnmyZ1=lGAudP?|u)%LCaCkM+Gzz<<1iua&Uo{Dz&Ee;6 zMAi7?=P7tRU1HdPG3Mgmo{&2VU(~=)2Wv=R;(@LLnT|gux&EuD{W4+lEUvKz+r0u> zUpBG-Dsufj|NdF@nn3u=73p^*l#>~||JZ~-7$$|G?WD(b#p4$U%P7vfH{u{wh0j?` zEF>|%0$cQsrA&voAB1lfBF;rqHhc`rog#HvI<)BcEpq(^Ri$v!H$ovngP5br^IJ~3 z)?hvAgqj2TRGtd4rT*=N@m=H1Zpg*`!sGI+)AuGWongk!*m*%ic0laThB} zc5^NjV=t1JZDy`tMfQlE$<(=e?1iE7vPx2COF`DgsWM_|QE#4dq`&E@p@lxs0JB#v-Tm#s1Q4dgkQv04OG1kROGtkDCflgX=RToG0)v1?qGSlo|$)pC|dVloxEpu?tFVT2d=d z#SJ9wQ%nLl#p@zeEP*6TQMM7f^ZuwTG~*XLvabv=kH+&4GdramtPDtKL5E0U*&D@K zy2L9f&uun-%ZZ~tN%?OjE}jBk=?io&K`%1}+zv#$d_vFxKL1OrMk4)f# zlh9BX=(~%_aN`D?B4inwzbyp&BWF5|UnWr*dOY`*pzc1{7A`np1&g;%er*F^+Trz= zks15(5G7b{Zm$`% zQxcyJ1@=xQe1g*-NUkkHx#9TwZ)C(Fm4J4NbBgyUo{ID50^6y@A6cah#5#4v_6%hq zM?R^7O}>m|ErnNhPc_vbm&2##7y{1A;qM;srx#3i6|hvE^+-hihblO#5qtII=cJKE zQ*hOE2-lw4yNGttr#!5oU%RJ%Er7>w30cYLu|ToqUNG${nionb`zm^9)5|_^O;t&B zwSspz)wds(GshV{> z!^L#;J+50Di3Rc85z5_}Jv&}vr^H*>Ns3dM`#wmm@#xBf@TRFL_1TD4gJ5;r#6*C~F1!r?e!8H)kQf*^F(H17R=t-c2BThv>pt z1Ph!@I)(gA7Q_@Ff)f%qFX&?fw8((^o}q9eh4|noZ)Gf9*Q!{+r`=8I!hCA#11+s) zwswnWk0Q^mk6EX)dWq3b&PZMz9=4o5kRn%<4fuUWQqGaFc8Xe$@L3^Bo|mNTWO<8A zG`Eom-V6rk3g>%6SGNeW?jXC_6XrgUnl3TD9m!6T$_+3HKI}iLRBgFDdk8;DDVSzZ z#b#=)2FjVWx!Q zER`@7Le)t=TZhoJVg3FJG`#IDa^dTOG3F&uV7^!h%!3m2w*f-Jj%*cLLS zTCq5pS}~Jb9z*^(z^NXPDcR7oa$>^^c-1w)y;ab68Ft|EtJWf0LIjL1(%&G7m7o#x!5dPhmU{NPoVDr{+)Yy@K35 zAY8N@y}DZZ!;b+&0n|z+E+{k&)kkK#{?LhsSLrqtOvKLJll|X+N z3Wy_!Zr9X#d2neTpfwB5@Br#1fX{iXu$XX-RsLl}$S3pM>d46~L?%%A?vegZBs0GN z3j&~Bnp2UfuwK}dV=8hgTRboW%0B?XD!}Et?3*>r995R(6iJ+uUw4UqIS#%9i2dGZ z<0z9jCR#y5%Fp@tG~k%(shIsBYC<}0fZ9GV=sP(4KApXYyi}+>7(#}f=V>^S&gLBY zF0F6Ecul8fuLr`708dp(YzVw@_r!;CXx3Hft-Hv5UFqGg%(*`-lk334Vl+;f?)8&P zz-ZPM>`5nn*pqFrf;tt!_y@pRym51P@M@jNz!}O970bXtheqO|3mSh>x;%^K_REPv z$h{7V>@;#&3&${pw0w&Rt!bYe`eOx|l*>5hfr=NWxH3rgi&&=|O(v(-nu3p1VMrUu z=w~&Cg6gM{uiNP}OE_J@WLgF*S%RBYVM>u;`lTtQ1Hg?R;*fWsPNHbXeQ4cQiSY`g zdnF#F2QLA^lv#A$HTjZS>gF;|QVx+GE4MF@zHJPcUnjONfRnG&Nw;zP6VRRGf=&wV zd?S2P55)wLr!yGNUDluoy1f**LDO}fT)8+ZozFg-N?hn*rJFIDhY8==;1*Yr^I^a} zLNf9dtd0;3>O#v-69N#mS}mQC%hXO|Z%?H@J(g37r7WGet_n;;F0|Q<`rZJQN(i|$ zsPqjuXef9+9qQE(x1E7~N5pUPf%vshR5v)y50s<=u`4k9b2R;!bFP8Rb!SccLU5jK7q7Cnccn!{4NjVKT(%8Y_EP5~?jIxAc5Sv>RgA7;Oi{`3PY zy+k!WK>h|00Ws+5P0X?+$%FF%SR}sj8?-Z(ZaxXAb>Rz9aM?^q&l)zF03;WI`5i3o z7P`!Y?Gs51J8^HmV=ScPKp0RmB@SCh8%v1fx!}54VwMxqTO>&HM)Yn-w{2(q*0AMG zfjk@Rc|Kj1%1SAu%+%Qwms0Xp8i`+rTP18}@k$GT9{8t6&tP$VK160hU zwnNOc3)I;!z)gLy)(rl!nBKjG{+un>wS~65$N8B`&mBd>MuAUZRQ49e#*W&!m#Msq zpGyTN)(e@BZ9obOpv@o? zuEhExr3?xdU?fAk;qu1U1q*jdU-=dICd@E1}vX z=By1n-~wadg63SI^v|)0oGotYgJ=fNvVC)^nS zIQV1&7;Oql3xM(@)M74Uznq<*MJLyzPXEZAdT4hkVB9HDU&5Hi;E7($)?}$mCjC5z zp?sk2e&m)bVBH%aAcflKE8iW%m?g0uE(QE%A(2zS>(A8P9E#2a7ast&9*~Ug08e;` zz+$kZN!*eKg#INRU4YaB&>~~#UoRMD2)J~!Og=Mqg9rgr*JIfHUMAdtb{%HSZb)a6 z)Z}#Xfj%?XkN0c@8a1g0IuN>>ESaLt*mHwcFj)cY1xtXdXE0I&jEshot}tF1fcabo zv7(G8Kmrgdt^zN<6K_@k$$OF~$7$!^(7g~)4F(s3pn4(FmBTEa!@B;KR@uXPvx{;3 z0=?`2-&;$TxYI@7DKD5=$d`8fpx>S$JNkece=;+YnUIZ+hV+A64$BDedx(uJ0HzOM zZS4Td4v4j)cTIr_o54aO$(IB$d96t88t76k+L6cnC#2Mq0H67w9}Pz20gu{gzdjEC zK65)7Q~pNlr=U}dz<@bq;CiOKpFHt~ZdW5_pJq1Sk;qixkZY9TM}YZEDKDkAZQx9A zVS0zL6_0_71K5}oIQANnao;g{aIi1X9z=ajheoQWj7|aLk0dR=fQAEJ=EDR80}uG1 zUm-O$PMi3%ZFHG6N7$wM%*StNnKsbT4(exuroW_-R!pKJ8S)h18H>9*nRI|GkN{@0 ziPPTHQ3vedW-3fsuHFVvFGJ7$VRGhS6EyR;84{ZSBMpF#91?eE>NgG?cq2Kv3Am9c z6)a?y#8a|?j!D=liw90({hkaRm>JU){q8}O(3jK%$N~8)d+DHNcz`6 zic%p}%q+fx8^2(x_v5^`belHl+X>iB(mXf%Y#1AV%>>=T-e&{BY3Nk|mbIL?Do~{X z{tIJ9bLmP4aA~DTwrdgKf$#lH-#tRLK&1OocCm%;Xt(QKB%ppJCm_J8m-Sd!;`v(Y=w(vKPSa+td|j*>4BW=%c)cIP)#QD-xLN z3@x1l?lgvjFXH!?G3{4@v{XRmHLbk_6b?$l3h8V0WK|{Eftdtm>3WLF%MY=ky`h7eL}HUt#NYPGBAb&p8zI(qFN^718SA3*bHpnN6;;gsPi z_NEcQy$ODj3r<{z`DIY)7TWS7F#9EqZD*qUsY@oHVvuC6ALE-robsV}dr-c*vau#{QB$7?K^+85*( zDX?`8;jO_Wxxt5`7|liK={I295m>zlr1rznvK4C{E#cA6svxT^z>00e(`?{TjzsPu zV^}3!benn=3x=;@ZheJ#Rp9<-%#kLVeTnr-Dig+7iP4N{DKhaJ{1HsPUcgw_(wXa+ zhaU-L1>l}3PP+o*o+OzKEN&y~*VFp}v@}xo|Bl@11jcrwvR5KEg#!-0%+n(1HO*Yy z&$t9Yk_J(uGq7fxREJMLsw6c^>CIPxp53626YbDT#~;VmD$ve>oF+L&)d9;%0z4ms zgc%q+MsAH}o;XsPq2M}AN$~*CK7?B}g74o;g>Ivn8FMX%vZI*fBxYzM^WQ({GMn1=o>Op! zc2$r~$jn9ro0|#FzE5otFkN9(Pzx|W7@r;iuJn*}alyt}c-tMO`x{N3rL2FkZY~Ej zw!lYq7;Rftb_DaP0a@?Dc*a4+nvn8eoHYsA+lid-0e-%Ci3{-Z5)KqGkJ1>IrvP!2 z5w4}w<=FDqsYN?EBW_HP2^MbzE>;B$?*oz`%6%U5`Y_YQfX}i-EN@`&KCV~JxPKwv z+EO>}q6SV()P7{6BT!w3+#Y9^hF~%*-N_wnFaf=m00;lT(+x%MWqdkEDoZS#&BWo; zwEaD1=o-_ukD)PonH=(~j_h0@S9uZ0+K8k_gP9GW*ccj9q~h|K3-f{7U%_ZC$-*^& zVwW^y89nnCEuKf&-T~{BsjeyPNGqV^1WhafN;R;g+n~)x@aaO()dxBNB73qV&yAoA z8*!jBo%)U%F{8ELP%EF&CkyHR5qf_yb0?V8(2!GE0Zi$jxH|O09x{Fhb!srin&7)a zdU!c-?I&q54tO|7b`zBGTKZuF<#(I<_l{WemQ@!cglv^Kd(@G3i(BL5|YZo1C3-Dw%*8qx21lf}N5CASD znx0ZAX~?@bjMjIgTL8+NK-af|4Y#0`>w&D_koOwq(IO};7nF!4@f7WGl_Wu0VI6fi zmI_h;v?7=zGk}d(fRUYGD^1Vsz^>c`W-v&fKN$2J4xI_xY$dvWF|AWn-vj2)2lD(g zAo#NMQxuRKDSi2czG?_a=Fo91=ypHZ-WpiF5?E)A8r=r`T423tKxzPF5dq#kLZoWM5exWIOYQs!r5z#5o^ky<7-Lh^ zV-u+S8#FhD6w4{>EKvHDStf=K>q};pf-h|)R;kR+YEpER9*bkf_RvY`uv0n1M&Jt{ z=$Xq{M$te#0AqiFRdJB_BKYDmB9Q|%)=A9oF#Ovj=QI<1n#jFE4=OPmbm`64fz18H zf3LaSU+91c*)bEadLg`XANcz?m?KL>=`v3zz`Ab2P7zLW7PUcu!Z_g|q#cx~1!t%! z2F^}qPPBud31hSYiJHUsOvgNi0rpSmzmpIrmHy`r+f9hAA>hO>Dklb*?~Wh&kNNU| zd>u$To?%kvlPyDNW<0sxgflOmS$+XIQpy;Hqfxft*%0RE9Pr==MmhqwSc&W9A)iH( z2XTybCJ{HA{`(XNE2jm9knCvccnUaoAr<|QbK?xK`7nI`0`zw_a9A5*J3@CJLw`3ghiZ_tyMS*GSeFCDhC$&p zbbJZPA>h)@!V5J_juqK0qMt3NVuR^dd)6DpzS*;0@5SPEUVwgJXX24g%R^Ipg} zf|+*%en3b~w7`iJl70#9GM84#-r#E`7hBVv2dO4EBH;tJ_l`kn8rvvhcA$#&$F@#Jb?nH1N z1u}@tejlM@oUwvo=6Dy<77VVe09lXV5d=TJ5n8cB8tldxrIObbn7Q^OdV~s31MBY5 zPu2snw+=HdVEf#d{xH-n3oPFXZ@35VOQCKGpo2c7coyh0hmQOT^`wdE7AD4yz)n;8 zTbXV5iRroM$~J1Q7P7F8?o~uzCDR*vuy;71z7ERPfk1cW)H!(NfF!^IxUzusILB!H zB%=IjO9#fHln#{AXgakk1P(t%Wh<~F{XkX_6nG8V;{_;gf|HxcJy*f1m$YdGwAnHyj6)Dxpw_ob3sAO#gp!Wor?RQ~|~M_*4;<@*3DULCU=Xn@mWT z-7M2ddSo6_{(xDXhxpzG6?Xy-TcH#K=HUh;Eem(Ph|u0b>_0%tCiPU9QFFS6qOK-@ zNiV5u9iaX$Lb8g}|Agu)X1$aH>$*T{J+#-F;rBw;Gl^zH=!^=f91k6Fmn>hx`0=Ut zD=6DI=21Vsrva0l^6!1iYCAx$GDeoJVNw<$ANXKGBa`5O{1lNqAIvRZ@SqeLeJc%p z4)ozfn=zA|OsDUl4E3S0Xu@hHb~J)WO5&)WW`1_VuMU82Gog`naQq))Mhld1j;yVM zWyZ@qd&u8P`g9Jh5<>r3PP|BhyZ7SBP3-%o)VG<)xH~ms%DUnRV0!S5Gw?_eExeA& z#fZ-?L}iGF-zX^jhllO}KK2lLp9MK-D_K&L7XCJxe+?ahT8N9aLvNo;xYF%MCMyg zU@`OLH9SKJ`d9$$+XtJ!qo#7<=s_~(4Wbn#Ue^l7Q&NhcN?T~BkA!a^6x@yrc4A7G z$=lj!tuE}#_;(uuzb zCkCoKiQjPNxTI0{?w|qQz(FyjItOZh0K_UF(+9}VlSr;Cepe44*&;b$2kbgVPUTXX zX|$pZu}vA>)kj1bpbvA%nkDEaj2@ebJ=_f*P=n^-@XVRio?c|@MoHHu$ft(rC;;I& z+#sF#8BBVbQehCN_ksY@(7tT)**)xS5cA?YoRtMUzXHoU!ngfs{3aZ+kV=t5OoOFT z68zaJ{W_be4WtgUs8>tCe^GV*YYL{`GvUXxkHtj&{5Q7>HXC>yGQuyI%^fB*={ zVgd0DfLxXm(t6DJGE;@U=GiYCo3cNta|G3P+g@M-g^9IPniy$b}2? zY5PS;&=q_^0qizM+;9{)Z$|#8qF1e_QEk$$8zFX*@%9K7O(;gPi$*E&P3&n25I-Ga z@!)Bjso9#y$9l;&E<9q6AA1coyq6>dGVwR5>$@pOZ=fiUz({OsDcL=bHF=QMnMB_B z1GOj|EribH(Xl2-Vlt^ILQb}bl19NhE%;|&0NN$nSf+$cK-68bstSIBka{{O5JaBT zWq*!fT3g_io}k5bFy|%QeH%C42oIFvr;FfA^Q9Zgz=Z=+lPr4ZCAD3Zv|0?;2NO+N zY?Bf)F$v}Fq-N%0;bp+{a&Y@?IM9vmjX^INOKsL7Usg!YszM!HLUkTs_Lyu@qLv(m zCw~%mWd*cDcy$!}+AujpU`2nJilcDRX6T?Ey)OVRK#7XKa9$rCD1sGtioeU|)z8G1 z&4X&IrPtV0eAAhY|4H(yZqyM+IHR@wmyw!}KS z04xwHZH3FjsL;%Po%Dxxdu?d(S0hL?Ar2W^d+1*1l$&btCk}rdk%e2N z+x-!*d`V_76eyKmN@O@4l&Lvomk6C0#q-Z{jP8?aUYPBAs%$rg=>z_kq44#Pb0=+) zhTPAPsBnX4aw38$4)L~3EF_l(SkrmV%L+rQ595RR!2hN_~ zvP=g0E|UrV4S^qJYY=kwF8HdL*#8l6`7DmAgPzWndRYP`VbnGjtz8GUJtfNyU|y%l zk4rH5D6;DnYxp)ZV-#AJ2rh{PHZ;Op#M0?auqP$H?hLJYLg7kS_>&1Kg8eUhm{{z}AV zo8X6sB?UI%#(Ml|HVu~o-?-FpIubaKw0?l{(uiH3*sj~?m;`k0R&c%=IC2#>X7E1; zVCWtWmco}VNSY&{+Z9qz4aRALj{Qn12SRR#h%5`%rA%`AdD(sx^=Bz20}E0MN@L59k{WFOuk6_X)=0a#M$L&O#-3T!%~u`3a4TI zS7iYL#C90y*aZ|6LYtpcNj-?xb#dVq`1n`JLnBaK)`#+C9?oR`zM=M~KvFNVxCzyV zNL~Q@`ZHQ$hWfq*E^dYhE?gQ)PCE|Y+lims5AU*&_W3|Tk^)mjZcu-zln5Tj#fOCW`k`AWMT0q}CXE{u0^5#i)DayL6VP?923m9Tx)|y20Ek z$nzkP+5y`{io>|@&SdGu>mXMKNkuTbh4fCGa?c0tN(d#2{jHche2+C)MumMstF(c8 z&%h(Ap^Ns^=3#j1yA;<%hD4$lOQ6W3`1fFNhc9t_hOOoVO=5fuI{Z+o)ySkyql3Rv z>)OEUp(HVlWmQamRb!?0lO~a@_y^210r=z%xc7eyorPOeO%%rG)^@PV(ikWpc41&* zH=<%-x1xyM*o}&bf+99xA$B1ssHmR^qL`?JtEwtFz_5wqa{S``dO^yS86klEkZ!fGBza3#gO#ecam zo2cP_V0b-d=#R_$!4bnqqTGIgN1R{gOmSit$I`u~f@6hT$7#@V4eNA)oiT;-^JKep z5oD{Ns)*v2quuX`0k^Rc!?+f^@!x$J-@(L#aA*EYyq(6OdghvxGCjw_8gFdsGuAJR zccdG4|1H)x5 z#G#$Ipf~%M;Z@vbXVd&6UE%%TRA4w5@dJqSu%8_{JO=0PF!E~RK%o8meEgrz(I)`h zh~ucw+(LqAb)CaEkX9qxxdnei05|s<|9v=$-9*H=VS8$jA(&|CNx#8}MT{fv79Q{C z0=s~X6>RVlv}!*}FXGxaCAL*EZ+wME`*Uroc(Z@PEuV%^@M=)z9E@$oG1Q9*?S(uz2-UHZ(}aZV#rr0Ifk_><#w8gl{CAz8?Qc)yS1A?l7O2Q zQmcy5<2Y>T3A}PPeQh`HUrqaczGM%WIi->A<>x^#JJAbxhg}1`QxRKe1z{@k>hB z7*a5<8InIDf^UE$LqS|;e0C+hJ{51h-&wSsSnNT@f?<)6aTUxVT+$ZmQZ75~1wVHTjBZ8s9|yuNgO+3QO}m+QE%C$W8Dmdk zz+OkP3vq39QVC$fJ?_XYD42tfxXMM|qE>z4&=)GOnETh0hn1klHCW|ZFsc#lX^y84 zWX$*RWA`2Nt1zDkI&lurxrmT<_04uwJiz{I(h8)gv zszAzbJ2#{UFK-`Qb(SD}(D3hA`ekf3$sFyC2dJHg3h>iMoG&Y}o=2F)!5||UHEn?B z5^%7VoBM~C8^d)wz$=w?PZIT`BV0Dhlh|4hNBDJ!OFK_VivdM9X@xd zV|XI&*yOx;3h%#)?%<7mUxeUvbhi=zbAemo%~NgSP8Rc?o`sQ>L~kA{AB!K9V17;6 zkQMle^Ue<=h?`3snjW~WFLS;gXzy_SDdh0OH@D{obl{)Vc zz4L~5bO7hqgBo=hb$SeJ^RRznE-MM|ob7b+ht^u0x2$+Up>ud2>;Q(&uS3>h_y+;} zWFysq94h5qG;wLc)ISJU5%_;Ez^+2pZ~%L7i8(DN(n}m!2l3Cy`S1ofo5g+VjLLP` z`azJWBl~A@Jd9UrX0;MNc?0^?;spm1UqnwiSLKM|Ly!zi+fKlaKV$;{ zNSgqDHbEEK;Ez{w9rAhMLt#la^6A;k>b$PP zc>frH(4cwnTqK;bh6s|v!Gw!E!gY$^fwA!9DN?xrZC!!)mSF?U+?wII;GwhZCYHF0 zzMPD$^I@=&puQhEbP)Lw*fECdwTyb{;I@w94atI>hD_gwn0jn+A?SUU^9jbH1K52b zc%QS*xCdC#c{n(zNhpG^$I*R%1 zW#V^XwL1FW1MKk*dYuKV%w>m40Pi5$9fdB`VebNA^RLw3Hc)4vzya9j3E6BQkS3um zN5Q~i?!#=XPa5OzhV$ZG^b<^moK@ce%;hGYK>L#M*OSooV?@COcs7^XzZ?G2kjJv1 zd@b?)9IvAlTbT z6?H?+9^nIppvM@Lz7L%I?HaCPIpxlYpD|O0bNOj(&`9Rw3^4uyy0#t#9KcH!z?wix z(FgYF$y@gphW96N1!|ds7Z(7QW|tLWbGFcLg!uX|&Y#_|(vfWFGq6q$GYe5}5nydx zeH^d$8s~ADx9vCgdkxiUEGi7a1}p-db#QGFCRQ-(X5(il=aXgli%e%tHI{_4T@C2= zB+PssE{8AGeR0147k-gCc7?N_;^n!p4?C&C1bDMO@pLOl>&dlA!kV69 zTHMC(4|9HciQOH{Xl{VXP98UL=Jf2HDgCON+ZVa;!{9d?3S)1fRM?)yxPYTy<>q`r;iB8z!D zl#pCbN*AEt1=#5da8n8KVEkFC^SU1Ur*__LiWf%F>#hKD1Ki#LeI_t?fnyI)rA{tW z!84tN-a7IOA0?V`pAz6_;}#TPMKvz^6+Umb^W0`^@e$@{A((id9WV#=3&j*;Uy8gUrMJX;0d>!HA$F#3= zcY3}{L0Z7=!@-+O)MO#-5kU%uLHjPM)nNFofcoYFU1i}j`hb_sQQM#3-zRQO0B-I@ zcX@(=7xaOFSZ-(bbRp_s0|Q#2+wF)egJDc_O7(}E*_t=#Cd4g-JO?%L$F9!At{Iuj zF7gYad%VYRi}QI17T$^NcM}Z$$|dwiz0>iZSgrVI7d0vmdg#_phUH)t^d z{JDvQ8!?X0wkyI$%%-(Ru)TiFoJn9rGdL>%@jK!>c&?p@SU4YU=t*t40-fJTF@ehU z#PbUvgo8z4nD#XzS&2=PGb1Kr0wrTT1fH7MIw9zp3LhuKgRh7lE1=e&${PqPu2a|M zp?PA0l7P0GLBt7gWEu=k!unU!ldobIchI9lu-J9Xp7-d!3gjoFqZockfKnb3Dc51? zRLUm1Q4`*OVz;5b@circF z6pw9Y=^i3X8pIBGkGO^4+7dYZIJt@At{$MS2wff)DlZPD+`yY@(Hs>Pvk0So*`^3v zF^--Wh|g$C_g{;(w=+5=m_7@QZh~3|<0l00=_e{@6g+v2s`Q2DHc`|fM1^CM9)gDi zTBr8U>OE%Mufd&a}@LUQdo&Y8+qGoV27Tru%?xbJMy_HOF3NN^&c?cC)2sVo!iUWCBgkhA7@7gkRpN6?xauy{==)rB ze_l&DYI&JhRf*y=u*`%lRm(qQ^U45cT;Vc#<7J_|7E zPrAu_boDgyAB0AyVE0_4Ff;L?Eqt+a`Lt%7c-4WMeMJkK+C82UlJrJAZ^j zJ&C>zDCZOE(*b+y!EF1CMZI;l)M3Zl(AWA4naNK<%shBr2qN-;^EcF= zBJZArA68S%CcxR-s8ONFXCti?Jad;6q{D&`s_r;Eb%UVd(c8Pgm8)QFigGq#UA>vshcI;s-EJ+0 z-ZQUWgLl8-7Yzy>fKADV<6Dq29ruyXV+-L1mME1Y-+Op%H_&!I47LNdDH~RW?dwX< z-+(=JWtm2SVP#zH2eiTqjJXF#>?7^vaLGAx!)BPAN8=FK19BI|ZxL1z%6esB z3-V~dg5PhMlit|IOtvZ%Sa`sH4k{duFST&`bJW2M*nJ22$^p0Ni5;tvS_?`hgHKu9 zp&GDi2)q3~HgXI7>oa%|$KIa+nn+>9FG#Ss$q2)%h<$Z%YYeG*2S+MMOCicXfOWF~ zd=>n}!*dI0yaHT#%3LHc*;S^n7x-EV7hFVH*TIQ1P?AO@ZiEwtQ(18+u01&<1LdyA zb92C*74Y@~FaU9T2VC4T1xI$eUF z9*JHDk^FV2Jd=n@MU5J~VHenwiR5#!%w^1r2O!}+GrkyPe`IRggT-IDS&PBUukctc zEQ%p|KY*#)?mXT^FI@Fi2VobQ_Fnr{D5#MzOkt(z$ z4Dh13DP6E>`Oi%+fA?~*=W^C95z6r6}gRs zr7`5KQ*d4|eyATBwiLvy1g_!Lt{WhzfE~XW+wp@zlduK~9zF$r+(CJ7k@zrHRtkqV z5XnlkW-`jtq0lu}eV4k3uvsUbNQ8_Y4-HLSXnncbBjV0M>oy{)0tS}g9>-lhHn^7odIxe3n}EKl+~asKv;!BJ1yTU~ zPy{CKg;gWamPz=G=16pe7?+DK-NGkMLKU-d@7ZYh86ZtV>o=h8zSyhzY=#af@38i! zSXcr(KLymaL$@}8!y2GE0Nh7owOdg~HSTanRb_ZDceK0>KDrr5I}GnnN24A%bI~6twGivv?c|&j{;>)@L)Y^y8@5t0rDqezg^X}0F_P#iVU># z4~%eUq=$)4Zdg&if=Jn+4lO2?O`>BYR6W zW;!NRqo6SRkRw!T{@=5=>e`n6Bl%ikivBXsSWaSP@`hccHFqDZ%TLudzh>N+Vj4`*BL}ckGRUqw z`1Z>vbqwP%OWrYCTBviszt~+|pmaR++`El_J&fI|G4`GBoIk*}mLx5K`pPDB`xaVi zm~~AP>w&?Jt=Xc+$rAlw`TM7$`*mVfTXNN3o>IXb94UCv1j8Hgx&`Q2Pun1aQJJr& zBGvmY*e4}hPufX?YxWz=djDtZ{~nLK1Up}r%1=uA-Elwj(S1-FZ@-56*&MEZfn24a z{b?%ro-=7AX6dhQao&C=TRriCdDSobClAzn2{HXYK|+zs$0iyTLJnQWHCZm37|u_b zC!f$!xIYIMZluFfjG4-YNO#lIxB9r=$l&_-%j}KgZHWS=5qF-5A_M;tU5>dM_9@5N zWIfJ{jhFbBgQ$%K7>BR}O|kY7d|O94tqJj$Uq4-I=rBvOC(SlC%CcVzb5`-RmC`G# zz!z^0hDdwL1%2GTg2Ua)2h00!q;g&7uX(ncZicER>f>jvlNsBAOzr-&y4BaM-skP* zfpmOtr&TYzGG00=$m@Q9;z}EL(Q0w`Zj!M+=B{xd3h#G+$P;?zwi|_=b=& zd-y(e@E>PI2{%eBOtOMbo$(2O`QNvb^A*1Lid3os#rj?R*vYcvlc{Q+<=H%qHO8_p z!|Ew-{B}<>akFlHscEFY!2oqjiixgG@!4bjuc$o_eDd!2M)JML=do0-KFNxpqixHwx_ka%+uV_vDE(C5=#O9#9VUwc7GSRWcpX-%otw9 zcn{4MX>=#%bFryrwCVgZ^C-FcM!KO*R_*wkn#gtfud|JBI@7Vu9m9;?)4!>**Sg>R zN^DDN?tWhN*28^pSD}2FaNj?wk1eKgwWfsC;=~t%F)Q5@kMjR(rg~x}cQF3*+9@Jf@%JEH z(~`foR;anxSW&F|FR?!Bfo0ejO+!<9i^|qJkgP+_w4X3wF+MFA%YE$AqS0sTP4}#4 zGH*uwH$Xspuzp(|xi7dZBd$NCz465`>wO)xsc){(Rdv_*_`%e)bM!sr*2XC+FO#`8 zi-b8|HA}?Xb}A&#set>kD0eBRu;+ERpZeU;Y^gqEk74U$_Or+`tu+|#Z=-fV=LKfu z0GOKTb}LBr=c?k<9eMU+vD#lW^ar{6JX}?6u|78qE;BEG=}fG& zQX}E&cys7^c3gXN-5%#VE$DrM7g#Ad>}o7>l*2Eo#^;k^Z}1rtu!k;drypMUfhwqX z;ybvnd`)c=-P=GNJxYH@44-o-`WM#XE0>*(O?TK2B#K7X0XE1hu2`x+CO5C-?bt@W z&qQ@ObibkW%V~~t0#0wReBZ^f`_(!hOY~FIpbd1k8BI9DOEb8)>*SO3M5rj`-^vh_ zT%%^371!>Oyu3}CxAJng8;;i*I#$#L%G6A{WovgjXRW>Bj3ebc+TMlDZH+JUr5B~E za?`|z7kL~q3;Xp|d|5A$cEEqfGTEc;MSpBJMzE`kiB-K$h%af_?jCr=&6Kg*hLV{8rv%L z3&+$y?rRhx9hdA-9Ki}7ithK2R;-t?oh9RZ1p62G+^FyjSTDUoiFU+_UU!t7y`c>} zsCOUI*zT>FxBuU_wc0zAbX^s8=Cxz$3rEr#S8@w8vY=UPs27~%10M)t&->l(CJhi1 zEtlGy1JUIYl=w&8Wu?BhTUB(By1PQ-q#VLTi>QuMXGxOUE4|K3+AJ5WUFUylw)dh6 z#gAs;f#@iVbz=^h<5&pB+E_D@(T4vI`p$ds?F7)kEcGRW5(| z7`M&uf%g$sG#mLG5v-kZyoTlfb~o3A3Sdi zY}PPTX?!qH{dcdCHlxnB1*tXUf1%`*+(C>S-A<)Y5%keN;tb{UL4iX(QH#J-ZC|--7?@Oqxbwd7i9`q);-|J-?}k z_hdz5psZk)%x?n|`GSQJ_MU4|#V+H^n@nO*W7inVq`^AgF55$q^=hLvvz;XBi}+Tu z;%Tj@GFo0Fr^vVBY6tq=MD}N)prR{iXkBpGukDd6;E9tzFQ}3QHYC{iz4DdRVQ~B>H1AC$^JRiLr+#c)<(3}J11xt z^)g~6(~nI1jFrs#|LmDfou8bj-9OmwKk+ZV@Ttgq%6gAkon`&ri&{=3tadQk9X^%g z3zo9Y2fLIEn&~}k6Qb*SbB0}At@mz1MulYeiAk;a^U}#V#>O(QssVDz-yhS#gU1h0ivQWFGOw*V{cP9)BO^%39)b4OGlf&yV z8dtcvRVVXbnmqpciPv$82!-&hKUw&ajXtD%^{MeroUN0Jy%ePRW;NtY){o3EwMwn%IuRkl6+}rCKHt&n6a;hPtH{)4Bx=yxT9}%5XxWMuz`EfqUOFR!aC4ZVK zyKfPb9T@*tmN=_ra+$s4qWZ*c!%FY!sVnNtH_hLA+ZU6}ou~HQ6M<}9^PT`bWLowK5=U#zMNc&p`EP18RZ z7SY-(a%sdgae2CO%x^({jwco_nlQsBHAq%FN-CJbKWf4P0onOmt>$s#*9)4Xz3H*} z^?SUnwx#AV$>>NJTVVkuSH(S7iQm8Vez8(D`H*t%G4UdWEP1tXUmcz9V;fM)d~f2q zb5%cBWT?1Z7cyGA@R?@tE3L<3{QfAxCtn*5g|M5Bnl*$|(VF*7 zElARPo*(BqJ;41|t+bPaH}p4ZTEZW6nddtbG#N*H@YU8IHw3+`$@`-g-8ZcKXbWc9 zQN^(PS<-DMdcQ#YHwM!?l$FPLYy7;121+)iN~_d755WD)pzj|sbUkiK7h5&{fGyRJ zO`+W#nt_cUv++i;{FrSp2_6oGHI7L;zQ@Do6C4tN7H0exMh3; zPp{)mYS^^X5Fc+^6m0)UahIO4OPle;Z{f>KaY8%v=&8T&Mny-ZhiZdx#6-dVGVrg? z7INDfx!g8Pgz`!aYX>kl=GINK>L_1`kMbW9AXrF;_;QzYR*LPo~n=_Po7Tx$@rb&F? zI(>@+d&WkMvKfB6na>E)H~ahZ-6m{PsgtN?TU7xU#3euJ(uLNCo9WPjKvG*TIjNu6 zuYTYO<3heV;kfY_4=@XO1BWQzq$$tpBo9xK>RjL5bsl$u-PR+?-z5T7y=W>=O&!*H z@idQDJ6gV|8#mv4Y+mD%M&mnARyUY?cAq@#ib2ctwY_qC`%BeWN*+At=dsQ0FrWNy zqVs(ZxGe*TZX35xbv#~P-C~2f*SN+Wi_KTpS;UD<=m7D*LK*)!|J_=2M&$qYpr^{s z`;A7iJ3`vDQv7M5qRa++5HLVkpyoV#jOuwchjJ;N3~mU{h9L(*_Gz>Ke}nGZSV%X>a3ud z4;8%IHF#8As*xINeAzSZDnCU>f62mn-qthxT`D87(V84?Sft>(ovdDcPtO~t8T7<) zrOue9vb;OY2g9h|lf0db{PQ=JDxd0A?6LST|Ib3vCnfmyj=-7_Q;ZGc4y%)A*SB10 z9LLj6ZDJdscHHy>$6MpAH{#bBp>KCGwS`Z~|J!cgz^1zQ8xUS2WzaY4|kJs1I}cUf>*W@$RiL?*RB3%6m#hOq6eIOZS_# zD$h;wtXno&wCG#uf@*~vm736t!bw@=N*Wj7Y;HVi107DFOD=bKc|1Uxp43+1sf#bzMyNV%1@j z?dRQXjFk1an-cpq9k*>U`e@;d8*R(&&};6ucU{3(pj;&4;I#yPzas( zM&SK$_13=jm?{6Fl8wEiwM(ZvDpTpHmryNFx;#x3ktj@m$QFL~_B|=>lIq!+7MnGq zbE}B$dW+_Vv9C!x-^()bjq$#UrIt6otJ6P8u&w><{N53*yUwQl_2|+^(Xq&5!7I@- zwR@~XJU&AhdY7C(2aOHoEnV!KH-by=UloZr`qVZ!`rGoVHAi+be|)jYt(61DQ%M@o z=9GJMfPBbhziCzSj^o@mz2lvz5PUjgTkC0xk=IvzG3k;lJ(|^CO3}>fV~`a&w=S|q zUT_4B5FIC+|T1RG1?i0+PvCF}`*_h#Oz5AcV zyN~+XHsdg9{g+Foc|itO%QW^CtcV~Zrg*BF`xR?tpN5GCt@eu#kZZrmVrG%;3ds}u zg`S^uRYv35`1)qk4Z~6!C&${h<<%eC<~;Zq)wCDS*ActL!0^-iw^jb2&eJTBY!`Yy z{V5$e1n>30k}=%@RK`A?Em7Yc2ZvP68>ku7LYuNeC!Asb>$J({$@5Lhf;HmV7x+Er z`$WdOc?|M4oRJkD6}}G>K03m*{{X&sX($ZTZ3t2K`e8o*xkj_Y*m01fHO)KPlI^n< zosE#YCQGxY`-N=wWNvw!+2{6Yq=@(rO`zHGl?MB2)+MgqAgUo^G!L>GmgQ@7txTh@ z7-!BW*9KBqGlXHCgmbeLE1rl)@Aa*UQ-%k~H+B$Qyu|OYN3!&$;d*nsph9zKo?&CV zM&V0iSe~KOm)Wt(^0_;_w}P0n0y(dE&*pnK-J~km>i#B27WYTku$*+e!g3?<&coUK z7J5%lLs{SYuYKxN1GGK<(`{Ntud&<7Tt0hQ7-LM0 z#ayG$HX!g#AJbFkF;EW;_V%lF#R7Zg9-ZT_^Fx61u7R;^k+vEmeHZOMi1qkyjk0{3 z^h%i6bBDkz;)}vKB9$EX}D%ny>_iYIxb!F|e&h zyut9yOZ|?fvrb!7d?t1=(J4U|+UW83oll2&Y5XDK&1~K1rK*>|?iiap5n%{l8@2Q!dBer>XToTNSR7e@o z_)fsRgY`*QORMFMh(Ocu4BLwf|HvW6;5-e#t1YIxV-LhrpSyk5`@d`_rAG>C)7%qI zN|xuUh(V%+R{UB&etDF`KT+5HTs<$R{^=2&`Lk6kY?z;5p3s8bycKJ?7~cTc<3E*a zlHH2keOphFDsOwZwUiBMDv_UI6O$cGFUy+Ej=Vltrnx#YMOXi$f5`6?;VX-cKx2G+E?q%Bctjn!*wm5 z7|BX*@?&O3rDNwq;rnj9Ne7#poa_;hqWXMJw0VhYtc03=AKTMtnb63+T}Y1lQGceH zF|}3g8oM3?x;2Hi`gV4hN-3eR4I_UTC7IWne~$Hy_ERdt-Re2fQi(L!UpB>V9e2y9 z8?PPHUpLUA{%bZ~pIx6m#M(Z@cBijneh|3y$YEIR)8ViODRF-_OZ;+`x3s$?la|*0 z;+h=5ijtw$)1>^*9($~2@>*?Xyv8)!y2xAC>Y}6d6(VA*{A>p0s21Gw_E@%GI?C>q za86!wO2HJkEgz3=`@##)4ABd9E8AK&R?zb3f5-0Xb(|))yQTF2`#zOJNm2L> zr7A}<-^1&{GI4lBHGu{rRK6`i0I6H4b@K? za+B0|6ZFBk*38k&&M}TrJ8@fc;Q9u?MV?VzRVlSzorZYt_LjQ|r9bas-)4jCMQreT z=l#ouj{WSa1r6D4wdD)d3&&cNl(kM~jX%m)BBs#zT?)$U!SWd@oIlew2Y;SY&0D$31H zN<|N-MeCJmYN_(F*RmvW;vnJCH^hkB^e@(={-+zbyYXL=UXf{K+Zk$aT1Kt5?C1r{ zexT;H=;?83CqJd6xle$`qgz|~jAqi!{{+MI*o8MJE|@#i$+`0*@aES)f7fvCa$VsG z!`#D`*T?NGx4Gyx!uzyvZ85K`Le_n%@U+=GWVZs{klSuhg9Zrr^YMB=+iZKIcY$`C z$Z*M`Z*$ueJVZBSFMVyN{f?AbASF69=aZ!OyUTu>vu;NdBySJN`>hf*-zenrQSKzT z!H4p0)Y%s4`kZGT32qn-jdv63&wVnk zp$*4d;W3XOT+7q#BfktmA$GsNZ&kq`JXU{{`aV~LHS$6afX(&#=1I(_QO*`NjbxzZ zetON%Vfy^lMp?aO*9wR4LT+}d#QwzXZ=y`UMif@%)3{kazm>wcoqYX3l)Xl@@Vw(w z5ANi!hW976kw-PlZduANHhj;wM~2(>WDq~LaO>-s3WLYv6K>cOPsuX3`Xcv8ck#E$ z;_l6>v`y}Ao&`a^0PKDB;Oh>dxwbJj6glw)ujr^ywr+d&05 zRe4VpZf86U-=ymvDDNB)StE(8c>9%W@SGcS?vD28a7*Lqx|_9{+@|W!#o9q}TtXBG zDWgJ`NM8OGh3=-_34C2|{_>q?rbKpft4Nc=Kex~^;E4T5sCiPfrAJz0{D0Oba~r30 zwv_vsy!(I;VVtZtDt;`}9+37K;2G(qEMMtfv{5qeu3+_abO`X5|K)C#I)3)$?s9bt zU)1+{U*FGL(`U48-WNyS3s|_FYMwzp*@+*fRMV3ssnF~3I(O$;#d1#Q{EwWRNr(nI zn0@AktU&O9`?n5f-+litLbo{wbY{Yl@m!#`b;A0@ernweL(F7b``*^_Fy_z|SlxrH`ziMH<_SirrZ-bA z-tSd>NPPE`^h1bfw*fdqX~$}_`#aO4ktSn|Gqt>7&M8yx3ys)h$D+OT)*!gmQ+zkm z^Ycl?uNv6~wXDf?0jP72OcJzhCGbzd4)0@rrP$BB&?vswMK4jSROau`^rk14U2`2| z1+h>|tZ+pqofR8a3pvw(?HB%;h4}qEcun-Q7RH7 zy^)4}6B6gZB^e$5t+DD)?T#vaua#E+g8G4Tt#jTP`_-`glWfI7=Wr-jMJWc$yq2Gq z2X9eDHW7|b^jPtnTzVP2y=6nyg>-`6%c@8}&*8^TL$#kl(UXlu(yjQEUrG%Ya3 zxJ?)?nc^-OYV(a7BJY=|G>3~ln+TSM5&7#(spA`39x$(-V@uf9kkiEcS=x{|+J-lldQgXT13t~LAKuJ6Fpx66kZ?p%9|XKP2C|F)D?LzuoN$|v6!hB zr|tH_lDnv;cB|pg9n()97uo1=4rFp_B+ERdcUCLghe~I}dr5%wW2)PcCBSp0aCiAJa9vdfD~A4MUIG>J?-_C#vZ)IqOj5bW-ft zDmZe@J>djzSbO=aOprT@n$r>=t~0FpXPDSbm)71O@2ibnXYV`0+@v#Cf$=(Ty{mh7(m9BhUKD zz#MqP);tjO`z4zbClQwOZ-`ZwXNZQrbHChCFkpe$(H^*i^n~f@tbAH#ki&=9_YdhqNfhZSO^FlbrA6 zil+;w^Quf=!*vq1cH|oa>u2pX+cZ$eWe%_!PGYv+c-?<_l=OBK}N-_Oz!T66HDMw7*SoU*q$W=xH9Y+tEAG@G3#W0IWQ$6oxi z9~B;O!5U`>L-uKUg&LR;e$ zP1FZ{(M7{@tF6;slW`sLjOFHb6aOB9ujH^#Pvnt7ve+K(OGk;Q90^uM{2j}0ylnMO zrl&44#DglXrBlytCyO`WwJe^T%kAI#{$wACf7wsyXMK?gvt^Zh*~i6N00Hj z-T0CLnD1xg7K`9LeO$OMU~x@i)hUsqoES-i{0*f2N{{ z6kay)W&0Fo)qMR-NkBC*BZC~Q#m`mSZe!*z+srw=tceE0Xh08+HZ(#u_6KvXIaTuv zJFt=PS|qLdEx!3(c4V!f@~FIGG5@WGB$wN+d|W^olZ_|79Kf z*>P(jOl(iAoFd!xl6=e;jC>*Q;3ni*td8E!gV$ zra80B?z2tZpP93*)}cO3-ykmCMpb_T7mf%#F9`jT_!qm#-l`-!!^NLYlmD9W*NceP zDQt8Ld#Kz#%FW6JnQ!cOT;FCn^_6?E$+~JZrtF8>K``!!aGz5Cd#d;yCJG)c-YgJr6Kr;vCN~d*U4nBrePo?H|PB%OwN;LV3L)E5RNT2mJFL1MH4|hm1ozn4bx3|5B`vJ|kKt<`#}PWmI6DqDBFCF^-rb{pBZ>poa6 z6unf8n!uM-3P%pNMCGt#>sp`l>epYJx?OFmUZR?4&@$keY<;-q7Yh5lNR7UB$Y*8T zPM!azDi3Z`Omo)qA)vif-r&@j@fN!5*2HvFO*zu!y{_4(qh>&R)u)5}yoWl(Ny*nq zIr&yLU4?EXN~Z~0gUz+xoKwC+{=QPKnaWc`;gMziulejrd42nv>ePy+_?6AOwQ*}# z)yF(RYc$WylHClEdB1Hffl^0RD6X{8`Yo+gwaVN{s%PtkH!kWd2{`k$S$DRo-R`C{ zPnst+bNj%SybzK;QhZ$3Y8k-o94(8b@;pzi>hoG3F3{w&N{Klk+6n2rW#9@}`Kng8 zJ#*A<@jcrdk*pRLG;cPe!{2D!q)hBrTrs#+))~6hREL);9@NODL}+zBg+q5LGR`!g zSJE#hR3nG8C~T1Fs8&xBcid}9PhnlUtE<~;UtFo#a{ zl##Mub?o_TVeX2$ilH#|zvf}*)dv)+-$^Y`H?aGw)V(60SE+bD-^Cf)Nq&7nKA7u?tZgT&=#wgs$c(8 ze{tkmAuY+Bv>bfJYoFznr=dDjIbx#VH%zDI zx!z|QaY3S@qFfb6XU%kBRF7(6~_JfI9aT;JI0mQ$mM-l83&N zKe`HeBV>QAx!w@r_epih2dMLCG~38FJE+b7Xso}BE+13@PKx{h@$@SBOItj1QK>(R zP8zH2R7Rr}igXA0n4fIdKzZP!CheDyRjN6=i}%oOwoMe@3F>YyMa&k1ZmV;4DoXFO z*f=eC1pbza)^@_fH*$kCnc)|{|G4nrEC1M9$l2TE6%SQg8bgorsWR2Mb?U%pWLAUv z@g{jgHFF7)?RCMjzq&DL@a&J)xozEs^hOy8bR zjp#?dj%=KCoOgMu4qVL&3dKtSn#TJI)?b|PReOvs&hDtNU?eI-R#Zceg@OKGsvW{> zHp!l()t6nOI@6o#`r(ISbq@9%HNZS_Yc|L zAR+0c#>7Clc|rZTHQl(UdB78RS!GU7OjRZb9|#EK6->D;-> z@_lu#8G%)W{PY&Ot(i}3qMr_E!XwGEm*Sa&RP(Ii+I(<*qEc<+rngzDxA^9!VwpGV z^jbczKUd}{-c<7I$x5pTV*dbQ2hk!M{?1i)JfeYB(}QA@^=#t(xFw#TOA}8R&v(3o zoOY}!Kr#G=#v-t_doo{>t=Q!cFaFR_PA2wYi_VfG5}-R#NSN8!B7OR;&3Vi4X($^S z!mD+OhekYOt!%7ktIXv~H?V<5+UF6Wkq)>6g@j(-BV@j9eso3H%o+}^B_5z&B_lU} zsfWMAKcCnQC(d%n3^R>eLu>y_Z0i%PDZ|mpL4L>yjC_R1UkPGB8E_3eU|%1sETg5 z^#lG8&5fz#zq>8p2E&zLP1Xhes|W5f6Srt-uQy|{N3~}=;3^ZvK7ahErRW|)k`;L8 zE^)rgvZI72UmBl3C$>f{hx@{IQ?+$1`!I&?ie%}z^8NAr;aj2UKk@W^ow(^przm7w zNY@fr_)hRHZ}MtK^P*MrE)vb|mfe2jM*o)i?wHq|y#nh3XupuQYgAtzFLdwK_$>mo@2D1g;oQ-zhold*3~U;Z{%jdJ7w?p%oKau0JGYw^|POqILb7-UUNox_ZP=EU98HOT?c_ ztz9!%Z*y&{?zk^X;qQfx2joS4p-Vb_wT2k@aFcfei)sj6LjIPvJeVv^^=isJ@D1bp zK5>65y4;pm>nIkEVV^GR45yfNNwL8SjFt=DS7`q~s-qW)^0Uh2HR;j5#nhIrifsy0 z!N{X37aM;06g{sIM|o;z)^Upm@=POM5v!#(#7kS{t|y_@X1I__Ez(2H_WgwR`Pry_T>dVCc#h!WHPXJXMwmv< zjnzn*_LcYRt5oFqh-QOCJUg5Frm;J}<)0SvLxbd&;Vf^3mcw3FwNJb78vc7o&D)be zPrlq-SaY$7#!&NX&GwXDh;GSm3y(H%znS<`AoX9^pFsK4Le1o5t=qJ)zfsBww{h($ zI@X&M)r%yO_&gFT-U&ge4O)U=5!RUW3?BW{44jLHyy5CQw*IYrbqEt)D5~y=8DTnE zd3@1r*^9e4cny`C&;Wb&@mkuF)O6U6+IXv4EU8-$)yJ9O;K>VL^1UYXaGl01x7D19 zY;m0y@!$(L%D4OB)S2|9BN-6K=GW2{TU)07Cqyr<4@@B$2UPu1!M9HHa|Hh#AZy)@ zlgl!PPOP=495aTE{jODFkEQEn8c+Nb2_d{;{CB!ox2bYAsXC(a z$i=a03_ijWql8~~*jBY{#AA(ktkwQId~2m#wH6#!kW+PJle0QlP44ekN8F%2jam$R z>5T2opf9)q z#l3&7t|ZS%#dF|Hszx;ykG_Ywv-q2D#Pz7gK2(?cuz6Kl&c&GeTDX`5jlJOdecCu$ zbs(G84N%3q31>~4p5KA3_cT+dABv7nwo_v@lFK>z-3#)#sFE=vZcXVlzh*W2J5x>4v{wkUs zwba(G;~57O6UP$Ff=e;Ni_+#k3DmKD^HPMBF{(OC;%%gIea9Wg!gN0#pht@`#Ato( zi|$<6piD@?@_X`GcBJMgO7neR?8W+BWZ7{|(*R*pe#85xU@=Mcxh;;n!ZDHE@s%52 z;Xl^Ny{!1zu3G)F}BjVcVo~r`FTm~_=VlkO)ol!e5|K zsyk03;|g28)M4&0rg+Gsa)|I+%yrgzbc;{jso-fmvqi3R73-8jPf7+#xy0wh?W-zk z6>VSIXkkSDRjbbJhEQvlnNhPm5<^27;tVQ1K25+9IaQy;b zpYvDuq1zv}+<_+hHcxF$^Y=DQ??P6NR$mOlNp4Ui$N#bgNpQ1ym8`A@i`kyf{G8x~bIn;-|yrEHg(8(4py6;KTvF1l97!bj0=OVofOa8KtOO@u1+|f?a zwT>x0ls%(Sy_H(HLW&CnTp&|SHIwhqgEb9ly`>SMoPO>p$GM4YkvV zfij(&y8J8G`WG&-^aYtEtlAG>BS}?&#>kS^>{1{8LAwSt{Z|e#2Q(i${f|t$*p+Ly z$hur++s`N;O6=IT^6f+Trb@YEci4BB99To*r}Hc#7@0QJ=8-oOR2_E^n_zXxQ5Z6t zZM?@v#}kz=+iRn+AIN%L(l(KT#1ZnGNhl1LIclKWIwtw%E{zk%{-r*HTkfr(Y44j` zT*1svoYEH)Oh|q`Q-4tyxN~`+%w;OuZ>x1qz|--v4F+VA2~@e!3(r-dv#FrpVt$;w zQL2yolBfXno@wYIArY*3`>|B(vH1R;mhn5*I7VyqOl)f*FPZ@Dm%_lMIdx{#3KY{6ab;4n+(&oA0Jng9Pu)XBJkW~ao`Xr@KEh{lm&J2T?r^I3znemG2 zWzd47EjG`|pv9_!dE{rAidez7+3Z&aUr>O34Vi16eDyDJ{4MP%M{shvEaE5n8Vj0c znDHG?RFHw8EV!Gnv~RPuFFD##)o&_#da`?Zn0p*GpIEG?tm7j7?xSp|$VSB~s~+L% zO|k+l^1Pfxo{>nEYV(QIGNYy9E?7Hi#{7aKE7aRoU{$@ip@uCHNuLv1q)5r<@8*2^YO88z+MyZ1ieOziZ*=t0lIVz4|pq7 zZsq~O@;jHf|6rwI5J(e_S7J!RYo6{(<1VPWZijF)ac(W72CBBzp+|*U`GW5`k15~T zAwhoLk533uti6J#%4EuLeDOeT(w5Z4K=EhrjOWu8bkt~7*kFlJ+LF)}E}hoQn1idk zVeAKf=?Fa)fLZ6L`8b}xRBOpSyf8}s!VhkqBmEP|xIl4eXL8||#;OcD`KjAIg#Jw} z!H2OSlsUR%um{}N;*K5)%|N^oB+s$J_tI@>G<;JEwf0bHPUODi_jZk`ihQzau8fDC za!th&7XWL9V^Jm<+%O4u%w2k%~d{{h?joKcBf!T0uA3n zChpQCwI*&?H7ACUc?Rm_d(b6Ibzu$sDADLyp}|wq*pW{&Rqk5OcWsdibNC`j<^ZsM zituPY{Co|)Y{{v;npS7Yw}q;}>*UEa^)wX(9M*I-!s-J=cO*``N*=`X<^2`CZ}1K$ z6~XP$VW;fX5;$UqrjEp`zZkoM802X-9Vdm@Tzm$~HmW-cP@~5OmGO`|A#o%3kCQ2W zu)hBkAFuI{)r$U|V81K9?+k%|F?Sm=uT!;7Cu@GGM#YiEoz#PS!~QH@XNUa);pYdQ zwn(UG%|nK2wJqnC9`doPu;n8CQw!%_u~p%uO$RoPgh9(iy{mX8 z6|XMicbf!jZC*A;KK?s1SgHK+5EHfu?`K0#Kf2P0lpC^5Psr>B)d~+%U8>$vh{1O@ynzfC)joab_QvKx z(;>q}lhcglkHyvr{Ngs6>CDw{g|)RTsIMaFH2<%oVuIww^Atv&h4jJjsgf*>R5$Ao zlY!!#P2^(-wRc;%Tr6&{1UrPvHw}TX zA#_|D5?+PME_7X}#`6qyF=<|N1@0eHOWd|17xD0Ip7t5`xA0@9$?gLzB1PHr5DzU@ zoX~-L0fP5e2ww-JhRBmOZ3x-*RI~a7iSMk+FGbOa?OcH@mOEFm&D&*d&G}xH;?PQz zcrQ!eA!{dV-vk$*L#J+pFW{az^!mo;qjECJRXuzsT=bJ*7MMSiN4wzuH*hG2_l}j# z?aYI7l)6#)oXE_B@bo^xtRq?I!k+Dg6A#4G5;d_@z2FQy+pfOe6D|?fXC(HYN;)F* z9;6%{ie2Thw!84!IQfZraJ9WqzNFDV@hA5DB>1AvMnCC{hm;E4%52J?=FP;658DaX4aBOd$PJn<2afa zja3gHOBQ@oyU&54vqZyN7?{Z8E6}SSnALD%Bqh%IoA0tg&w0do*#k8`tAc-PK)Zl{ z4}pJg#GBzTJ664ZAuJy(PH=?!WaiohzZnR9{^OZfg^JFo7bLUJ#rjRMS)Xz1T*1N} zy1AjooUAm_q;@2$J;X=z$*>6Z#1Ke0C(d_<^3MGJ4P2T-*G$CVU|Dhv9~L1S|CSpL zm#J&ezaKznSP+S&tHE0#TAu;!cj}-F7=J)BSs&u=;6;fmyN;R;#=*yEYbBnIlP^4g z-{WK*o3K=iw$BB_1FTYqjQ%Uy?;vd-sTIRWU9S4K3j6(Fw&hqFj`N1&mCHgKJf zy}igQo#hjPG2MVJoD3uq+?>JFm2aqo+L`K%eQK)V*L*?B3@;&6AXOC0qx;@5R3Cgj&eyk?#%a^gWR4saeADTDF+~0+$csazpg^ z4zo)5XdfYY5f59Vuq(!$ePn-&u=9MurvNfPa0e~&&x{qXBJ*n1#a)TdY_(ehj=L{z zt-^@;7@p0atd#AKLkTE-onX)!d3H7`w+Yp5aOxI({07OlB?%$?+Mu46i}~wBhgNWN zo>we?fCb$#*h(1jk?*Zi$Oc2Nhr;h@Xw`vUSwkiy^X3^~T*zb0 zNpd^&;!s%gUfuW=oif=4F&fV**_jypeUhKlhi`MBT?+60RPikg#W%9Ooi-05}H^D z*=^q zUKNG&Tg#UB!+owa+Z(3UiSw=Dd|%dX20YdiwFHQ-(U6W{ca6EaLz_J8UXJ@D63%6O zRwGojLZ3UbH5Q;Vo4k38#DdH}fyt-&Ut{nerYSFlfwwe{hU8Y3XdsHb@Zdi9raK6Mphsl?1LyI(Fpe=OmNiGDz^;mW-2@+pw4wityNlleK zS-W1-?-F*CgYI-Z=1S}*p}wi?;S>zFlAp4`;Ayg38}Vs(GI|aOeiAYl`bMw;cEq$u z<(mizeKfmfLO~_V`hm@xN$q1SSwVHQv0c9GMG+qzF3+}x-Sy;$7U;Xe=P-CaUrg8l zp1GRjkFa^1=B6R6*O77sm{5uP{z|4u@?{J<{FGm+N1Z>yMHTw_(bXaF^8mIpgxVTj zc?%Mo)s3TIayw1fZp^>TT$}MvGoPM{FAV75QgksAI=P^*Os?<*>$CKFJ=%RIhmAqo zg(rl=%Ilg-mY`ZIz6t`rr<#tX`1AnZYj(547X(~ z(ja9x&W(f>^<;J*aEg`%2I9g@!FC95o+n$o13N6lm(Q{KFb{u@tX?cNhw*-ztG!6c zca6ObnWKmKx>Ut$p`tOK^i@!i|a+WeGs2^W8>Dr z7kBZQ1NlBoV|xs`FJV2lK&PWTG6PnPB|*)YeL;3K2)i5+VpSaVg!?;i`zF|*f>9w@ z#?WDa_}c?!_-apW`P1{9Q=PP}upN^w!195_!*+ z=w2gyn2Q5|)+b>}dtCn$FV(We+hH+k$_Po+)2!|ZJ;Hd`SKwrW>@Ny~g=6{HW1O&} z4Hit7&6$cu6KSa(ei31xBmQ@UTU>$R+tr2Muq|G6{s52dm|hg9!?CIu9?p?QIk5X) z*+EZC{UIA2gDa2H-Xn0vND}oKr+4JVk+|&5>9!IaGg44r#V0=Ga~jW02K#Hc-9U8SgVB_YXu&_dSipSv)sahf?BPE# zq&<<;?N!s@kFzYWn`DHdR0rSa3$G4f_BlKtpyYLkn}8z*vg#kwRxaMT2?cg+h$q?f zh1c$b)jrg>H@VQ8XePsfP+>ZN&Ndoe%-0n#5K5SG}X;UKZ}8S0H< z1D2BM?kvWPh)dwBGwE9cH~+zX52}cRrD__Oh4PmqpfC5@L@cuSr!Q=BSIj%ka^9dR zWu{;7$prp$G?4-oX~ksw2`HUNvPTN}d!X}HIx8A&Hqi4+@b)Kg-^W{dL1ckM%V#P> zT&fXml3{*7_WL3k&DbI*^5iD$A4d+1CBq7Vb`>_5Lgh7DJ(M^9rX%}t=Y6>58~51D zP6gt?8!V9H8_L=i!H@OqdNx^G$1{D%oLyAvr2jlbhwaCW{e{cc7`~An0em3=pVPQU zXM9)5bG6vC8dP*->b=C|pxDcpcopNLdN@}F7A26`f!>}D6ZQ(cBkEfUT^#tQ&)EOAwnauQ|#&0psR5+&Ql^=!c1F*jj zY5jqNA3RfYw&!V?40>HuskW`mtcU>U!}poNpQ`mjz{Q1(EHF15}a z|0dA6didWoQsjVTvAjAjC~lz(c`5xZDLcWe69q;6H8I zMpK;gobHjP2NPk!aqN49W&A-$Jtpf9S|2qRCP`l|o0koC+xgVJFkXk|C?WBbFen>g zvyhp9m#JWL2kpC)IUbn*37a0E{wQ(kPaJ<)^bLTdY_T>3vd{AG6Ts>;OnC_G5V4#L za}=@xtsy{HQ2C(w6l#A1{a$hJNSxN4S32R4Da;`iSDh8hI>Dkimar78RH$nVsZCT` zS6h40@)@vUxM1%JS14&~=fSu@p0yC} zdl9oNxLQdTh|nRO+GWC%kF;e5EbB>J{_&t&u*erDk7FI&u>Dw;b^>2?W@C4wmXrhw zfr7iZ{uJ4C1mZ7&`E}veKDZx6k2PZ0ZIadwwwdGiD(>Qr*HXFNI#w_eeN^Jz*3k5p zeawVw4*x5Q8RZg7bE0S@OkJpSX=9y_nA(+;E03w%28AhGDxPCZP}3 z?_ksa!{ZatES2P&W3U8otfb%WgU??&a0Y1k(Tu)u=L_6Df@SV_dN;oi%11SE{k`JF zHRy1Vy*dNZ&LZmuqxz7dJg~n+E^CvMw2zT!IfaC;e_ zG=u5dkq=23aQ!Cywi529eYF_~)x z;p1RmcYN{?uTI61qhkMHT-}Q8wSaMhSVB8+(MG?`V6+p~+=XYY=;c%J;*1b!0$W1q zQi^)nG}8;+MqmdQd^wxH9)blsnBE?A=*s>E!M%ITw>_CK8Dpa0TONI!2^*z$)jp`X zNu_`Bl$S*CN7)CcUV`3T`0+42{#Xosh$n_Jul5kqc%r(gK{E$nas;fBQCqyy)| zH)A0&646nxN1g;w^e>Z&u1vE7)=c9T6G2L&wFJVf5!B%g zG(^&xVCeFmZtMZ!N)j51e{*rpU0iiX9GZz=OT`@$(>Ps>NyXf5I8?&aS7Y%V$UQ>b zt)S_jpw$X=h*13w?HO5V23#+G(&=7hcdAtYomK5U=f_ z;x=$_q(^qa`PM?jBuIQu=BG(r43ek~2P)VjUzpiTjIYKX1){+iTL~^O2FZ5 zW5K}yvqr${Z)D38d~jUY&=+Df^!6}tT}02P1KEK(oA6dF9$Jf|m$G~Jao1T<`!TNa zVSZz9;#}ybhue110|%ht0eQCrT-*h>C7?;6#w~bY3JFMs>U?J822+Q#J}H<`ASO!B zv0+blqe&&75GCD+@k|va-=&$|;DbUqH2}V}6^4BPr_=C-!pP1LqJo;OF|`IDtH#4wYKu#3hJ(N=rjw<4B>v7B&zXWDe)eVbTs56e}^r&~ZJ> zx`1z!M2WO~$Aq_i3Mq0v_W)!z!X`s#HWNfIusur)f?!-bs&s>ZWawYRGZLV0Fs@s| z+GZg(iJ!_)d6`)p#;+r>MTM{L!t-?Kb)ICbgFQ2aTZ6&<4xM!zb)0C46D%#|oqyr+ zd+gfmh zVQV-3=;b7;6i)oQY+qvI0?0sJpt+C)MYe2qs3qKK!?=NG|NwC%*y7vS5X`$B`(CaL0 z@`wHwG;<5wJ_&Oh(ajdc0F3Cw&+fzdG2-b={P~dmGLT5TEcr1`_a|v%!TJZ0*MXIW zEV~BDGikaRB&3l4>T%(32^9-{?~9gqG2jc6x^+%>+2y5p@+$BA2#c!l%zeyTM+1-I z;uPVR22)FE{$d!@ojyAZef;r`3=}E}XpC20h&wyOx}Bn{0vat@-2thlfko@lq?u?P z0ULKRF%)Lb5R#o>XQ>R{@VsbCgS;Tg3z>@XslovW|VoB?9TU*?88S8@} zYaj;h6{`F(_N36M2YhcQcw7hbG#Kdxt(!2k5%X5?8Hq4INmD)>-k%X8f8bj+^PG(A z0hsp!`!w+026I!X^+>pCDV$sndB4e|t+#MSGfC)fA$ylzrt=m=niWzpCxF*!K*d3RpXKhu&qP4b^N6>9N#IXe8V2k z#JBahv^QJei)aL{=Rl$$_B#W`Bk8OaaC@at=M1wR(|2hwVL;Q_7F1$*7V{kw$)o;cESHZC}ES@J=>x0il;uC@cQ^9c%{BmZe z{-ENTs9T4l^28PoOt0cI&0*X*)PDu@av)_iWaSAt>!Iow6?Q|)CGxWZnkL}HJXFij z?HQK;5&NIV4vC`s9n`8|(QEL)Wjtt!ch=LZ37Dy-cXF`vTVePJ(3?p8Gf{%iU-f{9 zZTyN4^js>gZ^9Zac4aFbTglu5aX}w^-~hfFT>lX5mI%EjKpRWp-xYW}m=@TO>`U-D z9eURD#}<%U#iVLmw6o~F5x=ix?Z%_Sa&Df;W9~>^19bABmrAj)g}(QKx&4It5nyFV z!$Tm|5atxY$v+~k#nJ?JC>mQ(WKNjbhYxy*Km9O111%4c9<_MINf;Ur-m9siE6Mml ze5^=dsK#37SV>mj2jc>k_&xd-YTBW~5${E94B!pXnFx-oF& z679AO%BPaf1yH?@&*=m0?y(_<@%d;L5P-A(ivO(qg2a}GWbXIR&Oz%yBkXJOS z6%nS=sRK#T4_FWe6_a4=94J}Ar!EAaMonfs&RQeIx;UKY-#+o-yWn0bK6?Wn-r_cE zVUGcjVZwoh&;-=y4w!_&h*U7rFpnG9V8?g3p~pn_WGk*gHsu~}+RTNW=;cAAZ!r2V z{rU?QkEV=mL^i zSawQ+-ax?_NnZl7{`BQ8*f*D69SYZaky8_4c^n_V567L{CiC;Qeg5Ka?S$QpNCuVaLAi&Bk+C<`{;pHUhHvK z{F=toK4Kq&3p~L;AL_4S#7%+Q!k7p2?F2~eLf7npOWk4JYJ3dfGa4_yV1)_z?3VaC z5`&YOv^O2?hLZzBDtRzf$ zD;kf%jlRq?5G66hqnA9^l^pqodv}qOSD>(#sBgoaCMsq_=}+?cD*Swj%dN>TmH4I} zr+#4vpJF_)$CkK$2)Dn5*%L8y4}QEzwuWJmo6xHt=s%=SS3{N~J*p&`nV6T4Eg$ea z!!e;OEE#tsi$8bc&1=k~iVp`#@r->|5}p1qXeyaw0pGlZF{xm-nl?m1eJ}FgC^CBm zUl0myUbBiG@Yt4xWT3{H8DGWFI38M$5urFhigsM1bER(YZQ5HOB)**IO$aw2CX$#V z2QI$DbQ5gf7U*$ND*Op7PL*zNKG6W9n zqSoVKtw>@Y;3rE`^AB%##JC!Kw1id6#+en&x{0R_=BND7^(3nL!0)d}M!{8cTHXuX zdkE{pVCp2=?=g%@f*bZAM59(D>I_5aMxawAK9j?MA*@t`F$%8r0`;rr1amsYQa4~MtF+SI9sV2(Inkh7 zf&XaGT~7y9uE+|DAC)`5*Lq@2J-t-)pv?r+O&8_Ki3U*jf`UvBiNl83zK8pD%n5bg!lK9sGp3}gG4dOP3@JJ#r@&zLw zbQuF+O8IOU?@9}QL(~H@C=q&eCJzl^v=VfY*X={P2Nlbi*B;cn$|T>=K+3g?ux2`x zJprvS0uo=)ntnunErv5y>!<<@N7pT+(CZ{xf%ldyW!cZxNtwm)0`gR9lr6Bx9qq9 z4t>gvyTR_Mcmo2-fjo<^#t!_vznCl^cv+7(DYZF*-e2rI~E1JLn?_7!+p z$#-=?7d4~L`GlF=vyjjB#VU?%qVU}^=oAOrZ$aP=8fHb--zOUqh-D1uSd*8Xuv$v_ z+Tp|5$og=hFD^LAUJpY1h5TSWpQ*wFZ?NlH!f(MXe`07zjCE-2H5mSc*qedHMzR;6 z!y^Fh-hjy#;Hq?H`kl{x$E!Ny>fg{pV1qX~|5552k`p-~Zl}c|5dVru zzg1Nj7#qNz(cJO_cl^e?kK{a>ul>M>zvjk=v3WT@=?qcguL>w>@UrDhGd_F;TF{&$nT-N*S(eqssVUxHCfu>VHbUkdpj zA*lF&yTHvTvTiBS=#i5qq@Xuxy23l|#Jzg>Cxkm>@}-mcxS9Ot2EL>Rj_ida=fm{2 zsQN+g{sPM|;=YQw-Y14HNz5(iPyv-g;lv3rYsGIF0oIAdU$|py-Z6(8C-9(a{M1x9 zG7Ri`g4<-+wU~Z2B6~+tvkqizA+eZ2GN0n36VP;nziZ|hgK^L!zPo_UcjNMNJn$!f zJ_F0n^E5S?tOHW@^{X!FTSS~U5W{Q4b_TII1^aJ8{Yw0BA2wBsl@XY+lLfe-$z7J5 z!8feN`5EYb71x-+Q*RQn8T{W%SU@rwXy_P%b&xiRtT4jt?KmwNr6TQ(0bCZ$l}0=$ zjxT%6Bci$UZV(orc{%x)1*P*PG8?h@E=68R%q(L57NXxld_8m;$<1WYJD4qT!~Eau z!b@JikGFQlVf`>x4yS4`w*hNC=&5|zHjMt(A?Yv4WH&N-90Xdx=BN1hC`SI{IbAX8 z2@@{yJx)A1ocqkgd+9u37BN=i+BL*69bTo7#dhQxp}EztZZDbEo;X~_xP6fLn+@KC zf1G$oHD7MQ|Cpk67#~)KnR+;+l1IOr}u z+>Qn#ct=Bg9LxG8aCtbt>A@v!-_LGn;!pOOg6~RVmjpV?s4^AQ1L>iGd~@*NWEi8;7I++wHUH@Fd0<@3wFWh zz7U^}lP2Pcop>^W9e>3)jN(3$MavThT;y~1!o?&U`xUa5!K_Bo*9XqNpernidMdf& zNxZ+|hzeNe{J&GQl|5fH6HRtAvoZLf9sgi}#nP1G2yESpth|N6KJ-s-@_j$~u!r0Z zB?c~J#SWOb4IBkG1s`{jUQ# zP)6==B?e7UG=Y3^K&j8$?+H)ngi-VO6bm%m%!=x{-3C722`?n!m}wYYM;gDwr!nOE zdD8PKsoz0-W{_qBQtS)UKhs~Iq~3ZKHQb(_XS}M_6vsZzc6?K z-4z6JZ|SPVqAL9S2i{_+kpvAZ>vxZ<@TtbaM*FAa+7V8UZwBH&D2Vr&D& zOG(%UQW!?gjUgW$XxwUY;y>tn3~cS7-DRlU&dThupM-~twyzSMw6GhX|Ix6;8Q4v@bdkJ`}m0}yXc2mXVtgdUqj-hG36ZZPZ+3^j%2 z6l)Kodn7yE0h=nB+abQGh;RMH4-bXWU0{z7oS6w$ls?Dw%ozZS0PX5WiZX!ZD+D@YLc0fTLnP^Fl?I%-{VD4|| z7Yg%k;fGZ)`X+OWM~gzX^$i~%%D+D48x~=kJ-A^QRODgragtUKBqP-y(%T;8o{)Wpw|R&!I`RS6@c$?}(}0-1KMtRB?!7b9)J*%zFQKv%vPYysmMmqD zY#~b_5=onVO=ZiPNOs8N~wq4J*0>b`i=khr|o6_k;B|z@-fQXH6aRX{ZBT^PHaA zhZkg1zrT1^H>zJvF6}@a3-zKUdfo-{`lQ4L=pPPld7*aa!4Fedc>y>e(EkcxC=Pr^ zPwDR?b)>y7>D^2sjOamE8iMH$eScp9*UzNi9GDx6zyyf8DPgbv@R2QCRY>bS!Tz}a z3n-(?@xbB0Z3K4kqLb4}nkjA0plWB@$r+l@0ip~>Ed}k}P~t@}^9Xc1512*}^cu)b zX!KFwhw-0bbo41wO&LNk>DQir@B>Kq^ep^FVY(Te9f`nJFI30}C z=#Qz;dL2kI20jBp>lonu9k0%(BNFh0C_0}Z?YmN87EOw#f4o8EEfBC5IL!uK_8`qi zaOM@XP659=LRX?sgwjz(v{0^Ji_jMvNRu-?(}YTY6g9{pp&9h*Wo+D4KTsm~j#GISiRei;8^KH2 z)J|WP^aZ}TaLWl0R0v%bfLBA|aR@F<1Sg_^rXJIC^x0UlYYR1+Lv*ufs4?AO0#2mS zO)`+EgmP=JZ9FPW21ak;s)Jx;09;i9q!IKA3wk(FvkF=`oHQlS#25JHRr;l+4ked$H=MWnkaEl|Mm#Oyz;u=rmcM~>= zPRpU%cl7Ua(DH=t{s|?+fB}nst_Qb|!-6kh=vbgXT@Q(*dm-2)p^w{vVb}0375yzE zA6C-|?P%c`8k`K2O*F*-u4cjH^Dt4Q`df3b6`0fm?%W2pf29q<;LR*@;x8?lLq?=f zgM}pY5WQ|f#UFIb8~XbpUETu@ETSI1$Z`qTyBS`r2KEp1QGD?C3-z`JcF{E21!RsO zHVjDOv0^UWVnT;?r!6Y_A(iI;0W&Q?YBdP)03-Lq@#DaVF>qZB_!I|R4gr1;J>L&V z&ypQmsh0`0)z9Vbk&CzJ-o;dNg2sCQ!==>aHw>}|m&2j96tq&kMiUsG0zu0`hYZS( z06{!yKN$S2$LHdx@R4M7p=m4WWBrPMH}EN?843>Ug&v8(^cXPv2p(Ob zm!5(h7fIjS^hXr++DzYiTSi-7dR0~6FMV(PWadi zy&@b~%>?ajVV`!i_ap>O^?adaQeW6ZnURUL>h9I#$dW;3hkDm z*Cf-rdboQXxK<6=a!_gtTrhB8Y4>5kv_Jjl0#q(!*HpTrH$B^r9y?0o(@5qt*q5Uj z+0d?@ju{Tk{J=m@n4@>LP601I0Fxi|{URu+aathwa*@1!NxRgMzoV$tUaE7a5hfsL z03G}r44p$CeSzDjf{rTSn+k3wg16fs*1xj*fm03ShYsBNP8flVzlE)$;AR9BvozK2 z{MQlUdsAR>l}4=udKpFkHi$jbR7gWtLi0yRv^4AGCr{pQc(jTR*e&n9fgIf? z8Mj5;w~d`>NjJRK&RU@hdDL9WYeRpw)%M3bM~LhuG+JOH<0bFDgMd!p)fGd_Dv9M; z1>PhL_ctuElrB*4b4>-Xy7k3sVaFZKpfPYvc+1a6`1GR|*B;`4B)+r)_kC$_GmCS+ zYE+YjMw-gY2a^oSMf~D@zH$0@t}qx58H1lSXog$qZrp8I{apLsDqYw*IxHKxXGuE5 z;K+eA+Ku^u;qDxHWUi#cdBsLM*|$Pw?it|K2Y=qGzW|D#mZGKB+D%(vyT+#PuW`nzLBb?RqgRe zAEr{!^nDI3rqoYd$NzrPd?Fh^4&fVHz)VwFS~RyegIzjN)c*(;2Y?GkrphEH{f6ug zLzi3PoNc<1|25ZA^_|7)C6T0Td-IY^;oEh7YB23|iyl8sfBjRuTO%EE-85&2l z@5OjdmaHiNMl1OQX_n-|~oV;8wgpPrDE$Y9cG%W_^iW~#Z18{0ooG4&+!cE{c$b%w{9 z><+8P9%y;?NGzUHv+lb3aXWQqQyhC&4E4t&b*2*+8_X|Iy6u3;<`zGbIfv)QbrN>) zKWMw3CS>yeb^zJXnvoNAJ}wRWzfotQu4}UBX@R|ubHloz?U$i+k+L9xtF5$hS|N-3 zs+dUOr4ejzeeP7I9(Pjn>x<@g0=amqdTl_fnAGSsj;|QP$Bf1WNrvCMOEPe?&=X_1o@fHOK3xufU`3HT8TAqF(5t z*oEzW6r$c1(n5o^^OR>x(U@}Si5BAhPVn!nYl;>d`%;}(Q(c{QLzb%gg%BiUo7ha)M@WXSfOQ9NJ6c2UbJg26*dk5XRWRPEPHJXm3FJSpzl zoUPNHi*BkprVDzdshA-0-qJ=LdtkcsYaETumi>K)YkMk59?W@Qy!$*Sl{1OK^wZh4 zcVYaFs#d29bpKTKnP=#{k=pq(JXNU4Pow&eU&pZb-3%Z2N){ebzI2xC8p08=8>Gh-DY7z@HUH4Igu0rpnWW*yB zl1AJfaI0U7;G^k^GUml-lL7P4(Lsh$$KVrzym7>#`_#Yv@FF+e?CI#HE)okqZ8V)XyN)}V4pf>W-rIzxy zS1<$K)NR@!jNYo+iEu!Fb*U6AIm3=AlpI}ablpetFUTq)+$a6Pu)mi?KY^E@{3@bA9Di^&EZB!*enkZANNM zUbfCRhyG6Wp*sXSi^e4b#W&k^hnjHXN^VaFcq!N1b&lltBjdJ3psP~((i_;tqqA4} zyv1mq6b>kFJ#Q=|?XS+?a*g~PmnUCy)c7@O}U6?8F$(#n9 z>Mj4Yg0-4r?CBv%TB8_pkLx!EoxMlXDs>|p@Y(CymN2-Twz+x3Q+FDRZi?+vn?s-R zF`vQ2xA1h6eBJ|Qnw?x7NxY~*ug=)=tD@Z+v@Ai=XFnL-lh5ue1n+O^HB9%fqGf0x z=Hhk7++dFjf~SJ29-yahfyUbK%n){Uo2hcM>{*n-hBrv_2pprtOD^tu1)g-_qr8|o z4GlYQiMe-GG1vJp6JGHKH0of&S9qbb?B)qD_m8xFfta<(r2ICzFx)6J0w$%hh0!#u zUY+(@XL`G-#g>2BMN_v8Yz=Qakd9Xx^GvIF^7sEs8CR!UOdl(E&ocgG$Zc(w`F4h0 z4e0j;ST_nU9K>|k*Fw9{FQ5MT_15k1Zz-BfJ-2C+hm+4a()%~qt?OiH40V~Q-0&WF z{4(~Qi}iN8zQGULUrN5wQJeFk1xx?UPM z#`HlTC)X$@xS=bz73ZgLrE!2C5BopV$*j@)#%fD<-9j7Hl3C>A$%gB_#EKR=>l)+I zL)Iq>JvBG{57LKKR+AS=zU)<=5g>bsYaUGpNt#Mew1RW$$pfjqeS@8YU^k?>XR~1U zj_>^sce*E?x|q4q&b-SJDJ2T;r*Od+Q^`3=cOy97mayGu+F54X#D<3^V!vCp*>E(*o& zZrk2Qs%@(K80ace8)s&LM#E-1Bf9+n@V?Hr`J>;y#4pl3+(mlhh`CE7JO74J*TJyo zK4g-Puco(Ye(A1`Z4LT{>(u{ZQ#9vJwOp8sC%I^)GU1trVzI9zaHmO}2~+pSbWk|D zJm1v)G^5@w8Mp|VX+*s^-h6gVqNBE*Wn;G^V8e!nOCL#ePu-*|Xx}$*;2_LuHcZ*f zj`?aHJXs<+qUhuae+07r%va;hZdSP=tl5aqgCSu^H)Z+4*APxRp>dl zOuw@V8^8vxF`WJ%ndWGb^^$!Vrg)r<*6d()gVCA%X17Q_Cal5Jg48B9jGRY}Ubozm zz&_JNuVpBv80n|bR*@#+TFLh|(?wFw<(Q#%Fq9>bu7Gz5!EiE7icr7qOltD#79G+) za%y^ancvk8Ti*oAyHfcDCbdrbpd0D=+j#9h^ipQh>n~bWW{@!k4cJ7wzbAo~tvk!a zq2rrq0{J_+rPvl8AFNgYn6Z-Ar+Tc%>b={nJlm|IMVexy{PGcv4PmdZ5nl&0M{fc1 zTjJaZApP9%b+69Lu3ny}HB3{h#}hYuP(B?7_m(ub14bos_B;LCsysFac1kkNJ^E1t zNY>{Xxj4gMHb8Z$@Yn$`^rN=QiUi42ht%4W(#9xO$jH~7=)r5MZdmwHrGri|;yEfO zs4mOcNheLLmS^OH^i-pn{ZLj1>0ljzGoWNI3`lM*wH1j_6Esir_jrqTK6$=Gvw09v zeubq2K*0%ioI-4ivochq2T0@hE(!Up#RhwAH2!-o3xPFJ?jWNTjSpD{AXQnW&k)F)>H%zL& zezMvBI&Cy)>U~M1;RIr zpqF{xU~Z(`;zK`V7Hn$!78Y)mI4{6B2LJ0v+F#O67z6z_S05Ry9hl$HZ8EvoOC29f z|BT@hGPpT&rImBR5~b<=@9gz`<_ssv@si&OM?SsaA0xOtrRl+6zEaUV?F{u>Tl@GN z&s(g4wtiC15mK1yznD@>tSxSRN`oA+;tPW1Kuk&pOVDZWYxHAUS-p8XMyYYh)-G`FI@QakqPD2vgqgVi0H0ezLv1C`IHnsi(gnk5 zTE%h%M&&7bKh#g=4JP=2vGbwXRqC43+Avb+6j^Tr8W{UbL-`FC_K%-Ad_~inn>kqJWu`IJY^u3A=-Sx{I)tLvHl!wiq(@5%c zb^ACtqg*}q6h7|{HeH}Gc1D+nvKvFpJDE#&Z8q$u0x$a+48Mv5CvvoiuDPd~RR}sI zs`Oy1&d2H&gbQB1+FmA+oHMku6v75BKA8%mklY~?jSRN=uMT}a2| zw3ZFgeRpjd0LA4|t(O15)Gv)Ly~UoVgm5+W=+9iMrhd!K(tV}zf-xG!cpOkHs6_oe z*fLX4>WL!~VC96?gBtqEpt^ahHrlXh@Kkc`u(t1MI^cjj+gm#Fwd}@6a;c-)&?v?> z$#hE=6XY$;WPtfB{_z1p99|#)NqsJ(DfB9SQL0iL;HUlIb$95wSh!b(r5@%7&842d ztWt+cYu6gjxr=;Q*|u!hZm?)P0=(<2IT*ln`u#h#mll1m+jN9{Us&~67r2e zyB?{Zwa~ebtG9j9UHn{o_8j&KP>t}@=_iHgVB#Dq&+a!fzu zqv^IP6GTT?g&Fa9dE3un)rQm!aq8t zzh)96%}sktl8N+GDLlCuew4DLrC~cOD*D!>-W38J>cZ@?%D=7hBB<$2q6y8gk}mp5 zN9011tTC|A^yk#u=jGjiC?^w<5=%v3|Fzcqsf*!DZh&JpT*8d@F+etz7B4q8a^uet>h|D*biwv7=xQbj`2d&vH2>ONy29T4nK3)O*~r)%)Y!02 z80UH7xbZabY4arm@#u=bbv@dy4{dy?6x`c{76f*iNKIy-wL9e%n`r18%gv)Cb=xgv zj?(x$vNjAWuZdF^P%HO_2P=ejF{%S1xl&UrH^p%kt@HE|&1P}HN}7{r)Oe4(^UrF1 zt8DI2rT-$};VUnafF&5#de9yV`PBBbuC{u$(E9aB{mU+V`V{r}euT6m&OK>!2kAz8 zz?K?6KZ0r}+H9_p=HFF>%|lryq%G0R!`!wnnF7ddd{%=`ZdT21C6+mL>F#2}^|ro_ z1XiQEap=bn%dHa(_smt^9t$tXjY|U1>mvr|AHnwFz{4AO6*nC8(k9;f7tyAkzpHuf zBFukmbMGdePGFN+gd$|J7J}m~pA&A*e)FlDSQ|mHek7Z!CN90nn?23rcM5oIYkfzQ zUj47bTYghUA~m&j;M_S*+77sw3IP`6 z&Hb8?zio#mG!7USzOJ>uIXzh=abu<2I8OH`~sxZ^Gbxevl;5g2lNV;_8{`^N;oNTPp zWR3sK-t0tQ98LVanC|b9w&VW@mHR$n%2a&!7}2APY9-W;Kh`|%Jjs1iKlHt>zN&3i zl-Ttc9vL7Ov@>~Y!6kZIB-AmzKbciGqoKKq%(ckf6*yXuB~$o$55V7HbxUsW(6464 zD52i4;b5gWaI(J3$=L|fN2l49PYl-vpj0!<$%#__q)C|z$DL=px6!NfTjQK`GxxUa z#Mt0--MT6KqA?9=6?~7aI{zwsVjVlDKS&5L-S(J$n`?IO4a#3=7QKS0ye#4N65n6o z(P+3kKp(fo=hpmtk*HNZsCQT=cI~O{dj?{6xbqsiJVSE(76_kVp&cOe=wNxRS$bui zeEwmyI1&m;^X<-xiMJ%zBmmj18l@Xh@EJfL2K7Mek4 zwb96P>~o!ke1~knIAe-gS9hth8_NBpS3Zk9m$YOI#*PYANs_SjLS2ie&TVR2{Vl;P z8#O)#w(U$8*h{8`7&n&z)0t-7U6}8&ig7w*s^47wp{;vcdPVUTr)vwYYahEbEf`0} zFK_iaL0$!rPxE2nb&g#RL9^-VxzYqziz)6hml1}rBV*$aK2vi0h32d=aU9Xs)e^AF z{+Xp{+Wl%kSNPR4c-d&uy-L=c&nziXKzDTDf{B+1S6G?ntJ&>$rAsFOhi&xgMQZ$8 z_4$YH05KpGhOj^6`3>=ASmaG>Sc)YED-*sXyE1;Rv()kp2U-U17)AqmFbR`yB$F_PL7JOW#tADbR zOnF5JoDyI>=%HTeVrI33^$s&~`i`6vL6$!WpVyeCQj^#=yXn+M-cVJ?caCbofui?o zp~;_gayDw5E6Lhu+MF-R2~>VbW=2LSJ^pa|o`_pM{d!Zlu?rT2)&1V3yML-KzK@=* z-mv(nAfJH0Z-Nv{24o_eZU%c~bkrrQ&;aST>n4&;Ov)4)xeW5mG$p6B>8#pgCq5h2 zIQ_lQr$uEO$-C{>%va%)-mvpSaQ{D(hL=0>Kaa+eKjNT;TEU$@$^;=>X~;rDX%t%P zY0)&aZ~w-z_JYBr`ffvYHxioBabD<&e(go? z1}VP0<)Xb6{BZQp$~-AYa(0yhGhEkf6m5<6n77`q6gC#92FpZgf@)l*=w#kxn0^3+M50Hhpt#+a>CzP-rAKELvk0e|6s_zXVSqExNE7cEP zsgAGHog+N;r#4R{;aeEVSb6_2D1U5Pypj7w%^bQ&LvG5T2Qz*a85ah=v^GxdE=+$` zKeJl6T(1f_E{+IQk7yugLa}BiS@K3UV;M4)TQz88S+h)*EM^XMGq}ss_Ze^#Pp)ke zmd>QPzw1|-XrC6;y|b@(OKuXRJQe;E4Ii%}vX<5tj!I##pd-)a*{j+R5--1Q=mp=29fvRbq0dnawpf za@hs{K9=3V&|@G~cB8lb+7iwSa80f4dbL$~Q~YY7#}LhL4)6KH1cjl_k7etU;RI26 zr5eUidn%bvuR!H5+MsQY3gyjWS`ALpo*t^8OyTp^Cjb88z!gHuc{=Z? zG++>0{KCZ9N$Pi7(HMtTo>9&p&f+-cn0`N1iudY6xgQ(+duZhe^}oEem$tXO8zpv9 z2^sUKiyzyl1xaHJ9^D5kzgx}QXz(2;ZYez<5BL4$le%M1L$%*x(srkDhbIm$ zulqTh?`o`h97{V~!n&U{*+LmqC6#wm=9M$l(6}XmS<*pnRR{0Xva8HcU!wUjUECjE zGi-jFTXoZkhvEwF*6YDy^e=oRgdSVY)JGD>8RlP|q|#&NIGdX~TJbprtz0kZcLxrA zr>T*PWv;D9b4Xw3x-QxL;h9ahtMTZ#ww$>*)l-s}%karx7m`XcT)lcEujt*%KJ}vI;)ATV&xNog~H&R?~2>L2%{4&FB^{61pB0(wj zx@KHciA>%oo*iOk3HWV;m}H>4T12BG8e5x0zF)1pRX6r+qf3~Gny~q3q!*Bl9xg>% zgZE#+e`#hp9un^cB@JQclyI#>VBA@C0~+p%K1zOz_ST-Q@G|;FZvAeEI_$*Vu7g%- znhx*2xlKwE>-&r>pfsIoR0*$pt3jc2AG zl~=o<+$6B`5RI!<=UfxdRjQW#()C77SEIz(S#91aB&i!1(1|%Vj)Mx6_SMMeCVM%+ zEcCTBC(iK8ZD##Ea0KIujhY_!g*}(mCohv8hg55}>%v|&4Lr<`yC$^WBDd~HYHXRC zfeHy?LsFHyf1`2jjlCW)^((k49sPKej$I`|H~I zYG3ps_l6Q_FFMg!ex?IEuGk3WqNN&TM>FPTs~mlUqepX_m%+k9e(gLQ?9@`lJA_Gy9&Y0+Qt8!EaN<4?Y^JPfCv9d;5}&@+_)W)q>X{}UxsNwI;=$f@Hf$1s?1ZxVEVK5ztl}^<-4DA4 z(czY@4e8oV7h7+w7oASE?kmR|uc`Az{4JaOF423EB>Tdc{m9sKy>#?C!&T|bmKz4k zdNa=pY5XZNVWBW?Dz$mo(&{5F*xulJS9f?%+s853*a|;$1tSxrZ(Z27{nG8$Aab3t z$r?8Gw$aL8X!8wDAN$M>6CR%y_SLq=F5pMIx7}KU^(Vc3o5aB*1%EvnPS19512n;K zaEe4<$TJusaVwE4hcW3bY~xkXD*PYjv-q{{*J3)dMHN=7otM$F-x#aTX{weHJ>280 zE3){^{qcvv)<(7sXx>5PYIC+RTJ|sx%(}@ap3-j3ntL42k5^Zp5>=M18HYrjx8}nD zq8TDo#1oJ0-1{fY@k~W?fON@A19dWbG|iyoHOkw;lU9 zB~7{nDP-!@=>9I5b=wQ-eDjYS?aK2iI1Ar4VYQ+g^+P9^$0IKxQtv z(~iBjKsMWf>Bq}|RWO!rva|%G=)@jzMC@!KG7 zj*n5NXz=JRy!Ci`>EQoywSF$FUVtTRkzdMU{|+^X`G69uB@5pIiwdj?bT$#m|DWvQyrbVAu=>W$* z5;lCta~EosMvA-JYaI6BM^2iE2;BJwmiPdDx5AwPYadF>ikadr@&r$oNs$(Xz_pG{ zy&-M1pcmYUTQ@PU2|wx4=JZ$RT&TG@Ts-wp=s1v;*rDjY=#`4?I}|O{%IH5Pw8_Bs z1Dm5_d$dED{xIYY==(_f)l2*`R70I`L99A+8+Pi;?~>5V^DsV7J#3f^1-c(1Z~nwh zd?o#3i+0bE@MWN@kG|hQ9;_pw)5(_-&6$t9th1)lUbtYxcl(FW#$c=awEJb|Pzti^ zCAoJUxhmu*)1zW@R*-RUuX|BqLaF z{vr;)G-m7ZkWfwFD{=I8vE{BFxk=~bfW|_}!mI45SCX<5=(@R7en@|JWA?lPoBx7j zQ?O=-aPgv;m9H*#;A7HsIsf7KDMG*({PqeMumG$V(M(TZLS$*DSepql>&Hy99~UV_ zUK?SRHSls3o?OE>c-`!9Vl+u}C`x?(k-v3;eBp4#WV-bplNrG{@sgN&X52OIkpqfI zeuH9jbbteQdgXm>~MHk@Zx156? zyS*3d`W6OrlB*7k={e?`2F}|I#?Avz6awEzteU6O{Sq$L@yEO2qPGHBi}6>Ge}S%> zfx>k3PG@Pq%gn9)l1LsUP2#$*L@V!sC0FPjt*Gat9$6zgj;5VAY5N`()sOkuHe4_e zM_i?q*HFP9l$6Dc>x>S1a@l{-?B`s@7-ony>M{lXGN#9yNqMubpi&t5lwTylb+h^L zXT*ZRWav)fG7MP#AkIPT1RcEdP)fB-ud!VI2y`r*8Ri8?+$HlP=uKx*VG9Z>bqlQV z!ne9TD$&(n^tnpFGg_zyW3Dhh>rmYTCXB$~Z0_!Q=5RO@wF`}Z0{{BLa41gl!8yJ7 z!qcKj39oC9w`U7U=J=eJGi*PcQELXcT!3hUklmY2?at z+Uy{%9Z&jA*S!uFwNr$tX}0=|qb)!!D`FfH0?z#X~GY$!%or=f_8 zq&$E=h~Xbf@Uwhjvn3&}yv|3oZ4nO|;b#$a!F2LDgjJ-%n0*p*9u@!LYK_sKG`3|R zn)-`Y&jDm3QOdyTKK$z*xc@ZW&rH#=vk+`gvc6GCJUD8`eAxk~U1Q=-!am0&@q^g; zv8p)vR&0hgxE{VqzNKT_R&{FJP&forlO=9p?J9=t3{GJTP9Y!fN z;MWAn$~LCfQX-CGSDr_*pD;qtAv6JRT!q91aO7NJi%%cei*IOPd=a2 z2-Y!V=U%*N8%enh503_d4U>EajIozwq%pT*xq0D?{5G=JKpahW834Bfg4r~>|AMe% z1AWpmlPV0UFgj^m&Vx{v1WZ-WJ?>?l9veF1z=1RmFkz9Y|@ z@}BEOn^K-n#|Bfyjdo;(B`~>22Le>~g}#{0HUf02PO`?6-MftW*dGnJ1_nC9ce zR9X-sesBeT^L0VhVysN~-5q~VB*7QS?6L6EPwHpMfwl1LVpb2to;irKi$o0%V2v|0 z-2z65pz^lRGX)nL@@HB6@0eJ;n0&Dzb3#ezDRAjK84`^22pY*S>FP=BqzCM{QWWip zF4seAeXRWk(4>n8$AYmXeC1YhaED&8As(~AkKKu9JgEyN@0YPBcf+mM*t$~i`a0Wr zEnJh!%=-)ny?}8Eu%GMy3sXMNb!QsHjLG7wT=H|Ec=|W4J5F@d$;B^Vg%geS;Y!w` znsV;ODA9hSnh!qEY!_R%-M`vKz zj*Y1ROHMHpw}Hcy(|kZ%5|}uBnj;n1ZUD3V1eXiA{Em>YkE9+E#v>B4TReRRN5s+o z$H|I?$ZS2Z?D0dg!k?dekZ*gAWR%W*ipi9YjRr6 zU+#(Z=DADVNQ{O~Z%;2Nxfc!4YKSCz21&L#C3jN(Ag+GyBAp7b$JW>Ng>Os*qao}N6 z8zn^SBB46|&rAHnQz$aU)iL;|4sTwFY<7W5zWPBHy6eW3?1HNYb7$_MNgL7ZPH19R zdhs+k)5Pb^CDjSKnzy*Dgufe0%r1ymdK04w(A5QeyN{}lgXq0n*ecYaj61y#S*&F6 z5(w%@_vd=(llX!s6D-B!1!T{0-PGY?$5+DPX?W}(x^o__T+Z4}fsdTICp@^W;_ka3 zA6u@c9TO*mFFHbmF|MR{Gjwb3;^wQmW?wQxE^Hf2inSzk8g+JoC73SFVCn;5lDqU% zHq&ks>+lpt4TIZnfRRV>+?6!9QEVSZ6Pt8VSH+kDes->?n@UdHBX1T#ee-e16L!F0 z_}-Ztz8dz=<3f5dqk@t36*z7cn2sR*BV->T4i|JizGI(hLitT%vP(RLNM$ZP>Pz+4 zOfnm!eUyZxFfOCGEB5GRAE-vmtPV%Jb z0l7$+flXja0c*Mg42|WaEpU^XExdxRv_mzquznR8RzkP)x+#zGxbs5FdU~OY(BGKs zdWenAk#W)B&SiRYG~2Nao|8(N@1nkK%NbXPIl^#vVaKiD;wvz#Nl5%c_$k7= z7|OomLr;(kvBKO^Ja#Pox13&?5080+9a|36p~1rET1c!>IfNQ+5s-h^Vs` zUfly8x+sbH27>JoA49>$jgZYb_@ns*O!hw8<5zGJJQxCw7V^lT@71)4v zm$*BF(E8h4at|a-Wmlety?O!z4$Rnw%?H!8Y*8?y?04NG1Kj_*P~$*K(#XL#^lTv- za1`p}phMH4VJ;iG9F0G!m&hRbT=)Zn+*o3=oc_7Mw{67WAD`EPg{4C3Hk=<%PVFRZ z>p=QaYE#LaT?PNUCNZ~T93$9K!LVQ?()|JNIO<+PYfEwXW~#loszVXgeuw)%R z-HW2}V9zk%bdl+l1AA68Yu>>{ZOK?^Xnsbx1Z-5880K2Y|#cxnATdr3IJl@Q-ZHE<8;Mrbqv4AHZq6;J}#-H7dS+ zIsRxYer+MGn3pQ?+ab7n3aLAktbgKUA%sr7{>yA(VNyH`X3DOVP?&S7wfpMznJpb%#N98SpbN; z1eU!M;Y%_Gip*!y-BO53#Lm}*@E@Yz09MC*-WpUq^@9J0SFP3sE12I(=;=-F1#dk(i_0%Q1u z)n`05SHgpXVdpqH(*(pV6AKGSWj`??k)&D(7fQvFEWCRZ*(am;yPm4Sh?`+V30tL4 zkj64mTcLe1im8EmPOMcU$ju|3pXrku{APqNuwr2+(tRNQvW>i-Py4vi-Z$V4C$MG@ z^Wq)c)`?5*%`_V@&uZYSb1;7atWC#sHvJFbJA-M}HDUBw>{ucsd*MWaZ9_;N3-z<9 zRDYIz2KRMgZ2o|{|Jd|tD8UdJrNRk3l?MWxDju;Rn|BF?XYqrlqV)(;SBz~uiQPl+ z;TW|)3D1Xu?FZPEC1`U1D||;`321*a3|~yw1_1ka*esjI_rR^c2;0Pu$`{jQxaVg) zTuSR($))}1#UpUCH&amsN?MuTf8abnCff~tkiZTt;Ib>-Sxxf_g);*7>L8}d$RwSp zZz8viq|!1{Fck#H(?ko#?mT>Kz)ft&3=oh%L_0^pq)@m~MqelE6SJiDB<;IF(6dG* z!^M?Bc$5`+wvSkh1m`EywrceC1?c!6GkZQMBiYN!eGBUWZ-qtG8S+Bi>Kei$2sMT`uA4_8WaX+=7IwdKKm@{ySnB##8*c2{XTuUjF>fxQ3vqVd{Q-s zJcIh|1eki6E&mQ(w=hQ+LU$7eB_hW=;M5mrts;_Ipqwx2zu|+zh5v?=;h)5(fn?<# zvO+;}-oo~$=rbGCr;>)6vd>Fk+eao&g2w%XFXEw@-oW@DSZ{{EjisLBG5ea_G!?tH zid|pevDdJk%D6=!mD8Ea&EWnXhPQ!hcQNs6(fRf0{1>#_9K1?~vNfc~I&gQX@Hmh> z_$PDA$IswE9ouIwsuWPb1z2@eBCMwl3cLnq*GeaMf&H2*kx{tvw!4(ekVa2x#43+z;Ik1?}g z9LgF2?>>XCBgu^naNk2fA|Z3dklr-+p}5tM=!?wv*ONZ4z|3KE@lqx#4P41%Z)fS- z;>?ja*q8*RZ{YtZI`4R@-zbis^IUsHLqnuVLXrlhK|^VvR1}I5O-UsU4bhOalnRlC zRGLZ&m6lS7HqnxXWRLqi=lA{nbN{@r`?~jgpK;FTyx&fek=7qxM)Twh8nvq}u9bJ! zlpAJq-3mTVNj!2weSKcRe13tiR9OGLwAh@ixT>&2&DF5RkKDvo1?SzjWes&RG@3T=-udP~^BZsd`6{4rHrXFm; zA|y))EJgWp`JIU{!bmu)hoaUhteYX5AZ59| z*d$%1I0IHa#yN+?<^ovptnF)oSm@U_(o!5+z}vfvgX^g5EYF_D7B-9drOf9Poms8$ z^bT5w$#fI3I0aAq#f}}s2VFEZleeluNTqyvG6lKF?KAnJuA+IqQ1XC9-^HSIN&5tW z_hoJQ;G!ujdWJ?dD1z9K!(=`N$KK#eyFqAZCy{j+&dN&R`&B%VLJRFM>##iCm?A#3*~W>7Gx-NcQLQND zIK+{BIC@bO=HuVpbi`jl`6iz8l9i6b@4vD3EKYEttY2VR$&cBCrV;0+^l^&(w514m z!sBwp&|vC;WRc1wS&8fidvp~>WirEUSTdEJ{Di+7K*FYY_=;b;Fke%C<&#jf<-7FB za4R>j<(lK@$V}lHFL{tfmX++)2>>01!1h?)%p#r9c_}6x#C3PXwlIh@5>D0-Ge!Qn zOwu^YkA{gZcX&{OFmHr;cStvrO>mOLi>!Gs96Te7e2&|fFiOCLq0qeoW_I8;S83)Q zxnCSr9+O)f5bwhH#Zlt@NqW!4fXR4j9sT+zyKx+@-IMuOK-+6JaSukA!QM~crb;He z;g1K`x-$@L4Bl>zsrbUr0aL_fnXDYjw1lu|)I6lIn z0A{-y8w26Q5qx99Pw#}1NcrMC5}V|IGQ_q)T+vEIDAJQl;%hcW7}3lSh1U_lJY-Rl zcCDCYoWZM&5NeBBT}Z+vzuhCBvW`lRx1GHt3TE)n;U7jZrcH^Z$dhWtDX~I#- zDj$gPv9hR}7`O_9_MoA&FxJM+PvmI>K&v;eI7x{k6L;RQkZvO&}Eb5BG*Lefo?U1=l`FRR$Va9K16^@on26p23+jdO zS`4~Gg^kSX2^{xP_}HGgOkkINFjcbcgyQWqesw?GJIPP#f!+}Lui;`)NABq@e8*9t zCau_ki@MR{!?J!Z;Pr*gO@)DXnVxhFua~C4LHi@+=D_Tia{Fo2L*CYQKtKg=%@CP( zw0nk_vjy~f()G*uTnVO5kQ%;hb2GDu!3Ztfe-0o25QC;*Xb#U>07Dz)6FO7IYyy|zkhO+l);kFOiblso*GTAND$2i6 z_(8eySfSV?*NPNh9L2|7qH-91JwQ>BtYtC0&|(to*UnN_-GB}qS+*Gl8$;Y47%_~` zP@qx=e$b4bY~(2igo3uTsEX^mDBpyt$6{zFP~RmhbjQ*9vM>G7v=e)H9aTQi>W|Q9 zMZFF}$$7rtht716FAEaUvw7VI;c89u+eD{u9IFT`Z=#7VBss}!*W#_~MI}z;mP(0v= zlI76M0Ng)8K&rSQX_K_%u^FQEfqaP;Rdp7Vo{159U}iy@J8{`dx^akE#De!(*}~PB zdzkg@j~~B4iV6;R!~b0-qb2;UI~h#iemBHKC*J5T{KACRMKNy%M&?tW!!q;tuU;g*ENIuEKgT>OPWNS=ozJvE& z>b;HanFwQ6D`f7&xsRDqcZ@#qHGwPKryC2S+iZ{{EX`+yUif}1i@S*W9#S-+l#lWjH&f|pEXfqHw26#2$~0!6-(Tj;(6$)ew&N~SSpEs3!|96$T+QZA zU8$~%JXcNJe$B5Hik9_~RZ}!O;ID%;{1mRLr3>CNtJ|3PgT?rvaK(*3fC6Y$9BdxL z{SVNdWn96LLMQVvo}$ZUp>Ih&HjCI_!7DFD#9*^B|k~ETB3)liO6%pu|oI{#Ew;z;U*i~0Tm~(KMx>h7IQw0M+Tv8 z02U_EKKt(C7_EEMlib*1=z9G-^I121-V6Ff?j zjoOA`o!PA(7!?ZM1~?)|Jn#U&aUxm)&Xve3UWspJ{N-Me@lh=5OvcYazL8w_vwa~j zWFfPC2Hk?$-~=r9!DxGQ>jJ}G!G{p;c%F6?$Rlcm>1$qFBKm9>FII`_8aiY}KcoU$ z5ru7&jkdt*1jagI%0UdCg-icYx(>!JmP#?;7|sm>Y4c6__;`_2$|d<})LMG1EP>5& zQ78C5h;@^YO@S=j7}FD3)mbU&jc(PzFNypK5F?kDTaZ-=cbQIKPI2QY;>S~h@~PX!)MW8J zLwu=(QLbbxwH0i@u^;Oi1`lV*67x{!Gn)6r`xP`R2NZL8OdVZV$-5@fftg(LEPwvP z^Sy+AIT@@W%@LC5nw)3K8ur58QZ^(BUNoTd0<^J)h8!3?nyg(QJb^2uQOt08a*_yZ z;l{nGDwUG{iCuP>eUj`8@xwogwUyoR!Lhd(_r{mcVdr}=uM?`7;695Nf20LR`E)lb z+|0dfMOB?Rrb6e<08}X?jj3vZ+B8|yMaXH#YHPq}46czf1Fvc14LBYlLVdt;wLG<1 zJa*;phEV@YqI;pxnh1z}`bA}ECGoxd$r=Lu12`WCr@_GoG0Quu_I*6{*VC@Hf zu7J!Vx!zUb^NjD@Cfd!U2~8s11#bk>x<-~50ZAQM*B$UynVpV9l@~B;Aof^FcaFmL zNIrBU`PIlhJJNK)4UoE+P}Xjt@)Tx!(q3OF|3DtU+3cZMn$AYqqd_-(r)$mt$f>25&tqT6j0>l=pe zop5wd?5Gc_FW_nkzkG%A2J(v?=)nMSLY)>TiU;?^`()axPl5gLQVq?hWf!e*ovILC3iVh!XPB8Dp#iUhyOr{9V7IELjDKGfrPE)p`g$20e zVE6IR{wp)FMgK3D_Y0yX!~9*4>n8>jk=_>`w3+;#^RRxTP|dS)#91d&+($do;l&&J z8qKz4fW9}g{{U-C@W*iMkpSQCfMqhpPlPAmc~U%?6w4h7#Ie2-nw3g7Qcf4T?STu_ zpgtXol)#~om6k&%XEyK`)V_h`d*OAd7-bG0H}dK8==h6p9*S`V>0IH(b{bEw=EO8rHw5j35W|4E$iyp(U9U1h}xUwXoNe&9z6V*&*F)o~c6UE$~kl&o;ojYI({!+Um^z zjixak;^sx6z~HtAy(@|VDoj*zaU~cy|fmGKGDF% z+^PV zgk0`KTg!O7A~nAhBg#aLC#21%bIb5s5}ntRrM!S6$Jpf@*zJKyrEu#ZHO4}Dgm?^) z^OmPsQ?K#dDovc+Aj-7q{8zHmCRr7FGMHh;riR1p8Ela$O2f_zhoadIa7XN0A$F~S z>H)lmAN_m4E9Q|~SF!rBu-!#ft>SMUboZeLGg)wdiIy*0dKb1!fbJ*oqZ+0uV52tK zjDf+kMbI01*N!jgM1MB(KHtTWC;!vJ7u<#8Vf2*a@bOSNo#n>BoC4-K2d}&1&n{T@ zm)_;U?myh7iqZ~o%RRJEni}p(N z#){;HU~Wv=GN{VnJ7Ng`$a5Zwp2I|eI;k9>Lw0011@9m<0u9Hn2OK7Fe^_W5DVmiv*nV1NU45 z>WsOkp)HTPX~Wn!NpnyBZStR=MBj(pvNIXo7IOwuwUxBB(Mn7F=LjJ-Y(+NQy2n0@ zz@lJu$_D%4u&M#(4-g*TDefb$h@?X?+;a{M7JStp>gYkQ1L)9Xd~==dd}p2Bz)}mg zdn*iofsf1KMKZYUfg=kj-yDVq^Q=G$)8$Rdv`I(!_ox2DX*m~)1@K0YLnJ!(f*z%8 z`8Sy5!G6_C{SI^(i2BB)8wHA-H(jCWANkx@^l27H9lEeY+?zv-)L_^=Qtrp5jfS)U z=CTr=PGaA0gVjCsErBLi(AI~`m&LUf((KId^`f6~+^-LPT`G<~7CRd$=>iRY2irKE zw3gBm@T`FyY=>j-W9?_yWCQh;a4k|?Y$US@;wPueEBH=xvb({phtq09$u>oO1bAnX z?Mx<#%syJM-r*29jD4#BHAn1d4Og_`=NQPY)m_ZL|ev9>l)n8Hj;p?nN#^u@Mc|Q|UX( z9!M#Ln5YZAhp=id$&k)wUjXdDrXK`}ndz|xMj<_95VVocwWflfylyOc>WdeTgnSq| zOr)K&VR0-SZ_hdh!R;HY+at&z+zY7r6~^~O8baNiATd;wc7^#fczZuu=EV;VrGN_} zT#^3(LOh`adF(Z^gm?%KGC ze(%KW>vUl$nf-zxPpNVx`*sYrO<}zn;qFB=nS!fEKx!;lj;1_E$T1XmpVN=td}Bva z(GuaENMjZC^CXjB@KVr+oj7L(Xl`Zm+T-)Ntf2$CwZetdFv*=xn!`#a25%>Wcl@vp zP08VI4~5|`F=isYRtD=;#NJ}_L?}Ec$wT4nFFfB2$%gp)1Dq|S==G5OT=WciNg}xGSGMr6GhYkA7@+mm{Mjs=zzXzf3;kPg4%>%6> z{wsr+3HR?yg`LGLOG?+EX-4D}3HhzGYXNqi0du0*%Z3Ks05{Uao$ zvIt*F<`cw%nY3*$SCfG7i9)MIn6^^&6bgTaod<(bU$!^_vbwO26<~21rzoJZ5d^z~ zZ>bpV2$L-NAs^BR=F^6g(sc1+AlclaiI1q;bJ(N}n+CEdd)UxTwnGD*zGFr%tXvN@ z$*`$jl$Fyof1#8`1unc3qcbnLdb5x_l8ZF^Tn;yU$jOOqbA?e-8!He7y0KrsVXOgm z2>hSMEZraHW zYdQiOYoT%*Nbr;Xk~mn(U`(g8xjgJREhqlOmf)*o{*^LsFs708yRn?ZKxxeMJ@imw z(&jRzJ*Iqurag3`n1WJ8N+z{=a;8H=_wnA{NjXt$`zJD@AuxsXvvJW}c-V-V(_wxR zvsJ{&ck#_tu$KTDr$ALte7HeBGr9Ls3cAgUZRlTDv3CM#Po$)0w7nQQ$?3Kavws6O zrn8C$C``vQHW=m!#y-$3gxU>;q~4-e8rcuvZyUuZIUhjc#B>^>O*+*OS4rvza8Et0 zyv+QvVenCQ@&W`;#k8|fvV&~B;JQpayF&h{JbojUZsR>{DQJ#}NvF<7$m$gJNJRN+ zh+D??6~df$Ec_3Q@kaj#aBKn;`9gS$xYQf=Ir0y~X-hPB8&4mX3wb-*@QNJP)8L+% zTuvMMFem9>_K*EM4QUcO>?o+cfzQdH`BXUGrS1x%(^mSpj|aI@Of=syo8nK3Ny}(g z45X?;lsUWq3>t^A@lT-6p3RV9F9%#750~E3zERLyS2UiKo+$odH2FT@DcV$-AO?0J z(|ItVoOZh7d=&j*lPEiERn>83C~PR)8kbqkHP;isC!8zzqT zre#Sq?g+UrKwoF*{121TVE%NrK?}FE;7nZ%i-Im;kZ(ZGN5Z*qZnT0j*72L2>5aNL zuS`=H)2eMW@+gGnkkr~%Qi9LsvQMSZGJ#p#gq(WFkU~u%bhwdv#*6kZ$<>vwl+J1E z_*OlVxbC*TG-EQHmt?-FnB)Ts`m&f)u+oo(wm^|Pj!T7p{bA}UNS`9Mx6r$lJbx@D zeB`!s$a#?fEBc*I)za_P9XH-3_|E1eLH#*)GzqG2phYwcjRbQ~xMfY|&uPDs*l>W% zEO@dy*=h;h8MJ9M9do2?HT*FE_Ge(b)o^VByLuj$4Pha5pwmG)pCbBw zr1K!mj*{;MzF|Jif5F42k+lik{Ypz>!Tu2y1u=uuV84%PpM+a_Ogj@E>!OYk98~~2 z013mclSYSxyf+ssp76eN=-f*&%!g_%rCEFEFaYg4g8fy7cVOT-w)-R$ZpFYIFtd>? z0wHUdP`*YHm-zAfblHaA+(f-m6i8TD2Xaj$t@Y?S7L-n~>?E+B&71>ZWEb`;5F~U@ zttQAl=)0iHe!RUYDeU7NmeLtR@z|O+45auYl(!coeYRFLx=8}lx2*DpbWmYB;ozZ! z1v%i=j&2o@$4KFSlcM+W(-G7(g^yTA@^vCEimC!3{u9;Nve$=UwjGObh34%n=ptwo zV6VB*GzMDC;LbSVl|l*&`O5hedXFy~OQnlNFH_pzOs(bgL>mWxpsz>S3qQDbmr00e z{g-I>1j-E{!~uRk5`90Bm4^87p8j!p7iYSDli%7&d&W|3GNpGx2RnH84Zm-O1P@kq z52ksrwlr|AKK4w4ZTX0Jsis&OI#Zm8A zx+y7=v*F)1Fg%6bCPBnw7LX1d_1T$a(BnGnwt+YU^2ng6E@I_=x|jC9eB|OTCIWFfoh3}2_}tRV(>Zov7Qgx zNn|EfU#Z<9+JBa2&B7nuLD!4DUI^Mw?2k6=>dp>J;`nB`_m)OV7Y73PFh(FXEzyy1kd4TN?gZ-M!Z4%V0qPHJR2qBY;q;gD*Pp1uc zdCDEKKExm0pfj1G>IsGUK#u|nYeY5=z6@ouMj8>tWG0aQ1Z_*m!Vz@8Qt1Zq<}URO z;!%f5JC&>F(yn1b;VIQ@r`vPEFCTQ;!HmVsXCW-T#Ol|$(!l3qp&Wgu}it?dAh^7{ED=y2KiPZonW(UUG>)-hTn?I!CfWV5imL`l-l{(~$%(Ao*cwP)Fh@Z=rNNQI#J z=za=3biqvzUZ_yLDyZM*ZKvqmNv^V%cz~F=jZCjo&r(SU3o}imy9`PhLgN&c6#x^p zSci$Q=`AF6frQC){xls45#j5pw2r?IAXdw_EG0t{@*KMD0$;w-%q;A`8=C9z;0>_O z$NDQUtPK48z~DKlPXnzf;=QET3E@BHQ~!3N-5v^S5sNNUWdO_@BxyP^$PiN9S-${S zVb6knVX`N_H7%J~QdwG+MPnj7g$DN!)M|`Rj>uu~gzp@^6%p z4+)|0>OB744?9el*Jk*m%uE--*N^a{FRTcmBopXU!=LS^LD78Y5h{AkCCveUDV#@w zZ6~nl1$$QD-|q1Kh;)F1XZ={8IS_A;(VCE2Pp`vB{1t!GXwgr;X*=0H7MC7B-dOosUg_I(&e!D&Ypw+V`GpoA)|cnp6l$zl_AI3#ILMd)!dh~>rZ)Xz?M zNTZ9^bl?~@T!l+BL3bD4SpiRs*!rasj)0Ba3|H!5%W61(j6M&8A^Kui6a`oF-Z$v< zOMWr3B7B^DIDcCR&){J0%4nn_5gkV_Whbgn*W^c$!rw}tb_>4RSuUbWq zIM27I^%>&pOsf4uw~NW32Nq9|Oo%vi1U&L*HgVt(gEv>h$IbB5U)l)7@Jfp4FB-0s zc*z@&lAD~z9-&HWn)-({{2-}Zs+OSnE|}hg+Yf;AJzTgO%uB&W5B5K&Z~%%HZMSIi zBR*H39(jv6TPn6BJ#Wej2bW*;)d}-jCBF}QzaEBJGKzw)BXLtGsQ#wq)4+bW2+gEr zY9ct3l1}q$duVH**iuBV)>3T-{I*;A=Z}FasjPa9IkR83t(` zV7?Z;NvFFD#Qq2LJDV@?q3q$pH-<(Hq~=mO9tXuoVUs;J-G%Gdaa<-mIETrHp;JEO z4}j+h^f8~J!$rL!t@9I#s`PxjxIBo)EvNn0>5wZd{y=3L@T3Q1c46t&;CTa+9ze)9 z*svDXU!W_$sofu;TuGan_>zs3`jyM2nDjLx zX)NB10&i#7u>$@g&Dc+C7KriPXqSiRGMYS|h{KEN)LGI`p|tZ5{F%xt@cL0u;W+s$ zgdM=HF)%U#M(Bh7F`Ds=cE*dDmnprHzwoBNV@3Z}l+uONQpsux6a~YO4p@2&+C}55 z1Q;HNsgEJ661JQIxJ~mr!52xiI-6bw2)P?|SS)@GqGUx%Ure)=;AJe`pNgM)!Jrn5 zP5|Af_~{CKb3m=l5@`w&ev*f@E`1}KAzFf{MkdagQ|?jmZ7%&dO?zaJc^YcALpO6= zy%GA{#ncZlWEn2G1B3iQF0FXOX?G5tP7`tvMOx8AbZSrbzT&+(jWwXGl_VW1thC|$ zT^QsJjT><0XIPtr!_=^)1Pq=);5U*I0?M^Qb1(f%6bJ38^K!AiMig}<4I{e!j?TGL zJm5wXSa=&ZX$zV@Uj zYwDy9u~Lp}3LcIG-(ajvg=>j;5b&c8_Pq=a4$xs7>@%a{7}8!OMs_19_3dLwk~X*8 zf$HxNlN{em;ptFNoPmq~LGShW$qJ>08dgK9IY{?$9Y-p9L0VtM?$xx|L6jAV<(I`A zHQMP$KZ0m~5_k`Rm!mKu3Z5OtA7zk#0GrBT!Bf~A0^Jz&)qv0f(cO$nBZU!A>;ZAr zfGj4{2XBP9FY@(eLa zlh&1qtvxBxha}%eZXdYb737o9u>*Q}p_v)J?vE>~!6OtR*TIXYG_;D2NsWm`l;keX z6p7%=!oN~Cv*Vl z_hfR&5PIz??x8r@C~8*IE^`{>4YrBWV}ntrK|-L615C8X83Ovpz|16w`9eeHL4`6Y z=hByH;<;Q5SuY~xVr>V?vZYULWLQUZ82qNetNHl799s9`3Vl>;k+SR1JOGaR!=GtX zzk`;15{?sSVw~71qp=D!rwdiyCGA;cb03~sL7E5d$pa}c7FG!TyCExtrOUx(Bb<&Q zc{lhOA;!B&)6l}GH*J0_yn4_+FY=F~Zi8WoHthQcD(>((9&JBB&zV@#0B0V6*+X#3 zqHn)wyfIxpKvqvggEO6q6*-!e*Mpu6rh<-8e3P_vkk&wr1)5xk9A_NW0@5IfOD44M z337L+=tXHCsn2e)&5=sai=0lR{aZ}vMSEY;%ZKDK7hIL$f-b%~3%6V_Sq0xWLV7)n zo&W=b;r21I`AVytND49?P8av<#KI<_tWL)gsc;6lZ-@7_6jKK20nlKED~rI&8jWh9 z@4o-P_Un}-$?mVT3Dq?;?uD@EPNv1;g%BG(Xs$P9e4yib6t)?nXTVYyG*!Vr2DrNx z&fJEb&p~KH-2m8@MH*T3oy4@RRQypC)rk*dX=i^r^nx7r5{APG2N?PuHYUT=(fE-- zyA;Me0iC_@XA4O29{oJ}j&xx%seBSE%*pYySlo$@Eut;qG`5nTPKx9-DGJ^Wn; zozyYq9Xvh@Qk7q$->^4qF|e&rtk& z225t+mI^3rfdPrIzznj?z+n(A4yX35qI@Zt#EN%@wEUO2=T60Isb@NMUkjR(;X*fj zT?Dx{___c#^}*L!;NT1TV_?)P5>ik3w>V`>@pr^t5=-6-W=#}JM2o9QDI0D+ z15X{y`3Yx6qe}t!7ef2p@O>KWngr={$oCRGixGE|EGmMUCP?unOo=#qW>6$0( zH3t$R;jeTLJx_<$P?-khEu1vbnF=bx0Q3?*E=nMrxlYBrrVRfwo%w0RVD$fTAQ z`lTh6S>VtNnBj>I84zfX7Zc$@6NGv}K{t4MmFm~gk|=Vo5H@Q`!rCrzAostb-Bu|m zPD21D9fdFFAmj~r9065RTyp`2tD$`;te*u9)=;#X-d?As8lmq<`^v-*Z`xQVUPY5e z5@~a?9|0~MU~>Zm?uINoT#@m=0d3SFSiK$Yb%&x7ntz8Bd`Xh-732zSJ2LtyT4&Ok z0BVh-0|qdrH{>0M6iMvZ03in;s5g!{4Z9D*D?iX=VA=};9O(2~idCg*PpbJRs+Q1_ zX>|K4buXt&6}0alRE~tBcDVQ~m}sG4ILyfhX{7FrC6xT7>RZ(31+6DhvYmcB6uo?C ztr<-Yr>I2Q`GH={hb=xZHy!%U2QLE*3kTh5uvrU(B;maigkGn@r*un~mTw^^Mm~}B ztXdr2Mw5dm{1MGlfF*JYuZ5;n(5WlV3YI=9>IFc>Q<&Zd&bEh^0vfZGu3x1+l_JcS zCLtwnp~t;R?=}@wki}5YcZXNwpu7Yc=Yh2`7Vn0iMW7+|*G7Y~sx(?oy)RQ|=?i~I zN=0J85t6Wz<8RRI80z_n;ymD>5qSLiUl4h)J=%MKnE}R5hqKG15q0XK4D*`FbO=2? zNS<$m-EwOADrP5B^>TXumkc@i4}wB3@EQ;4?Qr^8I9CfDf??-HsrUd!l4r1lqL0x0 z>lCabHC8CTL1abI!MT+2h89VeJ0%EQ1#34-|0b~Wh0Wb1)Fcdh08@?OeK711bm~22 zo}(5ssfr-5HWf-)2Ssmv&>FVCg=1um(>3f;o0zrHT*6L1r!lN&`oe zz@m*VtR;;wim;^R3AC(5ynR9j*0k;~)o0RaRZ#VX;6C8<4z#zyplVQ>BQX&`YB@D5 zmRtq2Y$`2zLDG@!GlLetNc4=!r~}=6uOf=WjrDTjGcM6~n~IdGes?wC#yf|Nc05q{g1! z_;aJ?y9nslRVS?z`*TzITQ>+kgZcO1cU8^ExjdlZ-wS83-%@v>Gn%e%Xe)r4g>BFC z!S|_beh2Ks+ubQ-*GOeugxL8@Y4JoUiO0O(bH8A8HK+Y2n+@{#rU4C7qV>|n=2jyZ zmo4nx(F>ei^DuVsIK98EC151^?@NA zNpEs~EA);v_lpqc2MFDcRQE{r_y)`kSDU8}XjK-D;b> zipW>K>NxDPZuL-rMKO&lbHt^gP3M=0kU*NK1y9E+#Ye)*P?huNsPvo6+?l&XDc4Ax z$%#rcMpN@&{ym8+jBj`uEzc`!QZS|RsZx1?-S5JGZ-KPiWb+3e7%JMdXE&#+lb@_> zgp#@&E=0`e4q3hVvO3D@%&QQO^l1IO2uxnq)->{p;-(q%DJo7h+Cq?>%9ap(ohO@F z!<$W12RhTvOm$!BPIOnnK3JLuY&$WKD;=pzU;)i_efe%@D}sfJcXxLFS-+>%Fhg`|qMu~xWa&tLm zavENp;Ul-!k3A@!w{P%OC!^4|p7UY%LiX7b5p_Jn6h1Fgi#-Ldu3GO8vZqN3U5CMd zH{!`@SXx-?zC#>6^=HXWQ9Gx;qza~qhWIx$Mpr3IOI8-E6jDVquWMJ_z^~Do6JjXm zh)UOY;^IG0*TJnt>RQWiT)ASD5vX2}ogakfbi|=+V@ydfohMHaDB33d5%=#5DGu!etSgw zy<&Mo+l&O^X5W|{!aF214$u_O-nV^TLYkS%-Qgh+wt(KA1cw$)VkZ61q-j;L~=ETeHSEFJd)#6XMG*RuO z20lu{1$%g8jrehq-(NuHooI??o5})lt7}7LXP)1&`QBQw`7d@JiyvOgQpbVAMA>Lf z8ttpxW(rYH)u-LWcX5hIHlU+Lr71M&c*`bf2kF_8ZGlUEG;fh+b%!^;u!QO_ZN^* zUJ40wv@=9@yqN~XDLfiNUVAmV@5Hs^)q0-;=Vrxf7s$(zF9T9fZHnGcy;rpbxv?k1 z>ZNW-AB(01QSg-H79(-(I5o?+GBt0NKly+Yl;>v9pmG(n1Ssmyp6U~xX`S|uXMC%f zuPWDyZ9J?_y{{3oi5{?LJMtY9u}dDqzM zEPo9zQ5M zZ9rWv!oGCcZCC5xUfzCbT}FT5zNF4kOXP2Bi_fEkRP1q%imodS+(Ua(I*fVEtS4w# zC6QlywV*|?KccPXDDN85I>H~$Km8|b5{jNR>dPp(ta;BhOtYtL5|F@I{pfpHgN~}h zLwI>gtIt4YDKVgY$m<7duLeD%8}5IRcX$6cdf@lZa1g)Z9t6*41;>#f5f_zP<2jT+6@T z(CDuEGfbg(l}c44hAh=Qs?J=3)$LSK*-i0}G?O@~HTnx5d-|`NqrA4EF>e6;7+0J2 zfxAzT*Gz@v$FM1YB3v~St1#ABYs^B}SE{P3NToNFH|(H674qytzD`;0+aEO4{@p9* zg$wI1TfwKVO@q?m#5M)jJF;pOrS~~l+)m|;9!?+FZdpBZIjJ)BF8Dm4xw=$)rM6#? z-27uxSx;a_b$8uG`=yOhy3}}%?|V#f-ITf?hwww~hF4;7mD;*t)c>T4R9ERaj8YeI zt_Z!&;L*v(pk-o_XRY5hp|z-K`5{nlfUAP7bW)f+hRylO);fa4ZVm57Ht>}ypM|sc zC?rjXK3`i*>iNji&FUU>sjP9ICnef7bS@Sr&b0hnOrHmfPorsPj9RB`^gN|LX*kSr zR+;cj%$=lQI!@TkQqa?eHV?U~G>v$rwrmzJc+_B@LEV;cdu^7r7rXRi&oy9iKRBqT zBDZA8_tdgY*uL#5`hlo_m~8rz#m$zW2cmFrtCl5jpB9V$@W8q8`$ZA=w`qv0*!7#; z$_JZR)gH|-ZK!h1N0IwlaaMr1zFg(Hv|y^q=58fTXwB6Ymmb&m(h{zJnyxFuwZYtN z2j1Kw{O^HgXIbA#7(Gv=p))g%)TrakYmnmJji7^K)_58m-j;(W9E^)Y^_U5)W(rr0q2=hwoeLsl_ubUoTrTMBt`5-7;DH~X^>+`il z2bpiPN^1}#HmMbI9HJo19*L&~JWU-3o7X)J7WbR~{SYE!Xv5T^$*1aL zm?7I94>oS?w(pj8yQ(&5DSWlnyjO%~2V{MA(j6!I77kttO~==g;*{zSjr`g4#&?ET zZr^O=2scb*iK?s-WSLW_U76OIW?VC0GgT^IdnxPx6f)_~_>~8jH16xd_s?ybGMMgu zu6YyAwGCPV&A_;~+4Y zH^IfN@M}nY_hF)UTHRlc!=BKFS*UedCe8jwUDrtafqB6iOKw4M zuF}?ibYhfTuUS4hL4L%64wW>osus=3wOMO<+|x#B!fvEDsQv<*Kgy}$=r>wv^Ex`v zQQcVqKi8^mcn3Mn3i>;t?YulNg3{_5ZjYs2_6=?};Ly3b%^c6FwY5W>x{Yt@3|B@d z+dac~I$BjT+2o0;-z(_gPdJ>-Gj2oRN}BvyK4c`hw$}X_%5_ZYqR;blquV|=lJ!2; zyBNlB#lhBa-&)061s-wLae?sWlIn0BOdTRCHHCwHT0^wRyGO$dXENE=Vw5Wdw3?58 zgx^=14L{K4XW%yxA2+H#U(fhSbqz^<9^bt`%=P)_pVN z_s%vHY!*4D@?p)ia|v6q1jYs`Nh{m+@5+wZbZdiFt}65SqFiVSSV5PDQm{wU$OFPx zlFljPv9Kn^_YiQq{`*Gi=*AsGaMTG|uQZ(OsBu1zH4IkH*iRm7mCp(yW0|HO?T?W} zMKoKz>Gvb<^||KTOL@l4<}Ky4Ws2PW8bt-mT)e@!Nhbd$434!s;>e`8GD9Llg(S)*4Wo=C zDM>~dskE$&O2bMbL`s@QNTDPZDkULgQ%ObZbQ|KJLFGOLRN&k6@KbukWv_gsC#pjFrr1NOFBHI zYwRFc?$h;!5sWTTYc-g@o*QC`s}`tyu_H@%_K8|eUMy9NGD9nOc||K&d8}I_p4YnA zW0V0dW7~asdbU7pH4fHZ>rPq;+A?9BnDY~;p3)c8W+^<%L7OKkA?aj`K#seIc26-U z90{&sWwr~L zIGDE&;v>3yqw*OgyR-B%9?EY`s-e$Eb*-#`Y7e@Lsb!C~{{r zRsEO3&K8mI12)a$jONk&5Lwx08hxnu>O2UrY4XCoPzgz~oSNyc<-R*#nwWNqa; zM&bPm#p1E#!JIT?tqG@_~bB5yY9k}2FytqZ1&-3Gc;RgG*r@nl4b;rM4+?wC* z@EWq*;I=xWiV1g}CvlMq3WG_FmTJljZiKqh@t;s}P|)81qPivDj`C|ac1?{yu0(P_ z6?cSoX4cbjW<7j2r7O6Zjr$dN?lbt#(tu4jXf-$GeCI&4YG|p2gv{g32X#&p;tZ{xX%s?AB_dCJ zc9j;$la{T56(vworTVUiYkw=MD*^{z;oxC-CxP@pyb>iVnh82>T_+;YV?@iNIR0d; zBy2UieM@aBz|lw1ZL(n4MES?F(N0|@Ck;MjD_3-*+($w1DSF_I)Pu?vWws|Tl)akR z)f$h}^*gsK;n<)2iREN;2vLlOS(d6!Sq#72=Vlx?D_@ZYL*!VYS24a;ltp^u_xC;8 z#n7hnmrmsu&JdrU4=bv>zuG{}Qcmk1c@U^@(iS?0i@Jj`U~u2x5=a&+6qnN*ZGxvl zR4fy(u9iMfk!%}7fF;Jld4&Dp76vw z-O^$okatliV_?sXq9=-+eYUdHgG3!t(YQu@k_0De@N5{~c@F{0JMONd6X&+=yhDx8 zcQ97-q;I_|gk(iO;O@W}efhxzQZ3c>*Kpg%s6I1=tH$zML+R?P{NTkr>FTxfrxW*d zsC?%iTxeU~%U@OL{wu=)m*LHFQY{n>Xykr}h(_)p8CzArn;d+h*!B>1ISN022esU8 zkD+*pwwl)PUt+{h-QY{BB+Cz$PnGVChBRm5YzR(b<<1x~w?ggAdNM{NnmHc3L_+4Y z9qz{)-QdeEcMl7s!%W&o`}46a?GhK7KBRY48-B8qlPu?QcPp(K&h3vDh4+BxE!Cz8 zT=;dxW540hRO%GVz$W5KDKD5RCXcXcVyBJ+>~WBs{(%BV8Z(y^`^bI&4H{on6_#`2 zToreHX!$B{R!zrmBsV+wu7mv0JgQtS=@~|cO>eo*7NX9Qho^AUax^n08mHveQ*z%? zp<)Hto2j`TgMn2y=Jb5(N0!;+ zEFUNjQ@XW*D>vxt>&tNq6h~Gu(5_rt0+n5b1{-?$w6wmG%5}Ho=1S8>i{szWC1a&C zy&>2Ki-A12Be&=mC|(m4_>#AZeRB&qlaY$$h|6CI82@u`Nteqfe%p63xriF_o$H1G zf3zd@3Mwz`ec%d#W`gV((tk?dalD{xi_%3)T@ zXy*q9`f^P-b4^NqwH}kulZlcm_H2vC_hIJ~RgvI?z{W!)E(C}3BE^50yG!|r8I-&u z3lgb(wzSuXiVw8*QP#lrecJdAdb*)KJr|#S>^b!oRz`6tYq=wDMJdNP7phqQ z9eu?rT93iAmod&wtc(%J+2^?OdbOV^A2yMkU z<%0Swb3woZjLDgc#_s^iZS-I#GWL^^)4~9ttgtBk$sJ7 zEbaBLMCbR&?ZcQ96>cH7C{wk=id?BzR%c+4}|B7O>4GH1dym6X@S$3mt1Jn*KwdmmYegC5^wZU zh6lSOuWj(MajU*IHI5di%fsP6vO@(VdJLD?Oi)8^%qg%fQ}f@?{d%u@=>a)*RBph2 z9I#ww)Q4u?>q;9y&ps5NE}$~ zZIr@}eV8*xV0jt8ily(EIohYqCzL;>)WL>6N2d0y9z*yfeyjsol)w$qBwIL9*9j7_ zNv+YDOD$Gfu>)%L1cxu7N>Xp(2R`$QB;gv(nl9D}qceOu_O7PB(jL~$N%$m){|ucz zDqDQG$CE`(W%xy3c|K?}c8k*Kkt1&yf_31SBZ0zjN z!E`Zyd^|)bD%c9SGXq3(PC>yJQNT#Bc2H^k09)S)+y^7hql?|pbxOx`Iez_)jyP6a zLP@eathp;S4!RnPW?r|I#w-u6J)Y=duup;|*J>;*ahTyfb&HvLmQEh4`y6yrQFZm2N5FYK`E zp5egCiR~9Z@{^0YKEHxT7bQMsU{)t>xBw~ooX>B#6WeG0I$?Z-vRHw<{I0OB6({@< z=&!_eYh?##p!Hu#X#pKKuQkV?&r zl%1h?r^3O{_-q2&Zl|+LJBG*0cHHRxdJB}L9n*p^^2j-&Dy?vYYj2g3OQ3xzN?+2YCbE?jwZFG<;rykRb~!OsP?d12aotvYc7j-pmoJ$} zroNI-H^tl5ea93K*Il9k_4vqMctHb~x^z#tBwMtvTd)t`-W4kz$3YXs6_NNoKsuP^ z^x8eW>+*0e``^^GGVL zeA`_y92&opKdVWMRG__pCCbz$MHBxis_pyWyjbDDL5%7Ll~ZZIhutrJ^Pf^B!(H&> z%=ULL=&ui5HO6qxPF6b|T#c2YRRxyKqTqZo_<|@Z1_pX6xqHEFQ=xDT*!s!(zQ705 z+Y0UZyf5OCWvyZUuKyoJ;P!?Z}GzVOrlP;yf-wI$I7s+4s? zKPW!Gg0aJ3HK9Yl$gC>q^`P#WvpC^F>$(trNtRfmiJvX0!4RTbBe&X+yRuVZW(i~* zP`PxHY+a+`ol6du3!kgP8(seKNqqW4?4?ZaS#*qNe}xsDeN*wr)!tLdknD!(|eD_LF!f9 zMd7iWyxe6{_*JP?3Nr_(1~!mfsbYX5`2XR^JQxiyFACT7qg%z{l*hCDQg#OAB;}Tva zwoCH{tig^48*#{L$wkBk?$qNktXV2QsF{2ZQ%Pwd+&ZO_U>tHo=_){A4AC^ACcgZJ zp}0q1TrHJV6t#EHpk=n*OWN=v6Y)$S3iW~}Ei&Mpyl4lEHR_uWf|3tP`xij_d4Wj; zhToFTF{L&myAMCZN7kJ|FR}DhyLcAsuSho!B0s(GcM;omDd#tHzHXwB#c*tdXyG^f zaz@@+1(T+77t-*|jvgHuzj<)0O`xpdv*bWC9`o)lB(OIK**VQCLU6VU8%Lh z`%M&nC@LL8wR6gYj3D5IoDb_vt*60x)Jng-bc8H1T+(q87qQIvUQEpB=_keKoC3i&-T;cMjVv4XE#_<=2cKS@C5gR%+15nQ0>m=9Ky-EJ)>Bc>TWBz z(aV;doH`8FmR^@YRh&Sq&s|THvkZqN*~;n?coVLo$dTSWxer^>k$y{kb=VQPh(;w!J{n?;AN|UXvnZEzkSIS> z5=|lX<3%px;Fyz=Pr(13LFIOOY&y*>$H86_*%n&7wBvLIefz#^WDE!=@~*c?@>pW! zN?NPr`agy@dsSV}bHWrQIZM!O=AOT&KYIB-!)fr$-Xq=AD6Vsa7d7-2_iUpjLDK%g zpf?7T*e&?Af^IhXaZstn1`MN=zy(IO%5S@bv75NbKX7VqZ<~OExIo*eY z6+MdG(7!{bs>`5!+?O;+St}ZQiZfiPJa;!)^;&MUJr?(Jo3wH6Y*~Rl4P4#x+64F4 zh!eG_Z+zE4#)Z2o-Sr+_?+aXVNJFOL@g*e7UtwMnj(e;0bs6|9m+Lu>6-RK)HnhDU zDLBl}3h2uEi)J3(MFqk4MQ;a*vkp?Hx}Ll*IK>$8KFoKN7sj%(v)pYLT=`gZsT97?Q=I!3 zyWP1AC+tm?h8j>qgD(5K)Y-b*eI59p>6$SSEFyU{CqK%7U6U6!DJtydvb9B7n@MD& z{1pvI`o#)jHp^KL7topuq&txzoi`j(@Os zBFl&5!N^WvrJ`O(Dqwy&m)X^I=0zHRzF(ua8*lvgG zn3vYG;R?2&1+CHK4^vT31|~YxT?SKkD#d3q28g^$InJtq&?hu&pv=RVX1tR+b>QhK zUBx#P@-hFMF42!MNS@gd(xNLm29CZcyeV0!hOJrY#D-GcH4Jdzx7aqtWKG1=6Jti$wSFV>Y#1FfBZ$3r6`&8*Q zEY={i{@}G@?F|@lNjNaSZ>r{!I zt-NCjtU5}HlSo^!V8}NJ>MtKPj@U)Y4;(?>Br=91Sa##J4zxes%iIu`OL}7V;-2BX zHE}r5QZ~^A%=OTh$D8_sN_VI)Qb=(k#YS?kE~DcZVee#|{{yCl(2WMPW(|6@_N?%y zb1QptSi#}2EYSeWu2GLNHn-%GUCG%)!m_t;=b8K|cQV99=xzf(Dx8rA-0#N^l*iQu zy@7z!p7qL_aA9-L$#gV1#gAGAUw(n!SeRleZ+?+{yd;-69WJBLJQmLz3hs@-=?gKf zj=r{*jekQkr}dQD($dY+@jr0+580IY5E(%V;>gCnf?Mz5fv4Qhp=7B@UcUp1)C9}s z!=Q3ZNW?iF(p4APf10#v6traZ#GJvegJdc5Kr{z8+CWAJ=gq^`V7bZt$c6%8I72X< z5=KA4m|$p4qk)-p&oqoZ+*_tY{crVtMS8|jru_nyEHI%KPHJ$9lgP85g7jyQULhZK zoAgT;Oj!vFj}p_q&_9ZIXkfu_nOho~?d+|~MUw-)ukYY|b$T=#GIoGx2t@x8o{J&! z@xne6;Z~+_<#!wx!6^+yb2qp!8-Iq$vSa8S((AJ zKXGXxWSyt*fF5c2ATX%Hg7*T}U&ym1H~Zk83p85=HwmSus`$;jWZWK1PM7IcVs0M& z6u>wJB;X{}KNSp;f@r?nL}zlqUf?neN<|zshvQ@DCxVM-@V@WZ1YT;Cg@((efzNTw zVSckcq~3vgLdIVcs6Hmkq}=8@bkG+L7z{rXI9*@dIT!pUYqUNt&QMfNtDZqJt8 z%)@Vi{Ek{|u!o`&a7`w`%fK*{8MTObv~cf1P;BG4Ma((@OIBi`I^E-p>BISoCE(N5 z+wPBjV`XylVNVWC42ANiWK;;bb(ss_1Jm*Z*G)lIB$zw{0=AKcEqG-DK9JJ1O|o4& z^jWSfa|NERka=2T+jq)v3FU6UQnf#I1jmaRuS3W@CUz4A{k37mD^l2i3+Ley3)C^8 zzwMwgS31`l^WRFp72?5%{Kf5{O&NU)Zm#97^(8?sIJIG5R46!h2nP6Y8K3apbJ$gg zMgIKpopiuXS^pQ*pix%28TanyCr9Gb)97A{$>C&OJLUxn2d*K~41w2X*!Yjru>+4v zsL99lbJXrHzHOCFtHID9sii9_*2`*5pl2VQ;00_Dcf*u$ZqVWvY2lHJ==cUZrjhxIFe_j1 zsTCqm2>Lid{cwS%H^?OL`!SBS!&Uhp%*+E;HkZ98 zCI{fiJ3*l%QQIikIFtkqB{C&griIi2XKL~B9kilBc5?yh?Un7#rzV$a!ztV_4Iaj0 zotnTV7wS}nSD!+pjLTPob0fJ^0;rRN`9*j&n5y;S_fBb8HJ$uW=8}$I`cuyh&~q1m zCPKhr@;eFcyyTQ$L3*~Jz>*AY{GTCK;tMRd&OhaOz->eLkIPW!x-5J!=2`PeLvURn zF1&&aU#~g|j1`1#eaR#cWVGT?c|rJEXzov1+fa|gv-$Y;rOZPOuaB1%4aIwp_&IZ7 z-Ftdq9yE+3KR-eh|9?{b(Mf@gCGr2tLPT(mBNvB5ST7YFFKD=uLpS4R+jjTUKq@m z%b$vW1!p9#+8c<~*MtmS)cP`;S21=#2; z3vQ#C(R{8O{(Oj$eZk0=oYM!-X@bTGP%q$C=Aq+fPHP#Qe*^W=_~#yWi%~p~KOv>N z56Z60U>$S*i8+uVxZDB^kHcq{POl&rHp8es!s6$I3*^ktg7hUU8VJKb)2Ju7k8ub; z;hG{@P$)i*kX0(-nEUh_z&3!_x%i-)bN&UheKC%V(l z4JdPzZMjMJ-sb1)Lm|*R?YQU&>KVXx3HZ*2Witi6RwUCzAo&M(+sKi5Ff|MGH$lX5 z%IvPq5xk2$*yzjd4@cW6yjBeUiKo}&F=0FD-2-!LxF0ScN#r~-Fv4DN@*Fs%lkI(= z_68nF!?&vZ#2d7aHGe$^{Q~&Dm3Vs-UGo3~3?XPVocTsRU4pCo1?{#ZHJ9su8ls<* z{ik7vJGRu|5g*#e6xL8#{|sC*R_3jW>bH5zJ7{>8LF%ybFEI~<{7oEVER^08q#bAQ zF)nL4l+;0=XXsHttIY6>Hh)tEZ)@|Gr_s@ef8v0JmMCt;6$Q|y1q!1C$IZxCC&5P` zj}~*!4nkTzR5PN*LL_tH!(-k|g7v?oNiXQ}q5SC!`0XfN{0Uh#V22i@D09pp={iJ^ zJ0A{&2s-kBC!DJ~*s>aUA(mwD@vo>rj~6&#umRt(73)6mpO)hOgSg`sn~ZU$PZ+;Q zptg<#RB&+t@OByZ@;R{A6XVRx*-1kbS=U~6b`th?lj)nXNdlizh)Zi}X@V!IV7-bD>tsb%a6ZToTfPbN-+rs(1f?q=JHVjG5^c%Nbgf+9FK*VW|hmeuNH1jQ{dwF8HS&{6X-zM6lY zh8z53N8eG+?fk!`D4Buv57GG?*|-4`OSnO6VDbm<&>+YO75p7R?iiAgCD5gh$3}u{ zyUZ&X6aLD~4IoXGKg2!RhdU`WTTY?^?u^ZMzVb}9QEM1d>q{(C}@X^@f{#$ zU=`jh^WHj`XvT}L z;ea8$@otQ0;Sbp0pC=$|C2nw1UBNLBuL#}}HUJ$bPel=Z{hVnMN zzdNcd;XjmNiUDuF5EX}E@n1wwP%Faphuo)dmf{vTriQ@&G`Owip4>ymE7LTC=vN^A z_dl8BCQH%p;JfDF@iBbV5lkOSlQrPpD7+zpflPJh2a9f!Fvf=zb9;JVst3oYS?@bQ zIs-;a=(zj1Sjxw|#mIB~j8+`^PnKkeV?yYeQk$oPi1v zA0Xf7!Q^oeJrK^7(PORnTtI_>O*LherdU|WkBq{dHrUvM`$}Mp0+g6@PK#iVFUN}@ z*qd`&3)a)gFb>X%aYF}o*7G;!&Ie#ES-)hXT>lb zusxS}t$?!OTt)-Bb#rdV;a4G9D#1azU>}OP{`Bg2jQqjxK7sE_`P4Lgp-bBjW6oC` zZwQw9WSk?Ur*LsUKz%poxCLxqk=t%Cz!q#&p-~>=72wEz{^Cgt$d%R4!&k%TDNPu^ z30sWd(lYWm17=r|x5MC331=JyVS_lsk#IE@HifZH1l8BU@|*mz9_sv)cL>8DnzVcy zhDKv?A24hLVht|~xgZU)^#eElIlOyE+%2Iw3#v?Dur5xP;X6hL{e!g!WOjxa6TzS6 zF(8%h)duU0;Is`!-6pG6!eJ+lag(02gs2Z(zC@lpLBHAH5su#`(XU^qQaitBAO85n z`(47BHncDgy%O>4kpCx3_aDQQGHzC1vQLX^ngzR`ka7o*nu2*Nrq|Q#k67WspWlr8 zCh~$P1Wh_c1Zad)`hbr%$vO+#NhE$S>`Laufsj3)D>i_)5=g6L3>j>c_(aH$R!*Amo4PdRyb3Pm;Yi(2>tsJ zW0d)wtI@-UKl2WSmNc{%Q{}+;FxdSC!+9`TO2U`G_m7-U0$j}??eEcMHDv5T-*PG% zi3#5H(G^U*$p$zF8h>c;1;Me-_|&WAHx=7S>>0N4}qdp+~uuF^q~s z3<%i#gC!04#0F!nF*=^A_~O|Ve)VwdLwVZ(oY@yQR^yDP@Ye%Iek2bsfyy}U&pPNl z%q87`Z_|l_2v`upy9Q5c(*#qxB1Of`;KNXOM-_jha#fzU3XJ7LKVuB94w z8geG{;Ljn#?#@f};1Xj78sL~ zUay11jogF?n17KAhy(E=a{41yPlx@@Sh|ufus~@e?|B(}DtO4jQY$K}MS22{FM|$s zqJINQc5)ZS65HFv(in`L$+S5Tu@J8QM7^P`1jKl=yuBRio#TJa#UF9B|9ft#@&kwi6Su`c@;wr;6-;%=n?-QvIT|M5r6l^x1&t5!$0$u7PW4jI?-}Ku z;DN>9?+nfAf#jW^2x{^#{5%(3RWJ;sYNt z5ihKy$_*I5k9t(%^HUhC4RMj6W&m1UgpY$3EAHodxL87L#CW>_Ix4W#7hN22;ZHg) z6Lr|zClvqm@YkN>+c$K-Hhf|KX8GWyd+eb9_wtMJ7dsC)~nZD2wlt|SbMmAIv2U|<}XAcTX<;M_hu za12+T!~W;!lCx-C%7<;hVV*Qrg2@MvRp6VGz%UJpN0V+3@DP)rdr z{hcvB6yL0*7F%)Sa5^*F1aDZ7YTtLR0Slck=>O?$B#+eNC=uL*7FY zr3>C!pz{>hpTo_8xN1H1Fu`hOXj+4DVRQ$@(B=4HD5xaDxU+DUv5o_wZV*@U5I$Fv zl%4Rq1Y}*9;fO7fXuq8<+Ji-NXw@q`@Pb#`jeV-=S`{!5!aQ5B3n8zH;NDkayd9Lj zk>{qcb_+2b0y)FrmKV-4WBMC(=;SvcJ*ZEQ2V-s(RsV)76`_bd`Cr1}L*SfFR@{Jb z3%H&?Fx!~;4T0GcVRI>N?4{oBcsqt>XW)((eAR56YfnEsMBfnHZU-;V!KuB#WLWLR zpqWZsSAk+Sx#R-1*CFBtcK=5A3)t#ScMI{ch?;0&>oa=p9e#AeGitDJ0o>TY8WW^D z3^v(w3nr0i*T_H@U~t#ScKnls>{;~eC1q`+R9&jgzU3R=lN(y{d&Rc^z~ zCpdZ=gqMM38tAl;p-o`Uh|TN3YBBk&3H^t{u>E*27@J~I?-*US8}mN#wa%DvfbLWV zh0W;X1?#SW{%2s3jS0T+{R2r(hd)<{$z+JA0PjT9yn-iZF#-b}t%+w2(S|_u-$NfH zhDdxIT z-_!Umn7%7SBMVd+0FaeWd`1%S(cToKp{@q14Sz$Y4 z(=lARIUUPmcQe&7hO&9^qa5=7f+&V*ddToNQ20jPt%Ag%B z{mT8Zeo?Oi)fKa zADLl&e|p9W-FMS4cHVeHy|rNIFJyUF^B3$-4Eb{0`aWcM5=q_w?*D*ozFPak+)Max zF#4~?LF?&=6!->H@F!)2FR>Pu4M7ct8vcytY&@c|!?#ViNb#fHjMSm8oGeg&r{ax({79f{#U`2Gn@ z|01w(`vO#=RJs+Td-ywk1ix;kTfY2cLbO#0#;<*Rj_UBHs?#VN9 zMH@^TASoL06K1&LoGhw4j2Q{3>j^wHn+9g%jBYx63s~L4x+5^mfUs}xcb~|Y!Ww&0 zKNtr0Cn5FdzX*~K;rGSpxCA>SyhkIwwS``LgXT=1sta>+aDgw(PXd)2(0q!}Jow4j zk0)XG2J)v2eNv!`b$lkGx)A-JQN{H*&x!g*;YcUCyb>o)!aN&@4TI9xAW9;gDkRy7 zs4ajFU-Hu!Sk3TW1$*YB^Df+elz!}wi*)F|UHI=Xb&}!%5uTq6jNml82=ppJa1KsZ zlKf22EhkeaGdl^H`vBJoVf}b4HpZY4xc(j87lgAX&^pGuen7Le;aw&&evwffR0EN> zAesrFe3*bM*gBBBA@F+(Wc%T3cN}JcyH`*nE%b1rSMQ^79CcA+uK?7GfgmGzeH}i| zCq^&8T1xKcK#v>gc#o#{p?51*6k=;X6wJnxOYp*Gnqz`iyXp2E{APlq+~MH_xStCu zI;5Z#*4UD%hd^|QY*+#v|KM{G-W&$Qg0Q`e9vz6`3^lWi?fPk-cFd`vj3e|UA0-#S zFB!JwLQF9^BY{50iQ5d&A5U(Vqt#ll*@Z3)-a7&hJf+MX&}K;&Y(SS9>Y@UT(YR+T z9C-m{O|aXL47mjk0i>S?`-#ZDc`)G=G$rE99q2m^^Y+msB{WR@-(s-bi-y0#m~psm z6|jM`_G9oiCfR=>_7<6)1qvI8{cu1rlvQEIOLWjizXiwwz^Ys5g(*0D16>x3&qm?K zvG8d;e9C~l2H5c$))|w>ds!Bg>`H)6CDK-px&y&_H=b2Mo3XexjXLbc@2)iL8pd9v zIXyV@CNk#U7*Ej8f~5zES_ka&BAd3tVN0UjpE>xTeGeKGVE@&a@R`QU!Gi|WXAD*} z(qt*~vEqO=@YN4aG0vw1-X8{+GBTf8u?~_6^PwXYf)lagGNKm-J)rew*m{aCh(gI4 zy8Su|4Ho{f9gihT z@OlyL@Wn;*=&>!hu9F&RgG)K)9)=0EFySkF)+aSr;lf7JiQjLrw9P!CdtdPlf@7Z=6h zbQfg8Q{@df!j(>#g24$?l7$upr-5B8W2KN~-{II~@-Y@%*AX{wsDBMQb@;R| zSg_lDBkdfCo)J`QF1FWFwK6Qw$Hg3Me1#iV!`ocgdASyz|(t;RAJ3Z8iCEA8SSCmi2cLghu+TM8BmBt01>&nHj3;IuXQvjB9W zK~#(dv3S54ZStt@7;LPfPg2pOoSMHxhHLvY25!uPGvQ!3iPYqQZX_uSg)VmERs-YL z%x{IIpYY#${A_|>w_xEuy3HQg;2i^42S45ku1PK_H&4qGC&=E2+>Dt)(=-1$7eeusO^BfZ}5?ZGwhl5V(T`C%}^7 zWY&DxXay}L=(ZW}x}#SX_0`AKuj%bW`1U(pm5iMkI7=5|wlf(txbGro-@vv=Qgah5 zG{}$zkQNFq?fB+4o;Z!qMEGPkzA~eIwej#B`sFG%F2KfC{G|^cHb7}D)J4Nq8^Ua8 zCmCBc3M7Bv=U}+51)i@_r;`q}#vzaBY(HEOL;GLGnLp@DHg`%#*NKq!0REhXO~Hhf zb8nav;Vu|6ocJw+7gnJ14n3kV;|z))vI-|=gwr`oFo&mCuVczFY+(Y{L-6?!WLLsz zFZjNh*d>9+I3n*00}jImh8R7C%P*oC(yEOp_)3Lm(Kv~Q9>nFgxVH;OjD_WE;pPYy zjR3n@BqIgxS(07HLG1(VHG}b;Xq1T>Mz|^znNf6wE)Kg+)3)K!0k|pyKPy7@3{bla zL$>_S54?2<7AKNPnQ*=ba>hfs0d&4X3oA5o!dFPsz3_7+HCcz>YH8M8bi9tnlVBJd zZXAY}6G_->$TKItX>epZ(O@p=?NIj*Cp^dXkMMUXm0IBY19bHibg!mEP9u|LSk<7N z5qLPj^G~qJ3l3Z-ZZAQ>mTBVQPzKCWgOF+*e*+uG;*s^(FPYMnm~@!>yW(0CbnL>% z6u->@=PvkI0duC2r$sPhF*$P!9)E)w>%e9##MhzTg8vWp>?We6iH@bz-WyXDQ7s(r z79uLa;t=TX2+=lV_!S5^PAacMsgQhUonHp%CSVeXlRPn5Ocl4|y*;#Q5&nBjP2#YZ z$w3fb`of_j5Iur$FX4>^d2j=wok>R)xWvJtkuWA7Z@k1$<!70qE^EP!UesfUF*}^T4>OnV3 zUPFDtus5Ab_hQdFoYH~^M#Ih7@Ov!Tc7xg9NM0B`nMUrdh6x{FgDTstV0|RsFhynn zpL>+DypUfd-G2bvrr^&aHdThd>tSdrxJQDX85xxWr}vQQPgx!nd@W$K3e<>EdnEq$ z$L?lvE0^eO=JA+7WLi$9K$Bqy~iON#==?0B&F})Hi zFXF3TG;RVa+@-6hv3VwDM&Y%y|L+!OFNVLa;8X;ak?_TvoNEMU5m|o<4urxgOHjOp zp5^#qBo>CCS|J^?4=Zlc@qxH?4Yq#4)rd`6klF@8SK*`%F+B%N@5tufLMD$h09ONe z`kukA@!2k1mrbh%V=z*-T==YqgASq3CtOUiEda(XfrE?5@>@`4Pud&o;ml4tYYz5SQO*)~l~eEas9=WAGV!Dk*tFr*eeiLIqpL`AF~}PepG27c z5ma`DUuBOysh6cRF$nY~v=_DmwP-uay0?H};#m-p_y@9R3wU?gT$9Dky;x-fJ0#1qyYs~(Id>WN&th^R z!)#DA7!IkJoQAHIB^XJQ{;-2N+0RvC7gDYY=WT^SdNi)hxsjpOC5NoK2xpvsvnO(lwbCu zc@mchil#QI_dx6K7<36+>qzx;(MTiZtwWzHXx#;6f0&ZS+kOa_n**4T$H8I+ufgPB7@C1YwWXT-5bhv7je_eFoOeY@XC!@K`EbT8rROR6W@GlZ zrkijIHKO?huN~*n<~ZLEMT=3Yk^DqKLjx)59(-~!Cj`rfV?cErTSDa=HgI93;p~tn zU$4z3<=J2gtH$!c1+EB0Id6>Ci6v2P_%dhXQQq zHHL?ZE+?RAE2(ZVR#uR9q~rY^d>{3XD*SYoiHV#U$vI*z zBKG5F+puDztOC(&Oi?VpVYv(Q#0_z;F|C91Mq z2R`^FUlacS-*U_4w2k6D4bsVcFBqO<@Kg9zm;63q+b8tfh_ef^s})L(Y?;L$GZ+=f znt8HcIX+Oc{%AT+=g@;x+TcMexNkw14Jc_Yi`}s|acCiSEN23~2Ew!``l8sRMyvLEPXf_eGEYUlQ58_#B zke9XPxZCn*d#*0e$f=B5#$H#rb{GbQV&iqJ-H1-@q+#bVteG@p3x@50T}w2)!5;~9 z?8y&Zm~X>g{TXuS-{)K1(bR+xc^7EGn*qdh;VrX{2)xAcPMy=fTDr2ZRWB zI=y4~KvcckvfL9{@4}CrI3-qeG_YeWq|JmNPPddGc`x2imM-MuTOm%4#u7JJf8?P( z4DqFJA3o^I4^QOgjsBT09*S=^f!_HBqwy0w{{W*2VNYNw!~Y1{F2ZF8EcwQg zYwX;bdUqbZE`O=R?e=sDVcT)E&*K-!F2iIlYyYeYVu5hR85w5(+Gd2EQZr`7W?Q2YVj0 z7!r6Sp5@=mr)o2;5}n8Jn;n18W4pCh+Der&=Sx$-ClKRa{b5>`y3rbuuma16u61SnH6uemg-5T_-nO9GyS z!?GTFd|}TM|6W5+yRdc@b{R}j6Q47aqTDm)uSoQ;$zZHc#H9!5rjo3_Vf-UJT?OkS zNNkUX?J@8G-)2$lF0;SOIYsh;CXDXMe!leC%6+eRs2c80L+`by@D`yru&TDS=n(?4 z|J?^VdO;-qME}Uw5C%9gX&AFK96XYJZCQQ^3s=)a0pGrGUWUyjcvOZ(SHz_hyPjiH zGCl-jRWqzDWZ_PJY|pdx*jdXnN+x!o+W`8<@?H}2y5nvSJc~!w#YnFuc|JlND=9u3 z<1=9I4edu-++oH99`mPL13FBm>8kv`7xVt3*D;>BM{^4dh(pvNnD658BiI>$JosOS z+wy;?*r=bpn9SSDdD@bvY&c6euA4H|jdm0H_9?G@;Y(NS8jM#3IDH6i|4C_|FzYTX z!VxkIiyYva%hWCGKAF3wu-XUtLOWiNW$yrL=JVb!o~eTDsko4a*KctC2?jnytxq_5 z8h=HhOb@6lI$zlbq+o&E0r)Hf>^(bEn1N4`W{Q(ni zVR<$>4aLEVclkb*i4pu7$fh*~aK*EhoaxM%So&o#wmPhaVEO{&oX6{Ue0hkJLi8;~ z(0aU>jh#T9FPtaF`+a%Ii%+WYX>YEvQo+|!;=>{{41-rz|V!)IRi^_k#!Ss3Q6RI zXI+FxQ8BV@~_Pd(-%8AqRJ-*rP>!mtQ+`&{Y17=b!gXEaOw5Oq>VhJp`xXatV&4 z!1js|dLUQeJ*5n}N-_N#SC4Sv$wu7UoxMHTJ&J-<$=5Nl9s;~DG8MlL3MoBYKcHn2 z%56czURco$H_x%?Ea!$YaxT{^{vGY_ZFpt^i<8*nJX0NzUJn-z3djwXcM-N1dpbbFEB-!4{t=`f zf*^3DP@F%-fOiZXMPoeuOmg|2{92t}{dsvKeFgL13O9wFD-k1SA^Ij7t-*Gc^h3O8 zc4Nr|Y_EWH6Pqn$aX4pmWnmv-&)~z}%pc78p`8Div46N`45CKj)LY!SEpo@GaTQw+ z!ZQ(%Tu`U6Xl3Q^>$Gdhh{=5MP9D>iBYSh|LMB}1)Z5gIgD4j1lM0)qNc(}%i@0(L zPd7s7OdZ?f&;gD(#q9R%=}ujHW_0KN;jHIQ#ThQjVrDmpbhi2#zKVul1J>UcQ8L^- ziaP^g6NuT5*j8r3XnLgl>(oed;^Dfi>CK>(9KMc0)lk_2L*p=hDr_I%$WDk4)Z+{A zNP%den`#GbWi$=^SF3cqBR3A>{LZ}R%J3m9H;D;?7d79mw(8&YoVMtgE_b2%8A~t=5 zV;W|L!^<7U5^l_4WC*8^<>8?my^yj8XEwsYR(g9L z_0D75EL`)(CJ!#sB6qIBEHv}ma|x62RlCFjvA;k6w!x~aT@>K#scx5 z^+b%XQ7ceXuESL+mI$JAlMCkY)Gv8Jcb;R8heuG8Q%84D~V&!f1c z$G**In2w=+F|Hvp-|*FHihf3Mlh5~JqF%Q4VE$0XC(_~^j~S^5$KWM6dKDR2*!%-s z4+q_}ZRR`>?44JNvLs4LV2jzfCMIq-XViRZuAz@K}ct<)z6d zfVW7T3zzjUHiO+u4oGA4Lb@zqqLO}|yiuOH{!E_1)}o`c3jVgj&=hRjh@3Zgu@jAL zq-mMBv;(_`qGw&C7+61m_m(rOF8_|=OBKB)F{2A7#c|3r&a%PH5%}hbCVw#|3o)fQ zos6cJ|5a2RaKUwjFjb0@U$zr5ikaM3AX{}|&o->Ln&UHg=LtPWqRkL&JcaiQVc$?P z9K)=?qCyTgW<%$KIk(yOBQLdLYzRFxeB)0?1!JdhP826xrj-J9R2Z9sdZB1kL28kS zd#})V1r)20GX@9AlY%Xr$GHdTT`VW`;plR#IGx24IA8oV{=+M!ys!v^M&fx1!Zu-c zO{s4t3^`cqi}{`Kr4d#v;MPQ{oAJp^ZqPDgBxkqfk9oXsg5@6a-Bg@%#!LhLW;({V3VpZXWDnT)#KR1>{L1J`96g6tALQ2k*}ol=13CUMfA691AWUh5n#b^VvaqB| zUC*Oq8M^L8y(qj3g6DNwe<%8Kqi8Ux&z*iOQqU|y5R+MN19fJ$_`=+DWQ5|sBG~W1 zmS^}c6Kl6b?rhpEDDy^uYRuwR+Hs2`tMFxZ=e$MH!R<*&T(jg!W~wi`A*#EL!t zvKv2c;mS$OT8qoh5Qo<}iGz+aMs!pyWQY?YF0A#KGy&}!!tNU4}jMh#3f+a zT}(+r%2h01go@)KH-rBTdS!8CYbr<4tquoGqhAAVUdg_(Onb*=TJ&|nyj@rnkC%{y zyJoPUuVb)gD>9owjF&HE^GgDkEdJNt(rFNFEAZ?H(w`Xzn4yNUIhu-4RTQq};m#^l zH6kG%57IH+7l#_+t(k_o92L(+_1Hg+@U{*i!%~I%dPx!pGjTOuRTikAZ99>SV;bB zaNB^_QLu}`3bDH>r0Z)g=u3~KY;MVy3&oXK-oKm<<7xkp>z;9Jb!4rE)dDOp!Jea7 z{vM6j;9WX448?#3u=&ZS>8zH;DV3Q%is|JUFqXFlaG>Z}zQRg{oG=pi`y%oNW`v@! znsi`03hp6sI<}8PcuN$gvThFVbYitwTK$!mhH;Sv`>bMd1ZO>GQXN?T2eG)^xdS_2 z!fPjDzhL}UlrF>+H+1^JrG@kv#%ZhAUCDhOOl`?p{#1pF=n!+OKqc~P%W-0WaAQa{ z6EU3-yP=2Mq4dKN1-wk05y2seY}$(As5%tNHwV(UJAZHF-_i0++O7|g~e*f5{YE-*Ag;3XUuf%BTu z-ZLnCgf&ZHH4)vqKp^NJuJF_>W-enG*vO6ZD|6ulHeSp#S=^$=T6+v$hjQDHas{1E z<6<$o<>H6<6EPXp>Lal_ibB~+5SZ2ZxI4SF`S(ftKAM7&_1y2GDmPr^|A4r+?GMmUFp(FAv_~rGa=8g04c2osB1V zF!&boA7N`MJ}yAaP@Me8Ga?=1$)kfA)`g2aC>Atx#&UcRBTiBJ#?20>zZOr{;P)rI zK7|gC(P%4*cVI(bY^jCHmsvA|hlL%h37fdku?oL<@Rbi|#xcK;_v#{k3Yx9OofPaj z0&NKn+=Su~TvuXsV`MjjPc+9T(X|V6$8lV%f6J`hZv4EEx3gJrpNHJgwFS!Zk+d28 zze2em))(L(iE5)^s4A!=hF@UaU@i#a6D?!gb6b0=eCV;B(h1tu!a!GS3BiLE81Mjn z^5Fdx$BrN@1Sf)^`^kfV*g$n&#`zrt!bo!ip7diyQE|ANkBYfpR0*%dU0*zTj)){w zzle3~(0vnjkA}JdP6CaNP<@#9TJXh4raSTVa9#`K%?NJ4$2%W*u@8n1LdUJJSc%#1 zFeVdw_u%RqPkEK|2 z24{A{UDT&8#R6x6zn>c(ugOBrm2L{bx-vY9yZqG4375+@pvNMb`OO@YiZH z!ZMI9Ji|*6x*iSxLyjA2-DLc04w%aB;#Ja)OGmM5D3ik3>O5Z_VTlXIx5bdTcs3K` zGf^%{I5v@!h@ye0H3~lFSeM>*WKA_ADgV<^76R+mSIa#FbM9s zq7EBhcA@8f2tjL~C}>qMmcxfVympKGy3lhPB^TE5q0J)hUCzZ{+4={s^n?2lwA_S~ z%dz_*-kd;_)z~=?pX%a78R)iB#O8us5uBLY_6`6g=68 zss~V%iG8b)vE2f(oo943Sc6x~SqbGyLa=+snO`(V8F!z`63Lr3S8thAnvqggzat$T3G1Ug1BWD5_AD$o18?~j+_rgsd7 zHet&abln1{h4>i;x5ocA9ckxielD1Nww_PbL=Fh1M;yn-u)|{p<}<$?vfE)`6lO-j z;V}B_#l!^|wG3~&VnBD%RKjLw8M%%%!g+fZ9Y^u@6!x0LAt$KcL#GDlAYMc3P&o>l zmcnTh-mZXX9}wfs9^R<+jXoN*h~b@md^dt`0=OxXmFMuw5#HU$$l8dqLI*FQ=*Q_z z*t-GsR-^x3O!0(n5rmLucnyr+$w$ZOx`A<#94mMO!Dj4aXK_M{x#bFHdth%n#LUB( z2-wEp;s(5!kAQ_(SP%D_;_pkge){iO(JzcYV%TgluWw;T(F^vD&X3u{5itT@nhKwJ zc)STMvaom<)~$zYP4pjz{PWEJMvElQPvcCns-8jPYJMBXjW3w7kwOiT`<~wOF@GQy zgk#Q3bd1E(C8*+tALFps0)e&AB9o3M84|T0kGsg`L5*J&;@xDnGR8 zi#egF83@%3*eyjy12h?kjju)E6ml}V?5F7vYbNk#0#%FI>^3bkdEFA_bu8|Qs)OL_ z1D{PeH5g6T;l3LV2jIA&n2+Zc1C?;r@OSNgUu!m^~1H% zu=Yb#2m;3;PrM&%Vn;tg7EvUcg(9i?9p0PAqy>!MN9$NVyT!zvY*`LNKC`GZ9*C0? zjgaw>!q9jgDt5sOZ@6fncfk7d?0S>GlIWGmsY{u)lGD#IH4$=rm@THb zK`7@7n=o`4fZ;)+jT*wY9GcHtRv5XS{dRNkelA|hh;yQvgl9f5CWo2!a4~SV2NtzQ zTR%Z>qKYeO&Ow!W7%Y0?Oq{6axCBNXrtdR$Nv5ScyH2G_yje=P!p5w$XZSUGEJE{Z zQ02l)r}UaHC2KY1HcB@|{o-DVuxk2Vwpe6bds=S0#&pAzhP1L1-^?yoj9a{gM+JMU zVdiXgu2~eOYrE^vOs_e+A8jvdt!qeIj8gs2R3^(SDOiQlksD;cy1E_~(3R@f2cYc@ zfRU(uiR;qk z&Y#)Q-zefd?$-_04RLgmJY}3TI8&i%rHJ^-VcA&qObU>(xu06~^yTi$>32<=vZy;K7RRWwLfvr# zjvr8%e#n!`sXqu$QJVVNeY*Nd5qA8v(d3#dYo_RbTeEDZ>F*g_mW}Go*qzF=zLI{X zw9Q$XF-cQf{5-=oeHusu$|)~eVcBjT4(EwV<^!|%&|vcW!u2i6j>gK}>KL!(v(<02 zgNfhHDLEzd!=;ybs$YVuA7q&=EIsQrE#0`OwX*&@ zd7ZV%zQEjRwXTzgJlWb9{F9b9bYhBfYM#8?Rx0c&CDfFBDq31cDQ+HCe@(>437WFk zIMGEJ+Zwu9^QMvPI>}gent@)WO$>6S-iA2QlzPQ5{|DV0D<(TjMI)7NEwQk!)?)-T z_bo2lW5rqJ{3CMn8Z>m0r%8s15z1bGz|ADNIm?d zqFxA?sT?O$m0=N*3)d7?%L*J|E%je4x9?;sFqzvYnr(eW3xHwqV>u<-;2y*F@60aq zg>P1|LUaZ8)wVB?R^L^nuEEO&8pzAfDlbEN5bI1=HP_0#IJ{qHiTd8}e#+J6&y zGvzvuQT`zh1wyex(mCMuUCr}nXj4z!Af5$n6@|BCHjqC$$%hi;fSPQ*$ULtzPwNc* ze#;kfjOkl=_M0p+iK86Unsn(-wla1VW^0tgEL;sG_Ry9`<2!(~~q@)F-)e zdOhQUvGDzAO1TBkBy-GKbX+a}=pv^53h6ZV^;3^tfWIMXuP1yc1W*Iym2c(Xh4MG? zYHr4PUCeXJM5M(q;=F7+WKv$Dg^eP`PEsCI2;oCxVIBDo1D}XGGrZZZj{+ z!>O12`2jPJ8i#$M)@Y26K&p>vc_Kdh8yi+bi~Dl^8eCT?Q=XtpsCJ7aoqMdPu9G9C zOIBCqw3SkyuB=cZ_bHaIywY!PC^y}#Pkkx3dM#Io!55!@!3wuyihXM^Qm^>thPLUN z-F2k#Csn!-QHLsNo1oku)2%q}$~4q1rm}@uXD@BMrT})3_6Xa(=}Dr zO4F{YQeSiH8Rgs_G&fN>M{unxnqHOf4K?Kl%O0o8rc{%MZ#2%o#deKNBTlmUFX`h8 zgnm|}4#2v1n!xYq*U;k5dmO5u>iC-tjeL~Bop<#jUa|9)si{i39;oYG!V@zL!J9E~ zxLm)Sl+j3aaFcY`ptigMpOz}^8b&KLVMdPl3;P`T#2|X9IS9J#x#k8@`ly=x>1xZ zTivgj{fW5;R6*)616&&+?Vq@*^QCPNgTJ=8z<;<>dpa~Xd3RByY;(HkVx z<&^`*YQ1Hb^QG;7m_wtCHh=gbQ{Tps`dC(LIEdF=>zL=YFksM!}JldzINz*P$dLaxdw|D z1<2W=7R?>o4lCC#V%#(t1#-}LIlTp%eP({nSPOq9~!OLc!L!Ujka zN+jDpkhf|+J4yD7RQDb5-Cc?cWlpBib%}g7&M0|sVtZr4RPIq2R}Ew4O>=2J*9PL< z2Uf_|)c%O6;hHiFgjlIKo=c*oZ5{Z`M@lqsKy`DQg{&B+dt8fa9vRXWV0vG+St6wb zOWVdtQPJ?6hV0di`T-g$$-V5@NMjC|OOv}< zdcya049Bb(lxEn`hci_8H4*ueGOjNiC#k-Q_d*+`!IjqO>K)}V*iBimnfSxezTET0 zU}um&QGX+wu7T#N)>33o^TBL{8Dz@`=-gWIx{0J6W-+U(Vv3}89FMIYl9)hVw4`A^ zTWm7dN@26LhP$mfu&nIdW4Wis{GvN{w`TqFc-u=obJj_-dy%~8lCE8r>~O}UJ&CacjroJcn8n=a9)5PlR2lD7T8kPAOxNg&@GeK$ z#=xpNML~W1cxx76mYWu)tSBx%T3S0n-ZaS|&Xms|Wi?v=WVw6t$)>vXZ_Hu#ri1|^i*3j>v-t$mr$pF)V};t%(0wYObm_)@ z?TVe!>aMC)_Lz8CwI>>32h3-_%O|gxgNv9NtNZ&)&N;1Xw}ji;83&sgpUUgY5z|rq zO%Ly@N}naPoMXBDIEpT*w%D`xQ%Mqz%3p@-w(|1bhFJsneV6{Ocwny5H!PrhlhNK% zMkmGh>Qd-giyUjkqX2a+;mfL}DM^K0G?oW)jl107BFi^2c#h{a-_lRATo7q|myEd; z%u%8fvrKVBAst9oSm$tAkj3H?FkjH5?_w`14hZp1hHO|SLQjUSGvzs5jea}n7_MvE zo&!V7cBi24!K`&?+fnKE8h+Sy>w_U1?^!r$rs7MaN)=HZ8^KoLQ)~h*EZY!G@ciG-s7&bQ@{AUfH@5y0~caf1@}_QMU=++i-!f zAnw;i$+EqZ{+$BvGYm&2!6(>UV;7E;;nx{Byipk4;NL-eC>?p;ntAs)K2LGfC`US) za~jIwN6Zt3QCntg;!U}yev?F9MN_5%t^K4twbVn|B`PhOAal2R(mB)*QY{X` zl|U)c3R8?Gw|MS*ueaIC?M?=JLBbC-)k{FlmU8Y|xTW$zTlgJTReXT3gW3k!SQDx$ z9L09$*?Fd1KZm(p`Rk&2We4WR=sg6Yc|kwFljs|g1>xU&s^ZcIbb6sI5zpB5D)B0O z^INrkAv*U}c`k(GY`p!={dvYek?gr&zvD97tT#ox#I69-;ZW@TY^rOAV1H@gDSZE_ zE>%i1nrbv1@#Bm1pioXKk?PK1_ZncBV;rcALxn|rJ2YtBFXA$Jh1`FWq zChq%zLoXG1udq$h2CAj|dzDes*dtJ>h~kjX_}PG0RD9Q*s!N8i8{~Q2b+IeuPxZ}{ z8zIw;f7+t-hazS(G71z*1r`Nc6poQ}chv31phu3RJ|$uQm?JA2N9rhJ-1Ak@BMo1(RGxjY;-|&mQZeUI&Dh1W z`&Gk)65)#}vPjNqWNKmN<0ty2YV@(vbrVjE4kqPO44s7~tt5xRn#3$=T)cAVd&V5r zR(OmL166&Faa~0$uED?VhWp-f%{RI!Z{_Ll3@5VLb)3PvFB1nyiKpS&OX2*7ZFgAw zu#@)J)-HEI_ZOaK(uyd4rQz`XzRJmqJ zEq5taQQ!ge!~9xh)`c zoimG=@K?R@66)-<(2KFZz1ly9<$RRY(s=ei29ILWD)WI7c3EAPS&5EW`bLwH_|g<< z#(^f1v%hqApt6r7{TZ+N77xW~&FcI3Q>GNu_tL%CHIr}J8d^=1OD&C6B6zF6QT&_l zdmHD3a@A?``xDGLs`x$$cGtCQK7zB=oIR`C-7?j;kS9J|X#5VP(GA>lAV>9^Ugfcmuwc@3uQ^Y!X z_#u&qG@KLRo!h#sZG3KFE_jP8R!FNLX*#3!2=R)M1r zGtBSA9I~EiTZU+mF?@>=9$kHWM_%l1K2XFuO6ja4!rj!@0#LKDM%J^=1LcN3d{>~< zJz~EBQgR;m^fpJ$N1u@lEzB^X5R7h>|@NS`+t)*!e+NYLM z=vqbIN&2>wlzMtLFpm`Xt}Nq{y*xEnzucA1-}Uk1`LeBizY1RXDP|7ErBIDF2Jsh_ zW81S?fX388a0|*~*Z)Cl5yQ!M`f7LN5{1EVn?28&{Ewr=V7y!b?E>*dpkEgiGtQyW ze(l($(zbf)JKo5wqRg+2BNaJvhk(}1$_G@RGHhDMQ|-&bW9!UbK!ho_8P?#sKLl~u@+Q-X{_z;Y(sD4Zn93wZJg zPDzR~-a!!8^9=dfB`^HDR7mHv05ItGlaUJItDQ1gA-YDayRq}&5 z`riH6wVp9xI3l;`M+u0cr@1H=6>=ou5bkr?B3YsEyP%E~^XDp>T}i0@M)9UDoO{dl zcQS6M;k+NmcQ2jxTHZfa|KL4cHySGlBB+Ma?HLljC~b21(pJ0W7eAG=Jk=a)45}m# zj<_RtNR*ow=yxZ})pr@ccfhd#UECV(u4I(P;%y5$zQh23b?+V0>rH(<-rFXj;Qc zeGreVv}fjX%uvPhH*#hhbB}{UXKGB1;^h>b^$^wy)U{3}UYmaJh2L{2sk#(r(3H%O zI&apPV&J||b0-?f8WY@P7zAIG?o0|rqa%P06Poi_0Blk!z^ab--^lvoQci4I;~)6YG^+K*6)mi z^(^lo`HdIvGL?U0G<|FFGz9ZrYc@tQN~sK2@Oe3AEBWJ(Y4vhm%GcK(#c5|tBRUP2c(MsJ$@B^+E6>X$%(V9vZFmD!0Z*$8vqw`_bNGzKaPVZX! zW1iT2Qs2|a%bxO&Y2tiItbsCHZRh>)UuF3)LU6gNRfY1>WV&3OW8E^+0vK#{4r_RWy}bLnM)b zjr?LBG=LlG>Mk#q(*nw7T65MCQ}rOsSSv4R0nZVt;u54>Qq^n?r_C0{mXe{7CSw8) z7Ae}dNAymU=uIAI&Begqe2nNn&r+psvb^qIe0zmMU_FAh&J#=qgIWJxTY_jxJH^+RWq=J^@? z9A{}!Lz>n__38!v1<-z(mLm*VooRdDxHkiVx5^w2k#YL5wc)ImvlGy4q_XNaw5qFZ ze-i^bs6TC_We?3>nTHA`KPS3~lE?MDJx7-~RQ9Q%8|A>t7YqYF^Wj-@t>!SNNqK&V zw@|sahwmt>a&x5q-86*~=6zBI*kf;HdHzD?EHm~o@XK%A{Zh`{TV`3n5g&~Y4*oN( zC9J}X)|#tBrNkxbG!wH2X!Ip?x~AH*pN+ms$70ze*_e|qd!8wKzFWT8#rVB80v8yZ zmax+Xxl(;xj+ZuygjXAj^^GML3yZ&@SYDv6>wwS46*=YMQ_DPeGnel({_KNmL(6LK zVyBV1XTr0z(^z{3PB&0qnk4lcsO}Vp;MwX51q{2O4feu;4XV7|9B0qSGWlJ7{WPI) zoNU;a$5##XF=EoY(0J??gX?455DXOAl^KX=Y;hzLOa5xUmxq;?dSOjGyU)Mj9QW0H zzbO`-&@X$)jQrC5+nM5G(2J~bC~a#>OFk>yZs504<<V3iWLx7~WkLO``?3l*Q|@Xtl+_C*nY9 zdhKN37j-qDqCs)Bgj*ZQy|&X?q2JV-p+n2uQ~9U4G4B>Gt~R&a0o^%i{uN={P{jmb z?pv+#74~{*JtyMjOvT|D3`{kb+Vl4(!^d$ns*QGkcqX>&d69fF!f2PzhyY9;j1NE6 z!RIhzzb4QUMV^|y1uXflKG+qX1lB8(6hqBF&oOF#nQY0&?{#){@b-Xl_Ez+q#74JZ zUqNwY3CfPE>zu{hC6*stB>NhwW2@QdC>92@d9G>wFn)h!y#19aYs(Ir_c%8)xY(PC3ZerR^(4bjIru@ zbf{+X9t00>&RHN_Op2h57?fi<<&1Q(hbFBm-gi_EOyjx=NJ$VYdy@zPM;+B)pTU~- z%ie1lRc7?)fuIj&!En9^QrEMQN)D)3%RJRilbFxv|7prwL3vk^cZ_|{nPXj9*HBjV ztbA&t@mT`8H#Cekam)|XdOx&_hfNax9?*EslwJmC-(5pcj%v|223}OUw?u4_xrPhF zrkm2Q@UL Rsgs(Se#`yVxC7P9+psoF8D(yKazAmzAfN<1GGSj38N&(Eroom_Df zJ;pJsqMyZD&8O~S6`aAN+!?=;7D1RR|1=?f7rN1B5wOZhiue!K9 z`piP*X`FsmcKFUX2g9z>tZY-JtwQe&hC$U)ZN2$I3!J>H7_<%b!<3s&i7m55a2k4; zG=`5%c2}O8$@}BwzO}f0tznWgfA=s<`OXc6x)tZC*<|t+cI(xg5RGdKHMWt`xhI;Y z$+#V(eqx7Dg~~7soXf_H7hJW~G(Ulf?@Gmw`t-QoSPkKU#&T9r1hSVWLRKnF_IOu8 z6YYw!o)({nVeK-te+td@6!W9F{kHM&L)ppMxa=mEPSKw{%W>=VbzjixjA>Axe|w6o zj?!9%_H`wxYP?z)doJzJ>{*5_E=ou8>~^`;R@Q5!-)hg9^L2s@aeJ+IwSuaFDOQWc zBPIPZ$cL4o6;bDaw$dzEY}MwL$33m8Tqw(B%Bg?kAA3xt8)&jK{(iw%5oJkj7_r?T zJcL?reE5U5?^IJNO1iGlqLXZWJxD5;e#mW8P!g~2k?^2cSQ#j5KquEBB(9KC3pN_SAmFHxRwZp4d^vN^`UhdCG zUC{#0+-#WGUpQ%v^UfaR(2k3Ou(7g(uWR?c(k+;q=4(Nwbv(1fL;rwo?{YtowQ+{iX zzDF#owU^>6YM0J|XG3M@xAecq5nos~)G*(hUt1cy8-RYg3}GE9G7cJp03UgT*lJZ* z|J6zjYH6LKamAn#@785b>bAmy8!HK*H#yYo+>L*_lnt67D-IjVX|QgUv0fn~M$27m z!Edsn_8hcpt66msX96uwe#Pq>sy=4n&{OLB!Mca3cTfHpV?1bwO*QmR1>Ck&SD3+z z%}v`|B3h&Hxq}X$G%bH4C{Hyynqo6Jrz4Dxs>oBc6Y_TPa{r|-J1sY@uD6>`uTaDO zUwp8@c<&Ky|Ke>r=6_Y(bAn%nwq6m=Ki4>p#HTx|Ce85TG_3@;_DoDR=-_0m(inx4 z%63fT!z{y_zG$Bs_Y`4X{049 zID3Tg)FZilWy6QDYN>fq4Ney;>deNyIIYJIyuPa5QpVWJs@v62 z`@=uiceBgp$V>blXb8#X{zbY;#{?W@=;Vd;_vVC|aM`S=c^*cn^vzMG)-33WCNDM3 z#$%?~(X6A@0kcmz_Sj<(5!73Aj1z&-Nc}c%)>4@2-R9=QsNsh=svPzpbgibnH$KLz z-y8U2oa%KW?7hp}J#4ec+|JCV{`#y{oP0v(B3|G1O<~K>3;3Ch3xIPvA&^X0f+pR4|&7$gmktgn*3Dn z*orBa)$^>OY^>H*!?-ZTs}i0(#){Ucm|?tmiVe!^awoI$5@XLKTw7^|V25m>8;b`^ z6rVfd&nr#K0?0!(JGWjUC3mKE}6#L5kJe35&=WQJk6K6BOjlo-_u(ORv zC!@88$+a!&NmwdUBoCy_;iy|qv#}VRkEyRuLQr+(vpa-@3afefsM&TORh>**uaHH$ z$nqS~#niiu{cg$%OVmH7FpkIVpUM_SmK&q~U0p;A)pHzBm?7G!*b%S<~w0I*bCaaH|kYT5qR0a8al^q?h{i;;% z2U{PP&x)l(Ws}P~n#UN1FXYV%rcr;y%E{dHCEH|66=tK_Ls89yHtST+Dx-gQwOv!} ze5@FJjj26&CW24Ln-q(f{mxXcJ-*gA*0{^4WK+AQ2z|=Wu_D}|6o%Cqom4(!5%*TP zv$jZjDo?kEe?PQ7#FskoHQ^*)6;`HPZgs$i3IILRIsyUmTTgtoEAqqmB08`NmW37KDks6=s z!BM07(*Z`ka#Cj;u~uvo7_%1MMU0(hdMNTc0S4bl9++*a>j*qHPq)D5WBA$$)hUVo zzy6<9?gNnIp{nwdSM3$;68K~uU#{dIdvm;SZk#rW#PNTZjZu&JDBirGJF5L=!bE(@ zR{GsWb{&=1cns>PG}VH%SMk6F(PLl>G|V;UJZ6)lhQfs$QqFXy8md(>uP)=;fgC3~ z5{2c`%uxfCp9&7Md%6u2{l^&fM4x=FZhBGwzHp6AnO(;_o> ztu;@6LFaVXwu1L1DepPo2dHXvMC1}x8#PJ?D}EfLwHdeF-KYBPA#J* zjsCUG_d8=u2LFWN*~I@TIuE}bzc>uv=S>ny6D1l5AreVLMroKKS=oF3?7jEid#|Rw z$%x8`l&q2lLds~MsQ28z(;vV`uioc5=li|y>(bcoLS-wRN?y<}(a8yf=Q17d&Uk(m z1utlsAk8yaw7o2GCh@LpX9hzf%S*m+b)Fn!h*jY@`GxLE6Rn5e-E`}BM;%L@AA8gp zSbRH1huTcsN}qq)O&>Y+ZrPDgn)sJb5qLa9z8Q^fV=#NVI(29=_M^Z@$1@N=YgP)F zscNmFm*lD5m{SQ&&9rUzu=sk}kw|U|D-ScoX_Md?Ds1^! zr)nHG9niEn#+OMjIK(DP<>6i2ol>51gZXQTdt;Z2F^I8RB|{lrPBV#sM;@3G$}koek1+ zG?UuksC^~9dg%B=V|Rk5n`+XovRV*LM{;O8xiW^iFU!`fVe-myMI(`o zxI0W!wi3gQbiQ~ac2p%UMb%HDRzCYyh1oYY>7f07g5^o&mHsH;Te)dD<*)MpBrl$& zQ6HrC5*8NtsavVxU|epaGv*QN9nzHCpv4WiuV9y+RD82{8m-}Wu81ldx{X7wYJa=n ze02tUq1|v%cLi?SXv|y{(Y;c{Iy7vm6J(F;&BcYE6|BzQnf&&q{O);9&n|CZjIhz= zPrh^UQfbi@zy5HxC1!lk98x{6BRaww^JZ!;M)1UIvHl$Sj}2FFeu%_zdMd`uSemWT zRAt`-8GYcvBq}IflTW7YHB=V>IfLD z=hF)^YYS&RE?436|MqKlmT(%iW#ydwk-r1+JOf=@A~sy3=p|-bbQbo*$pT@Y&%66^ z0hPVSu65Jc7Lq%(FApZ)Wt>cCR)EUyz@cZ>mTn#%FBkZc0m&-beJv%HBseR9o1vnwOO-H z`6e!iPdd;`V`LVMmrC6d&Rnc*tZa1A<(B2_T34>?j?=Rl&|THiMUy#rR#j7Mj5nh+ zhYR?srAD3Y_dY{xDDTC}B{O)!UAtr|7wXF&1sr};J4KZv>}XsIr&qzWA-*2gJRON- z3!PS82=>>c0M7=BBO0`GqsdF&n=7xI;Ye!v5ru~MSH9dBPX zI`{n58i?2&vZ*sJm&zp`c7 zK~DqQI}EHT-!Edv8(OpboHti%^O`Np z<=4+DMZ>lxSh!SNZU#q3or6QLy@{qyHn$sSk|md{L(>f2E|<$b^IlKw!(?tQFV_}v z_Y4_32*-mJG!?rIMD{d{h!F4WaWGAz=~7!+3KU*vtA`ww3|=S$yz#> z@oOjT1m(K@Dy>ZrJrc(CP%>H**B_k+X%frX*-7& z`a8_Mr7dlx=y@`12!?gS+^xtk6!&}JRb$N#S8OfN+;_v|M&in4)=uZOJqmsJw|NMqns=UgYf0 z{BuP6=o{|J99G>{mK;0$=BYH4w`U{)Z(Cw&m@IQ>s6%8|}@L78zS+x^w zc2j?%92dh|QQCp8IDd$CMh3@QbD9Sh0SD{Ay@$qJ?N1#vqJW1gYpfrz<9}kfYWAs0 z)Kj)RD}7>l-dtN4&3b{d|8Q)WEx!%K-fe6)9(rfs;D@!FHF=|OeVL}f03&9LU(uWw zr9_sz@=i|M&9XjH?>+ZgX^%glEYP+#N2FHXu|bczVs(G?veLY6tQZtxQ5^HOYC?+` z<)(W2OgzKZmpEpZwseQ`C27m*;I*Zk>V?RQa!vpa8o_orhD;HPh1fDi<2M+7RWv76 zv3I0slg`D5X>*kAz{i=~m!fU`hIzKyq({^pF5_$CaV(2k!h4z6=!J6;BIq5jD3=+K zRa=w%jH*}~8P2>P(&i*x?rOWov1h7wk`+e&);4W{nFF|P3|h{|-yq~n(gZBQtu>mo zHkfuvJo&);cafT?46v-KQo$2t$2Zi^DzAQm-SV|ThPYZPgB)Nw9(|hQ{34NTh=8sd z@4wu0MN^BMKS!uWSdKj&++>Nd+`NN(UTAw2^4$|{x4I~Glcfz1v5bSgV6|23odew$ zniaFr&rnlT#_(t2OBq%3t4TTo^W?xM96d~HtIqZT+Lf7{*-4hzz{Q38Eiu1^xZOoz z9))7GxLwq2u7bbaGB(}912)>= zaBoC47h4+On!RRRHH>JcV`q=zZ9-+na*VNS1=k&yi`UU@u#Ec7?;W&G>eKt9ZB-Ar zN8c&fZ6qd5#6@S#$`G7&(5Rbc2QN*IK9=vq`5P4_&}pji8m+Bzp5NMQPrXyw206tO zd$Q%cwkR5p7=Kj*5o#9Qyq!+%ZfI|#F}cSouVAu~mrv7vCr_=CioH<%jMgKS&Dv=H zc>G6b*1@5grs721S>q*nCqYrLSm2K@F}!hBs`S*R#@gOzxxRtC zr~Fy=GOq=`P2_oBIK|fd@)NdPGgt0 z+SgBL@k4u28N0gBrx(I1i;{Wp{URcLkw`ICGVz|KMPs;?qR&GfU%{k!7Ch0GU1vbL z*2ot6S=#Z=7&1cUx5m`QunxfrXVJ1T_9tsXoN&xY6LFQ%{GAf##k8ZYUA$E&d&c zkW}t&FNa>?ryttRHIaW%ySz0nf6%Iyx4#FCdf|(^7&Q`aqBUyk^!}3wI?aHe;^7s3 zNW-=;ItFvc3I183-M)`SW3}@O+0b3)cff58>$%}`8UhF4?+)>w84O}IOX?!3r$&6@ zC_j;&!2RR-UZK2u$PHiEK~HXX!y|X?q1sqKTUrETkU8d##)bq@I|zpiG=qNdVmr;< z2fX)Gl&YPGDQ;}wg)_2NC^H6Vw_ad*inc)ci~GvB7O4M<*1d4?GnzV}z+a;_zuC~~ zZi8C~#NM-PHx4t^8TGG>j^&Gy(!UlQx@+fFQJWyGn+NKKFl81zqtSP;A|7l0c*4m* zv*ryKIB3+pxEsXSql~;!0e+wTLHlnDM@`YHOmfdAQZYR)U6U5hnAHlqJK@M`k?Vjm zjZSYLEV0wLC2~YhvHTj<&-E^v^LNNDk67)#c8uhYURs4$`mtTcj>my$o*RjME5(-C zN>nErnP9P_=3oYmOf{`jsM>Syw{cvIT(gL+)@xgAXKouAY>Px?j;M|ks+ZIjwyO8) zk7#$z#GW{^U884)%W*>gJ}cV_^)y@U!MR7dez#2j!)8;oOA45OLffM$?obYz0=q*9 z--yJ+LQU8@B?!G@7KUo7S3|I~Fg(ThlXO&;Lr3{=C6`u`i@!2ILF-oqZ+xVmE%rJf zawvv27Ij;wJb)&u4ffU13`nB>74iB$$9UuW0dDf6+I&qkmA`Y@)>?bMkS3dC=rq{v zWwXUVD(X$a=s}`xO^iLFx!43DRWwnrsSr}56_2Ge+wS6ZOX+cj&CKQgN{Y>;-B<-b zU3j%OIyhjyGaP;i;~-2PuGwOMtVrR1fu>ExrqkT>l`qfI_`AwZQxl~9^N>I7Wmo{- z=1bQp@czeLGq77X3h+fnO~`SQQ?rS z+FA~8iX_GK>ki`*wC;y^cg-|U3=R`dlw~Gcl-y&|eb^r2`Ynu&U}6pFaEYbrPEif# zmdbadVBC-@jj&*;I;tV6p?K|%)mG1uA7XVlvk*r^9La_ZB||NUGW0 z4sGWm`aP31JE*rmeY?YTjo8owA0CLjV!l)g?qvR3F9u$wg(p_*=RYU*k73(1ZKEW* z)sluSVY^GV8>yb3^jU`bvymH!HnAea8{0=}D%C}5Zy`^!^Em9=#cIJkAHmk=Wy%M7 z9M(p@SL9)Nu^G7}Ygl&SkD0A^$~k zeug%rh!g&kGY7!njXV;9lo)>Mjb^4|Yydt?(ERqpyL2%tjm3^)bQIrqhTnO$N#vVo zHg6~wy=UkZtrgXJU3T|D;0We)!hSumF9=VLifUCb^oQt~NR!W^_%1!3rja6+)JYC68IjNQ{hQ$qBAJ z!rw{qOd>D5)ee8fM2$2wgenlx7rUJBHV6srMU)FXmy7QoIZE{-N)(4j=%3|TQ~cS@ zpoj9#A+8uM!-}cbLAGs*H{<0mH}xCKvdL(Zgw%crrAQBgbCPiROZS=L+6z_*LEbs; z8Ob?Md9jfUd&XKDwD;a~$qD(z4~I9fUT;`wM0f}$_ZG_}``c?CD2&M=q0YcVr{c~A zzHwys8g_S)gCgiSM0y)z$_S}(M8nn0n~I<75jP&;LbGZWz8Gl2op5QeIFrf~$FVPl zjg~UuI_K?|hmuvJUz>BI!akrVB}a{D(;4f!;b0K@xrql&QE*S_lqxc(rfx2;yNELf zXyA;6J9zw{Tr!vbGvw&6oaieRuBZ8CIl4RAZspvG7@;E`Z9ucinmV&FVVSV~s~}gR zRu_U}?ZEK*mCk})Vb zDSlg_=z>tK-uinLp@?%c`QJST&yvkTc_Uxz_=cI|WX(pHt;eUGkxp+`T?`gDh*pc(zOTz)!5W~Myak7c< zgsWm8+!s5l;X*$#C!Gr?;A}KoxN}$ps|cBRnyw>coH?#9km~98U^W$p!AyBH{84X} zc<6;U>PAolep5xr4K7bYtz)#G&udG#;;U4<>;gks=^4ic%dM8^X2ZXVb2uI0Yv9~p zJf4K{+r@v4up(cyt%YXq5faNWIs6>Ots7+hqb%@~!+x-*mrPR*%Cl0p9iW39in#R# z;}vyKM>DN0((j4%7i{h)5|2`OUb-!y;wN7|q-?x$?oCdAsonpMlWxdQy;Sy(59grA zE5s~=D!txu!yXgOKNq~(D%`&C_flksF?SN(kE@x95ET)J6$+abRVEe2pj zOPKwKE>%QYe*|0;_8vH?5=WL;@)G%X*wzM%W9Shj-yUY#FzN7(6i*h}HTU6@W~eES|=4L`P+-Wa(&h-^{bK8MBA?d}QEOMS_yM(s=Eg{G@D0 zn`o}?%a33`8M9o)x>lH5B8E9**Hkg!D|Iix<_Wup@bqy8ERzG{IA)6+W`QTpvTaSw zwxTMPx3$K-ffzYo#7@Ac&0?KCdd?RfFIZYjSYBpaVFh43vwG`vj)kKCmfg} zzt_RU3C!|EC#7`=M9zC0X@{eQVn6^|jS%|Z*#0?YJm&2fWr|^U1NuGY-K{d~`M78<|2oewSX;)JyG>4)gt+~Wy?di3f3BzSlC{HEI zn(7<$T%NKgQ9P$!;$5Y4AQ&HI$&^n6yjG6V3KYPmi zMf4soCs-q=4*&V0{RrsH#Gcy1qlW^vh^ZRP-Xh|Q71irhk#q7hz6Tv#6#Cpd5&jpNkZSbAnF zI=Q@9%wN&6*c<9EtTYYIYoR6{dU;T@7l-3QfuQgGCu)79(Mq@;=FIgxvYCVL%Ak`R zw@-$B;@ypMus*6xVYUxaYoV|cR(BCsdZGPIVOSq8dx_>LoEVGWml>JQwA0M)$~}o} z(oq^DGHQWLwL@3~h7E_RC2ssrU67%+wEy{uYi%*?xR_{+;y>`d&A~o6c#OkpanTz7 zcR}jKu*Mp>s(^Ot%upL)=h(&@z0YAnAjB21wkxWQ6I(y>R4CFPW|Ao zqzTHoHay-7jUx&`?w+577cn~WPk_^fzd+Iqy|p^Cyd|mp^>P0k41&d-OHa| z+<2XfJILnIytG()TcA*v&wFB+2Gi$4#b^%%V{%V%s}oEgij9sa`Uv@e59(s>N#1Bt zf$N`ID34_-@sLy-)w<85nz5TF@S}=DZ$is%_%lxQ^un5+V$KhK8YEg}DA)y*sa%0v z`b5(%O7;%r+WxZs2ln_Qr}@Bd0&5P#jm9`R9F8v#-xWjB#F2rx;U;=1NBIUQRKAGCU}T#W05>q^f5=TV3F{VdKzIJ&-*_3 zewhJX`208vd&;|!74+ur^|3EsrnSe2Lp(ABF(VK>0Jo=#=u!B!N~G3>bvceF@#qT0 z(cO*nk85?1uvS48y)eqp-^v%*;_@(7fwsY zqH|3D#`yipoGy=Trrmwnx{y}!@|5yTo6|M`Prh;6V1(a>PY^73iUwUU+Fy7UbLmf* zKcf0Fh8(Uy+5Ai3gZnZfm7(2b-=F;QTc*0;(N!)9#z=|vC0tB4~oY^O_{`~ z$IMmemE#Oo@V#AJ5-(?7VtBMnl?;uRg^f@&lgHHr)eTMI zIS84~xsxz5ns;_HScoGjOs()pRRa>c6wtl1qC7NErlgnz@srpR6?Y8v9m z43V6svf0o*#=cisbtTUk@W5#{dM`6oDRR23rx+$CH1LCB_&=S2^y*^rG%Roz{*4eg zNLc^l4n5K76~9%2XDHVw-0@{K@RZs2d3&WiYk4dKU{MZ2OZEm zNo=x!y`@N1%ve9TZsXq(th$5S4C$K0MN?#22G?GfgY0m94||V=Q3&iOp+_U}sy{vk zh|^|h)>pLn!#Z>E-&N|rW{k3Fq{_vSG@K}dv$+3-baFwm8+R%ib_2u=#*;*h?1(=x z!n*@%1q(%1?05;Y)P#o=JGi_J|DK@lIl1Wx)xG`RPg=S2R{-p@I3ol%f1%lQ+;tMp z4v6V5@&$&}7Fp?h)B!!tvC}zwQ)=_a)Y53f3@dgjz%2q zfu+u(tpNrd6;CQ**+pcZV`4VH9Om}V^1oyJtvLLpd~;e(Zi1u&S-l&Y{pPJ{Xu2O4 z`eOG6;pL5IU4&gKZxmwV3D#+b+@;D6$yQ;Ee<`b7rD=f7&0=+PhASe?1uB}2>lXY_ zipkITRv$T$!mBQxcM)Mv7&Z@nhbsWfbq;fGoXm<=j~5wbf;NZb$F|T6;m!%l)z0pN z5NIhzkHn?b}S4DVT2|s#s&0t9YBkzm>(0Tio0f7TZ|!KmOaT$UO3!(v&=t&#R(pbw2fi zMGDW1!iAxz+Z)U3iEW+GYNuFdp`4!BeU;n$CeSCuU*4qf{FA55c+_QAhQfD^@YB+i=_g|8=90nGZUfXC2N>02{(23w-;4DGU^^u&hw`c4jxuRTCNUdP=+jiP1k+$GO)y+ z_XDxQBcz4vvN%j)mh zqcOYpLM!D7oq?2*@R^12(P-2jUW0^hGbGg(lhZjl5XW~j@EkvH=4)_TIL8#oeZ{Pq zB0Ws-b{muWK(7wURVK5ta2kr{y~Ub3*j9?f$8?EA<^`%6;JoAPGn_S)1$(=EahJ!w z%Nq9h-Jc!$A+3PzCsf=(i-YmPKx}ix^sZvPA!5%W=9Z!jz~}_4^x%$doNU6;FS#{E ze)+@tdi-c9hQH*YNan^! zhi~+)PoK89yO_bj5S>tIJf4MuUGT57nB|5MW@3B+M=!&IOKf$Yi?`9StAcfKj2ZK? zc;JdG_{|0V8P*YhOQ{`#{tsX>8WAN(u|W;vis@F5G$h2ctUh9nQ~)a5DXMm=ym>*{ zo#dCw6?>UuUKp9c)uUkE8TW$Fq6kw1Vc0{wu*I%BP-Z0{JU+B8z)JU&aW zn9}ra!hg2d*`IBEpX87W0AW5_R8l@>Fa+#nLNWyqQT=_~(S0 zNHg;<4_Pt30nFz!q6a?OW6dOFq+rP?EHxA*P2lwu1Jmif1uj?Agq#+;xo|uOUZ8uH zG)ZFfuhQQb^Cq!fU*y%oJhg>9i&DiaPQh(sY;zaEr92#u0T)@b9`0@7E=8f=Lrq=Q zf62x**s2C<*z>(JI;HUVXq+2_)yhOuSp){+@Naz8!Ms>>NoLJfI2F#wE9?--0ksqx zig`EW@GOq@sz8JV-{Kmjw_AfpQ*ibovf7|yJ>jQN%%AZkS@E<{dnX$mXTMe4Xv}X% zxzm=Og|x7zx;sC+PK$xq=!xL5_^m4%PQ({0k=+=XcQEih6^C%gJtmyxk2CD-#?5y* z`=|_g#2j6ou|oVj#&t%iGpq+d-a@-BC=epe8gcE#79EwjRUl$kZ4CFL%<9NTTWM39 z{Zcqy!!e|NbCxwn^J~1)A0>oAcvsE&T-qFJe{m;`Nlo!;etwZbU zT>D&hQrR};Wpl#0o$TljwSNf;L9cMMcf?*Rv92EUsL+?(y#y)(dP#v$4l&uAYRXZy zHJu7Mi?V|*?k{4U4wzmC*Mo8F1vEodRvdM0(IO8swW!P>?7XlqV^M1fLh#= zD37Npb-0T8p~o_|XpM3wwDUvgQ7mYO>RrT5XH>2%dgZa>R=mGWpF(bqQ7 zJZ;Q^5>hL>YuRrTgPS3?5rRCC{u9MrP)%2yGe%H0zW-DnXL!G1_!}Bt+_EG}U}OX}By*-K`4V9j#adBI-&S$hmBM3X}1B`fhgx46UgCUxU!c!w_4>2gDD zE@Vj_4LxaUjrf}ka6@KaH1mPp6?|xg6i*S*61($|na8Q~k^GeN?{fN0o>lYP=d4sF zGrzItS6NsED<*SWJIpI(S1$xS#?Fpd@(+Kip-BPS<*|1draa{$YplP`wo|EljZ<@E zUj=Ne%gu(k?aSvju>HVJ?V$*zdF^mRU!=HUb`!C?I=-f$VHUUa!RM#!x0(xL=-rC= z$eK)IK8=Sc7~}9xZ@)2 zwDR0{UZu1%fWvSucSN}v3_GF00laC8xJDwn5khN-tP;+%o9s;OwPCyI;V@1|4*!qOBkb8zGX3nt)p6651o{TV0q=BjMw z$I4y#to29Mx5nH>Y^XF7buir%|03bj9LLHq)(q1u#M<9HwI6dI(%BFnZ}5f>)wJZT zR=!E)*ncuy%LAS0V22}_wCn_~#q@62*Ibx&wG%$f>DgM3Z`cW8olWF0Mk7Rs(e!b0= zRvi9~cT43vEg!Aoyw)hQf=cRYUn5^NK!s4xw0XH0P|BEtuuP{y_o#cQ!#tYb<>D+^ zs4_W}rw#FHB=5Q6i~;ua#mA+n;g7K&5bTIdV%NsB zS6Q_znUlpKUR=@uR`I;%i{*=;+VKX_nB)Mb+Tw`?nt#UZELx31{1w{YCL(yxmc@5C zM3<{d7;nrCwy@p8J1TdihqFQ0^$1r6pof7_C-Y00u=~KOixq*HbBg&Vf{Mz1_A0-< zlan9txIRNnVK$Okp2~5L=ze&$1*3g&G#~pK!N5|)mvO;u9Jxn3H>5_e?kL6`=D1q) ze8~yAtgMIsn)8Ya9)$6)dIyb%{Wv7*il5!k`X2^Y!dO+geM{3|j5^OX*H{w9O|G#lg}%qwpbL9O zbEPFu=kxR@+1>z8dNIixe+9;mR4wd^Ovu1rP^rkz5R=Qe;ugZyJJbNG`}S}NAD?H| z7uiwehjZlv4OV#2umyVNFrgbb0Rf#b>K#TmMxKu_)W?`qe2(VG+2;^cmF@3r_M-5K`LXm6C71L&pyA(PDcI4ETd}dE7V^m7_VSh;=TqtRpL5 zX01=MS31iYvXz>3Oy}ZOI8g)V{IPWn_O-{OhQiblPFmE@r9y@Gh~^GmWx-S3L%LpO zxg}S;V8ADN_aEmC;C6S&H(c5S>4&g07{S@t(*lJBXi^P3pP)3C8SQcME>&H6@D*15 zBdf(QN@>IeLYs1gInG7#sWU9S;NJ-wo?)#kitC90Q_M_8%Ty{}Q^Z4#yUM-iSulc% zbGWupF3Y0Suxd>O3g&*F3PQ5!Ah?~zrLGl@3~7z|reZ^NOxTWkPk3Jseroa@%w6ZH zHRsKD>|@Gg;Rv^UwExLd(-C`<8J~GFiksVz z7kJQu>57GG#1K8itzd))s^qb000u|GtuKD-i?jCl@){3v=&OY054qM<38dI}J1?GB zOaM9i4o{n@KaSx_!|INuX%!EU>yxn|2>a7v+a3|7;)W^aUdM?#GmT3s@NAd*I?X1ew)IL z7g@=PyFYNh0<;PoZqG*@p^i^h!|{F<^oO8nI_}uxUO6`Z=80EGctE8UC_2K@^Xztl zUIsM1qgn~~FMiy^3~!j)iSD)0_BpyHvT!7(pJrGrCxo-# z7-lAL++SJ#nQ7HHOU>0+RER14Yh!N+lCEQ~9|nG?s2D6N!e{j_+=X5jIjjEvB@mva<|Bx@VOmgS_cF?)X-+fVcCTb0UYdm5c5uA&;0+rua7%hLQMDZDBo#$G0 zZ>?M5U2SI1Us~R@<3MXx|`2Xu2T#E-qW(m9E&XY`>gcIVK24E{UE>Yv#yj9Y^k zb(xD?+4D2oG-Q2!c&ufxCtl{!r6=-tR6N=C4`$wma}}6IBkBiN2jWaDAHQSM3l);4 zbJv(>&W0a3G=O!i5W0^m+ag}!WdgBb9_o9eYYs|Vqh%IMw5+iZCJ8+AnTO9ZavF!+ z;Lpx%np$B_SMB5WisRG-s_6H#6H3>@Bmke2EAEev-s3Xa^gQ&^d9(#|uX1xNA6{ik z2j-}|l{MR!vtOVZO=I#SZueCU9SF7CI*xcnbpL}eYnYtD4{+Nf0v2NneRAZ=UYkUiMsS%bh7LS2m0A_zdXE&55V4!M7uYhSP^NisY!?`=T=q{`H zaPV_3uwcs~>W||61_+2{geQ6`ym=3Vo`-b{Jb#QjHL&<9GICka6=xI*{x*FtaI*_{ z#Bg4F78lX3E32ELXE^7z!?C7V-yIFlW0E&ke1@+Drd)$@9(~3`Cy}k6^T~CNn8B<^ zd}z#`+4QmGNfS(2qQ(~(X#&;iI=L0~+o9hbsFaI`dV7{}{ZXuX#4%Plsm@wUxIi@m z+R`C|;~W%@4~{*!r!iV5vA>#p_QZHUB&B0jYZT|eS_9|v*p|+9E(p6z%UB+bp{6~9 zpVGEi)-Gm|H&dHopL>mArV@!xSgi`DoiS<+a@G0pF^1V;aS7H4EIoy%NldZDu$y%Lj}x!4yDL>e z^><5-C{sN_J8KcM7LYO9TW!?F)u($7l!Ge!=9LX!jAp-XFRfi}94Z>+xJh_3q1~_6YR_fvAUEEA&myS@$%-zS?`8qp?Fz^Na zZFx}5B3${V4idxJt1~=W!MZyVZlSzA3|>LMCJsGRU}vh~zixu!wlU`p7X?w?RU-~L z{R4YBv8xF_tmew56}IjU9kF#09=oVP9F{l4<6I@kX8Kyxe@;~hO^xO0Sxk**^_Kjd z#duv_7U(gYr=3ysiYtB5U?t+Zq0Un@Z3@RseAC0=ix{5Gz3zy+%>@@Z^(wQiI3bP( z^|}2oBieIPEm&M(Gk5H7iC^AWeh!b?;I2fn5#Gl`bryUkq5ebmeMFUn>^Pl1iJWG_ zfnWK@hSM!!x135Hl3;~t>N;~Ax$SX25!n`4{T)UBxM3BN9x}T+O0V(a1XfWep9VC@ zrl}2ERKnc;%(Ta;ue|P$_x&-sBRq1^+z~p;XI;vXXA$s%X3eqmD(~E6>NT4AbL>;r z(MokZ``(?~9H8pn+dHFkFotx)g%b#BS)trjH}@mSu+34HRYcw8n?$y`!4OZnspL|9 zp8Cec_8d_a!}ikC1?iQs&J+HTC~Ak-rAlyxbC2=iJC#cQTC$;PNZgHs)G@GqBn!D%MOx?y@U>NkXMDGmblj;KX27dv71Jyu@En>Xp<&OL9r zRSADf=@`I`R?xY_?{1jU4{f}VnS)wRc=jAY0yYqfZ2Nx(h@WNK*t7p@6;lXeHLQ!3l_g*`W<#3z!ncUu?ovuLm5m`Fn}Ds4+R;@X^3Aj9JZ4iV|c)f+f#VX zmVO$1A5Cv-^!dRWp6D8kBzGjmA=?>mwNQMUcW2;|!?!I^lE@kRSS^;%2GH#_Wo>TD z;j@-hm;Kn|Om@UjcLcg)!)>g0!o5TonN}FSrk3))8sNWTO)ZB$=B9yE9sE%ybjqc> zCnF88bqkw2pk^htR=}f42yw-b&zPd}`Dv(JrkJ1jtLOpR3KOX5TJ}reOmDVIV|g7$ z|DyVicvzu4m6k1#u?X(2_!pz_GBA0pqKU{p3)eih@u|RR&r~j@$_~&rw|8q4#Myhg0tJj}tR8D|~8~ z6yR00p_^IRUM zJM*C#Ca&hOMu;&-oV(g>!J{SGe8CM%9d@`MOFF3`D7MYyy zOdoyp*-xY9s8Sa#T@ii+o7_-z4}}e2^BAGV=rIF>K67DpG=539i5&Bo7k=+PPwI9Ns$M!zw%j`kTNzn++E7bOPslvm}LjzFd*b zqRH%0MiuxBt%E_GQP~l8O6A!S$w^2zNB$9%ABP>1*C16@JAEWa5zyb+R`SzxQ9Z|=hi0A159?j*Y zrHs(8I25U#k#|=Zb;S6MnBa-$ad2&f!MiaGSndVu9NvjybONhQpdv;d>%oW|9_~ed z9sD^)i-uThgM_vit_;g<@hJjR>moJ`?*!Tm!pOIL_M5gbG+M_kcj@cTl;=D;j4WW4 z{hU+-r)nY61#JgltOs%IaF!V z2G7~X3h6dbT{gQ}c-|V#Dt%{59NP&)Tl6~zMS=65S}~2>W{_KkLo9N|2-WsFgN=$06OQU;7&{Nv0{Zq?o=#x zxe~5yfLT2-tOX9OM*pUGb`u44adRO8i|AVq{oe6b1luR@^=wv4;;fNOc*UvVc!}?rF{8?i$7DlnE&3;*oFQXsw+)JRoZuh zkyX*%61KK*9)gfYsCf=nP0=$H->V~NFoM~1OgG_2bM#w@Vq2Il#z%ALbyN5$*89Wq5A3p=swwC)C~sG{VlI=&g^~#^_O0p^vmpW&f*O_>`O0@Ma{s?V&-^kb z6?32^rl@026Fgr43rBpKQZc1T?FGZ?P!Q%=l}LZcL2tPA7{9;di-mN1$I4+$DCWX< zR9XF7uIOlqKI&7lMf_L{t&25N5Nm`(ssr_h3%@YzE7u*POE%SjZp~K~9pL;Q9Qly{ zQ*_>OJ-u-hKlk2mg^0*XMP@P*8QElHlRf)okId{@$jIJ%6_PDv?@~xe!z!U%d|bGVC#Jv_K_`BrKdv5%`xy=^Xl& z<|*Vhj(W%D>sav%+l6stuBeK|P*Xf?gJsq@KLDF-vD6p$Y9V$AhLwS56>QZ?UY!TT zRrUh2f6!Uv2gPpmI5!nCUkr&A(Yr#4P5t3$w5%iCh8Sv%m|+-N36txhOA%FJ(n#n1 zyG;MYe`^JtoEJkl={GaqaBv>$mB#*ZB~`^Mt6_mRn%BXxS(sM^hub2>6rmcejSPLj zw?8@a9C!WV-|g&@QR2+GYrxP{RN!%VNC?Ow#feC^p=)|_sbLT9mj6m(^S*;n)O@e1Vq*(t|h*?ur|g zVA)X`SQGE2%R*vJb(OuJa+W*eTJri2&B>jsH|XM^NE@j4IVh+~+Q+DnP@*OsWaUT$}q^}^+MJD6R?5FE|u4bwULHJ&zm(MM^7Un+2zRF6ERFs=$(l8h~H|2=?oZngMb&#Esv}uDmNzw;D zW6s#(FO&JOiaO6A>?r!zMu;VH>tkGFgGCi=9->^B&Hg_O8xPWKjpS2_!7pLDhHE_3 zaU)o+t*Y4IaG+XtJ&v!}Ncjeh;_QRfZI#f|;(le~`+&i7m3nPBrHi!K ziT>p^H#_F^HQu|(dv@ANe$W09-U$OH## zNMb)SEkT>Inzd8Z(J$Cyba6~5-7K{xW6=Jhw%QJ_YskB2pyM9-&Re?8FxZ7NI@|Di z6I)-9#v9o6ooHkbBfr|)lSK!$EuCPjYb=yu+F3V=fcYyd&4lS9!y^;P$=YBt0{@+m z&*gIR9C@h)`d-j)9^$Y1`sFOT&L}=~hzBdEHM-$?cU_r>h%A`>2cH$vsjsXzOTI2N zr79+^l36%LwyMw6>PV}~$iAAWhpsqKe^Jbb{_3n>yj)-VZvajUoc>iuHn!KCg)d^Ue$pE*b_{5S zZ}-`(h|TNBjmqNf07WokFIQH!&E~v#YT5drLVZUsZtcsxx2Sc}XGO8%Kh-svEvD#W zCyGZ;8WW4^AEk~9QKqMK!ATr4a`V9${z@L5Z6xLa&@4Vfolz7gd=OF!qM+8fELJ-maJGh(3YC?}M~q~*xY;Lu=AI5gsqsh_H& zjh8-iC-QEqSv7I1v+mdjk49s>aLqL_+!cG1uX64JelC{wx1v9#(c?H&7!_Ptyhc+8 z^SGnY;Lo8qHR~z(+e?2o9-A*pdv0KS8F|}K+$&|soPu+s4W`Z_9xio##JINnFM_k) z8-v0bSfoW9#kVKM4y{q4o94a(v#-#96Kcy!A2G;3Qry4Lcbq)8FD+{+#S55K4vot) zq)y53d)?mnxCzHhRE1&tpo13M4q6S!`!G9Mid=?!nq1fqQ>vKetiix1@}8?~>@9h| zji3{+CsMy%dru=T>R8cNz~o-Jz#-pU~_ z5TNkSd6swM!B<94Tke=(Y?l>BHDgAD+ zYzw*eBWAYZj`KX-QQPrLxPa8YF8HvgcyVh?P10I^#;X;5H^{lciF>#EfrEUtWf`2*lhvBx z=0nVCgRRXCf+->0QvB2Tz*OF}pZi82FOW&Cv08zasBQnj5FcahVmdd_GD8shli9)) z(ONROhJgnVA^b%z|ZTyno!jWhP)*$6aqk*hR;?L*ltoz5?$nn&rMBVWxD8nQI)KCe#TlyG{TRQJa6 zh__nW16Eh`XP*!jr4Kmk`D_Dck#K!+}%YR)B@S#bY};Q@RY9zy6GgPe;Ew;B6|cf+1GI91MMzJ zXT0d$nejdxXDoIU2BZ+}^#*kMrH0SLx3T(^P-ydU`WbdvDsvv9aI4|^99Y$p>s;Z_ zmh#d!R4eMHZu~l07uW1I$Ba8Rv1u(eHkVlmn!Pi&Y{JKZ2-u-W9;odl2c6``?k2@D z%Dc!pgZN`CdQIb;g=&8fN~hFjW+*jJE$D_-kJS0IVc~~~`G|NW?Kp$mJxsFhV!KsExyDWP;ZuQ&p5we7b_HS2Hbegc z)K684ItZymUY^7AQBp)Ao7r>mA%32()d*u~hvIDUI(F1H&%jAlJJ1dL!eKuO9j#>1 z;~DcriOXgG?uI(&IX+yf;Kat&(Y-I*eNhj%Gb2~CF~MvfHMoEo!?Yt}W!Z?mPQXu> z2W4SVuJX?h2W!az*_>Bjv2!WW!yCQjmt?)keJ-D2thAk*YpY@PQDuncG#FuyqQDpP zTS@IZVPZA8xPT$UXh7LB8+J>jzXi4_89i}@)bMgW0emWgTsK5X4u8VFx z4<`Mj{7B@tQ7Rn8uj+DqWmsymRR?;z!!!n@s4c3ijiPSL!h*EEu9& zd!YOtTDf4Y7ycOVw7O!^03R0`qJA^6lN>RK+r}{2j;p-*`#1+r(I?;JmDZ}|X?k5( z7g=M>NnQ;`>^mtT7+n|3Vx{H2TQ<5N%vb(X7hc~b`|_A_jt;L`SVLR>m_;4b9-?cn zvtD+gsL|9rx53co`jrZZA0cUi!gp7(ZGd{K6=Mj)V_;~=%GdC51x2EK*Fs)BtzF&5 z0d-ZkP+A?)T6MveBKmGaOq7(g7A>~OJw~AQEIH5tZ^M=NAvjh`S|xx6oAqh}XEaV- z9ml{;+VC;hdtS5Yk6BZ+>9*+Qg)6g=)L33E281q#ewJuhCan&SOZLrMaMym0#r7U*t$OI@L2(0FH5aFQAh(9WYYJ{IRt)#(WF{}4#3)Z` z+CoaR`D-klpKIHfa_#Bj%3cg~(C>I+$Ua@<;@|y4;$$SoN>AIvr@P_rbS&s$NUMV; z!zHm^Z#Wu{{&ADLI_@0f`f1t2@bjUupP=WB5egtw3tBBe#~*U^0l0@4X1Bqu4zk${ zD)p38XBjsb$K2?6UEe#AJ+2n}HRJ5p>ZQtfbwbN%j1yfM+6|?8$Y++Ll&m~xj#lMO z-20$#v)Q`G5!@;iKyu7zXsEgZ z|6Jr{Cxm;}Fej5rj#S5+-R6j+hI6{AMXnqhYYe=?4WqRJd$b>?ZIO^PR~iz4yHV1h zdZ=Y<5+RsQF-q%itm-H?j1b}>$DgL>Zmm`pCoRw}3xYv>v9Hdu54F6L@VduId$8JE z`MMu-X3Nw5(EF@1GM~*VE2pk;*m$%Y&R)~?qABbbY&3lZ}>7wH$<~fCA~*wjF*jHKJeaE_0AI9i`1*1 zM$ka1+X-wOBj4|hZ^3dcGnfY}VTo%P@ z{eQF0&=Q?(b4A&9AeNprJRO1jukwb+w6Kwd=&@@gLgLuZl1mHd{6;PNldJNK%k1Ip zq+MTxrUHI)7DrO0s9lIlkiOQ%wbshX28bV}gk&+Uy%g_HB}{wOnrSVyp+_m*(|(G~ zqplu(!eMpvUB&$J0j1ng-9nkw6%9Ho+y8Ol9(mqtdLEFH<2XS`C|`LrSikg}gS}Pf z44%y}?rSO-uzLH;$XS8@hhXcE@YZB$c+Dtad!cuhT-T?DRMfx4O0reke#j^wngV^lJ{EbZLsvb zKzit>12}%J*7X+q`lutXatO3`-`I3tNq?O6KdF>6%#2E8T~V1Mi#($`K(RM+!(qwh zDc_oNgz#dOQZ2%`d5LP(6E{cd_fFz@b^Xo=jJ_;3H@K7`4e>&juVM6TINp~(i&gde_+? zblqG}=~@UQrg5-PmO5$mg^9X_mM-`b9gFuZWBf+#TPqydO#wO^eo-FR3FX!*XA-#9 zU)gb|u^X_WHw}1S!R?a=hkX+op%=~L=_gP$_8)xM5zPl!RLzLs{PQiGQd0;Q< zuaicdM9VHFArUaEq>M{tr_##!bf%V)%A98SO#Q_w?q00ETF>!^ir=gg#7rnd|-Gk#?o@K%Vy^J>bcYSgT|pJ7(YdO>xsJYM!VJm z7_0T(i`05}eOI&;DNPg6=BYAwEOvBLBFdv*klZx4;OXCaaAh zPZ`}&n<<$A_G`NGxuMCO9@2R#|Cq3Sl4QjV;l-lcBnJp;uJ; znn2#c6T2ui)2<)k-uA{T7ukG~`m`=))z*YY+Aox`195bQw5S%=xEuPngqgd^)l8wT zOX(fiY&MD}@!oCSd<|W< zoS$Ua+ylccr7wZJG>?vvj7Tw7TgNR0#xHTXUFke<9~B)3p;gc$f}rG4-h&6Nda7 z^5$*`-efSJg`auy`#*FRDdreH+O9R<%%mWra5PmLuf6Px+r`FFxinv*Kkkj4p(W!@=e~8}pygHhNV>Ks#x}GimwT_jnwLn1~wA9;-5YBC> z-BpxrE*FkP+e{^C1_u5!ycvY#%hHxGzFEV^R~R`+6_Gc)HQF`dT-t8D`GcXEs->WP z9;7OiqD8XxH0&B?=u#j5KFJlgbG3`o`~sy7a2+jp9irNp;R}swH|{-WO#i@fzM9~= z#B^mlAH>a*JXT`*W_jo^?0#VK=NP!mbz!U0?5@gw%GWvKm%8_LK9vl*GC2ejeL zBxB9C?60Xi1v_hzx~Z7;%=GSEu{;o$SL4YYdB|ea4KVET#_r`x4YI{xxzSHvZ9%^v zCXCZx{9&<+I`=0ZRWODZF?XoeV*!p%LB$h@tt`)(f-g(tkuu(0GBmXnc~6Ds*x3`3 zE0_6dOItE&rFwE5Yc5eAd|<6h>YjWKY{b3)A=OU0;ECeChGUyhbi}aL1u^I3M}h<^ z_>B)}GMS%V^VeF<^*8^1C=L?oV-HoNhQH?MJp-{M7{69vqb%3%i|MD7SxyLZGq}ol z4f*5}nx3NRczT&>iw5w=E_LYz4i8YrC5u*5&BOxj&#~fv@H-9mY<*%`9FJrD59!6@%c8Y2Y4qptXX_D@Xzzy>Bw)i#F zP|+2U;fnQ3{)j-FCwq3(QwDOFqFD#9@fhv<5B3Z(zQ4q1OMP4ywETgR=`S6o`oIPMBBwN(TAW2TM%ehs{9;lzI2S|>SA zK>SceFlM_pG1(%>t1l$c7B}b%Z%$_RcMVh7<(PIWoT8y=>oy*_stT`HQ3~74M#}}# ziM}utD(6Pxhbku~i~dBVrzxyDOBaLL{t=^IaC9rxe>;y4F{b7+;IsC46P!BgQB!cj z8ktMb^ry688r+tdJUfoZb(E_&nbuWW>dTJ%*<}&cv)W@{t_n~C_p#gzWA0gQovarP zg!2jkZ%38QO1FJ*@2xnSqgxB5X(kjy266NdjApJ^GtI)#a=y(tCn!~ ze$}AhL?3;E6EgQmf0m$cGv#78ygemX`@)S^46%-wZ(Y(_Yx#urc5_@)HDfM+nrZVI zqW%MQrHJm1)@Bcf-#6Or#e;O|=~h^+F|^+Rds9U?ODk=b4@5KiGM#3#I87JH>ph#) zvm1D_txOoTBzB^Xg%hx9&!&P=R(l5rawJo;JQ+%U@b6E1-!o=nJ@YTF? zNS%Cxc&29AV{>11o0B+=^IBM++*=(ZPuL^d>sHvUt;ErBxC3en~CqBgzRT;DaJKo8C3n+T)zKP|k zgXD9WcTVX}>lygl=(vEZN~_lsIkt*sQGz?r=Qv{7SZTn2xa%XYw?)D*LueiNq$hB*_$iGwS8I+lFe4gvchK2PS$PTvQsi*qI6a|U ztc#@y^3@z3H{fpo<;R-YcD_4hT(W}>_0^zaKHsG+Yly&T)*TG@?vh9(HTP2fG(;;W zlMjt>@t{1V_g=ep|AyXbUM70c^YD~y7;vO&^KmLu*XSFOVC z)$)@-WTePZUGcP=GOdxorzrc%V(JYna^jV6dnRlCTzK2{HF zg~FnejA4f^%ItwCGs3XJ0^MfH(iR?0l!AjvOP-y`tQvaPD-6AFTz!NahO3@6k@Qe+ zxdQ1Qv2ic9jFT5G!j%x&t1SkE8G@$aNvu>gjIG!5*m172(;A5`n2+j{e{A<$9jl=K zPxVr1BnqC`G>r0;cDmxu0z>R@v}&i+y1_Xk@>JK5i7$5Xv1;p|>(9GB6&wZ3#22b&mA zP2{OzTDBYd{nI-2#)xffG#P*HOEZ_C$p&T75_rrvIQ4_bD#SgdkEQhOBcq3E2^$&N zT)R`qu+Hj{0yb%*_AQGJ%k}Gn5O+iBv>a6%8l<5(y+^)vf^AMI*JFA9l_V~)#Zy?> zN9_I8WKYVSR8v8@chg3RoRm?E6UoX-(#oAUoGy8IAi0NO>>LEmQudp`uClCuU~UT@ zKEdjtniP-cV(pvV%wC@a@jzeqZkjc2aTi*Vc4Gt=|GFjbP*&fas zYq8LqkyG?u^Vq1hTDY20IcSSf-GNjBiEY7Yz8_=Yj>1#Ql&^&AixruXp1k7vLn^jRT~?145f<;nkeWUTV( zCo3gOrwxEkbD2#S*v z^O;v&9`cJ{#f3_^xQFU(?s4RGwQ@YWlu^h2Bn!TP9H@XJD-c_=EQET4xR zeu_sgIU2`I+3Q9`sKy+U#o>aVSKcz#+5WiO)*35JSO;{&IBo{ zKPFC9R(j&2qAZXwWVF09m(5bJJ&I%gW7~Ih@28y=+RF*!u1|DM)y~YrshT`L2`$E> z(=d3Qk)j&mzh{a-Jy&X_jC#YgICPxD*@x*FK;^OK6u^rswZDSU`%pE;vCA~Qp&Ks! zrFRdESuT6bL&_jUkY~m_%3VYcV`JGfOEfc<&}F|L)Q`R9t!C=|8=N#oJJSnohUvaz zMMRGMN5alodg+P#zH(?+IBZs4*r9WT6dJ_+p@9h zCWm!mhESdb+gxN(%PNWPFw~SkCvv4xo?HgA0wjxQT8ajNI}G*FZr z#$Zfe{b@gp9gl+xvAVP5Dl9Z!%GW{Ylp}lp;FWGt^OJNrOt-E4G)G^3iQb>IMzO5p zrg{iMf|u^$1RDX$Xn`f)rDolbGgf}AGvJe)P|WK7@}O@lwEzb%bD1*}ghZC2K7P*I zOSN@vaQcI`s}G(uV$KZ2_dq)j9GWceoB(;DQn3a4?2<}fq1{i64r9ZKYLk&aD9r>08IRCt=pF=-U-hb`mzZ4VmV=gw@s zO)$-`Y1x9nTuM78xUWrgubQY-hR>2r74htQn-*@=2 z1uKVeaDvw8F`Ew5ztqEyMVg?nZd#$=>;oZyi{`u6vRr=-B3$Jb4N(2NWSzxJnY?U)dnV5q=_-<0ysHL(3RxAMm=aa=a!6L`w~# zSiTGFukv}QUhkHm4C&vCc@kAdgK#77Z}E90%F-Ql?*dP@m;e>KwzaL?C`BT!!;l&8Y6v@A-OCW`7E}Gd%-fbolpLiOt+n2(gJPM22cspsj9d?RI-xPTV zHHMv zzEVngjDIDimqGKJ7!kpLV|XZ(ogMYabIgp;Lf%lCsdq6Iz3r@MiR1gw(hZr`(hW<5 zjFoTIM#m2F-YhQr3irqS7tC*=O!}%%e8nKaG1l2(mFASk*gN{AKG214b|NZ_lEl61 z{2^&Y6TD8B?pTXX6Y1z5zAA@}qAtyzjyGw3M0;_RK}Id_JCEj;+H z;@@4#u?DI{$)dw({5a|NBfi*-D))JE7&iz~S+w5fC%qnPvz&Coe}#`t>6jQ`|6Wb4!NuEkTBjJ zK91O6E#(Tg)gjqexPs%PZ3!X-AeSW*>}t zNe;mMQBs@vn7>fE)fTg!OX995EtM(&4_hMVE&nS^agN4FS!Fj?#IijTbYvehwg z0%vr=lD)V&8T&^_eLdm6T8eFrlcnT+tq`{hFOn#dfaT&u^@-jgjIW35;-oq{L?52P zeNXjpomQ^mNWrIkw5f*P&E=;J@pPSJlEO=srA~4DToKOKIc^FsKc)CdSBzj&q99abdx8ykUGM&b z)wk%KL<`Rcy))S`kzqB^L{wTkqGFUZ$qA9N)apG~mXSmVJ=YBn!Wp@cS&un>hCU*c zpSI|)(zwK(>us<%iUaGT?sGishrD74ouutW>3TP~m6xK*mq5VY>jG852XS0 z>UTX_=em`;aIH7-VnRg}xgy>g<+@3AT0*KLZ6iz9kuGP`^9w3`=G5OjafA08QCNoj zz4cb2Z%bxDG4J(fbQzp;#pf;h7PE;0O6 zm7NPYce(yKiDsYmGo()x2iRfEDU@!4?yaRa4hSxUNH5l_D5?3}F%e6jbLewsMls~7 zzA~ufa|?LEL9OYf^1q|>vcZcHc-tP$KVx(|EVw0Qj>3mh(&?%gwOT;h@hyl<<`hc? zq_FJ`{p?S^U8yg~;ztj@un`6!gw#a6N|K=+MwFK(ihFfc>0CZ@Q&Ay>el;*4iuLOA zNhoXX)GJ+QQawiIGt7?b^4Kn!mBqZa38$UVVU`rq7p0a+V_LxXGiq33+c1=oQE@0= zd|-M-9uqsw8G5g8ym(fx2wY!9$rd%XVw)f#TS(ST(L)k_M#< zpS^NL>#qJ%uqnLsM&Eh28(&w&@-i6cfUn^gDFI3B99JL5wr#3>nvL)brd+nHPS`Vw-p6z1H^@YrOs+A z`8kIkRk;_)AF4ZiWpuv&x;zfvqi0<-_QI+LDEf#BwGmcJ+FS*{I!cA*@p35oe&WI~ z`Xz`?E`8259!Sxbz7kz>dhiFHo65e~yw?jp78qn983dF3ymY^f2=}3?0h=zPRwbNG zW|`s={+>r3EBfo@ez5+*k}jJn8yRJX6XoGp1@Z3?>42RDFtb3)5Xr#?4N9Rc4jqtLrw6Z)Ny}?}zT%S>b zQYq!fm~1-QaE{79%k<(T?x@9pQdoYQjiVEJF;PnCw6l(BvNvKR?DoL(_^!^yWLZIl6 zc`|N4Vx)qi(p;F$A#3$u@CiaITeKf6{c*)S6KSZ3pb9iZ zA^Uyg*KGEy&PUHVs~nGul-wP?(=#3%O=AW>7csq%#%u@z$zK=A-40h1a9m^SLI~c` z*XAWDgQ$7D{)-b2>yaW+TaMin3>wC~(x`EdW|i@5GOQf1E(ht2ku+2i!bs!07%pho zonZl-n#4izG;7PpY0Ukke~ss{r~3M64%y2we@hUl9wt~-L9()eNfrz$tvX4atKi=a zbT`sIjlnaFE)p))%S}dlyc(pgiC>tyiF^Sf=wo1C$3fYO6 zY=Y{m(X|XlKILuE5LB6^(}m)p4-?LodwSn=o?gmVMO{En;We>08@=mbQzvOmQ+WNt z1zXYQ0?+!0c+Aq|h@Q+B>97-eM!?OOAftvK)j#as41;7iJc7{_b^1u2Rk5w9 zG{IcR?C|~0!8z1^QM5rlis8NLG!Y4rM|x}$n+#)5LCC3wayEh-f^4xXsUocsl)M!5 z7K(u+9dyD|5mz^I^8kuKO^363J@LXa>dRGnwBd##c0W$1VrKb45aCLvV>@7&C3#q( z;!9jhV~;`5J~Q<&*9k}Bf4nN#4&U@3!Ed}%La3X6i_ukabR<%1!{G<4TA=iMSk}Ol z9?}O_q#T8f=;9GqrXm(Pvt3Tf<~sZsxDmGO6?k; zt)0}ut|Ur4$4Jqc5@CWaG5XuD{Q6A~P!Mm_2b!S#KqeT_u@X|tp`3~zHDJ3`%C8FJ zOGJL)<>QF?PS4Vae8)ROSudWA1NExWjC`*z&E~EZ+))+*1w3bgG0${X1><=HBWcRBVOwlMT9e1x7P$MocY!`>w)|#BIG4qZ-9|z zlB*fseZgXtWlx}TF+YUU_C0CJWszKbQU4ObK?7N#K(r__{5x~SiE2Q=GprUV?>^EW zN6d35xv>oM6=+|KutbN_sPD|wpZsje3!(|-u`XPTb!xG`*q3E7z!tZz;%QSn5)FN| zP$mxjWz1Kk@mA>OfI44A_X>N(aC?fL_K+_tb8-?L+OtavqXOA2i!s|Ur5gTAMXDvL z^pyrQLYGLqF9-LDSXd5|POfzt566f&H zCorpsEp;WEI;eLNQ;igY%$#_p_o00Zhvw@=PwCZ?hHvcT&XCU(2j&qo*!!V}0r5`K zh5GQUE;Xo*q}4c5%#_+_tFro5w)@N3tttM*>54AM#)7W!&k`}>M`?p$o=9zgeRt8j zKD_=SPfYH=N`mtT9nm6#zSsFKjg88&OavP^@_aH)8gcGthHoUY8Qca>%%CP@?r zm%i6U&M5>I(_c_(OTjdUS8}*)8Z!ib_KV&}oNT6aP?;?d(ACji=kl7!-i=&myt;*A zTU;wAB{W2Q5VFf)v&K1p_;4@8XqU?6@M|T9fkT@KA3Z>p=%qgZ-W1P(V`Z7 zvr%6ZbW#YI*xv(B#b*`H4&T}HKZ+*Mk6pQ1kmXc;l9&gVapoT$uaDCbq7GwqDdglM zp$cN6v7j_8L-5KR$(2#7h;@##NGNQDdY^B6SFA_pl*F%&Rltll6#dZ`JC=|}-aUqJ zi+MGbx;rEOjKI;uyCL!lIYfjaAWTCu7@wTvyPeh(cmLK7t;XmQCizRt);WtE&Djo}mM1Bl70C6u#Mr4XADgk1yz0 z7Neh_q0WQph_S-&t~f2sqDOf*lecWSI*pl*JXy$vBj^t-+Qa!K*z6AB;mp2(fZAA4 zM%vp9r7xjX6-*tBmgZ>kSQr#JYzpIkiAx%V!k<;BhrZ>io7`N)&#n-pRzIQ1S3<&L z_-owr2c0bNVqS44y_cNH?H89!#STB_bYhBhPl^D|Gq=F4<$S%K~nUWGzX-1X1M zGoayLEV0J&E;z6A%4LS-GQpJ(Q#o%CXBV@58xGFkjbrpvS*9gETj2Crq}N8yXH;{> znqY+2MdTdV*1?%*mNCJQmAop$R|H``a%csPiKpv!&P-<~G4c!5&L0svc@OToI4&fS zS}J=04Gg$YK|p>Ox|2`R#g3cv(-_i}f=Ic|jcb4Ka4MIGclz*>8%xG#__*Nd6IeCC zkt66?7ar{}vMO%fW@af2=*q%u=4R=IaiYhT`!Xnk24~Z`)fqX(^w z?X1vS)JDj7pCcq4t{cmlU%0}Z&oY>3&$=n>x|T1qDPmUQ6YF~l!)oDEG9EjKG!P!x z!TBWWIN(Adx0@q&DR%=M6kbiGe`9X_P1orqEQq8RJd;6vG2TfyaSv`fBg#ttV&@`4 zj#3FUrn2B`oo3q~gcp4>`K1nbe`l;M{ZhE%IX6mJ-w$oeqgFWdI(Tyjw$`|jf-6n& zaeN8+#N;7&nhEM`$vyLdrXNe?xo(`6#ofMq{DV&#!$;?Eu`es4a}n+es(&U9S;BlI zCKl5?m)*q;VHqc9vUhhr$zZUi3t7@qxE=EOwh~V3Ji8I^MK?qwo>xWPPdHN>)h5EBuwODCbS%M2guY|DV(#pO8Y%-KAZ|^11j5TSpMNx2TS6ZisYog1N;)H&smxn~3PSM~ zq!e)lUCWVqyxD@kvUyISSu%SDu+d+>&!ST{`)-D(0mH(OptDvA?5!~SA;RmyO~C4{ z&~q<4R=~hIyba74NZ;~U)rIES^f=Gie^|LQO!E2qn!qN*`zxGF;m;Ph7W3N()G$L> zJU43e*&tLL{HVlHDeTdiJJa~!HrxK?dLdAVH^nBrB0Ghmp$SIiU`0cy<8eUD?>XF9 z4H4e-G=o(qzBWMZO1C1OnZ|MHbT4MbY`&i$%w6pBS=6}T+*OnZsx3k{lahbD!5ooy zdB6+-JsIX%6SbaQ1h`bVH=*_T7TB#r*LF?}WkfvdENy?eW1D)b@WBdk&Tyy#kV?5>A3Wl(VoM@!&9&M$>K zrP*GnvNJf{fX&HlVv6e1(Y_pd2V#nX8xhDY4Z}nHt17w+(NMzUjBWT}8dJh<3&{11`Pdud-;_pW}f|U0Aw|K;JMm zn_rGnu-RRz;ivGLUcfGmqu*hknEno7YbHx?L))^57hPR? zyz!D3@>!`Tf(m&s42uN|D;P6{8}M$)Dc5#1;uUO7q>~J{DZF5$i*w2MdN_qwO|a$! z6Errlfv4K*d^Q&_gBL>?3h z^UT>eV1Wg%@J494S70G@xefR$VS}j16lA|+C4{cEgSd~}RE75WqTiW9{|}3#aE2!g z#v5~tJp_N1){h`2bhCKuGr_Q~c#};l0q@PE?^r6?G>oKhnRe+^!V>&*SoHhB(Fu>t z(Q_+=x*PWrXUs7m1g*%JB?wdClFlK;yu5{$ra07)?wTlJ;Y1nV_ApqY#OB}^&E7y?%X zwO=Br97f*5IU}ze5+zwQ>4~of%(!228pnC?jc_5BrJcqPb4nOQ9fG(}oE5du)eO5f zqO^qa@hB_dR5*GVxwSVo|K?OdgU_Ss49?2p?7>_oBef}e6|lqq9G!JkRcRZ=_qj9( zCP;^hsMv*piQNvis9@`u*kYofh+<%OcYxj9J+^|RASoS+_w4UE-+!}a&2p~yp7Xv> z?7e>*wHe*ZDtKaqACZ_O)82XME;h0P$r+tQIMzItxs~=oKrV0?g9wN8)a?LTx{NJn6iCr6UHAq=gI;9y=b zM^9tSv_kw4Tu@o~5*cQQybq%?CJmO5IwqFKuRI!`q)7?yk6^tr_Os<%VJ>WAc?+RA zbCxNt4?!gp_#DzIhvdgtub}=3lo=wz5zD1cyTiru?^|(DF`IScTPsA2(@x0LW7=VU zyaxIjVcQY3wZLRa_LiD37+ZdF<3MyPWZi7aPVIXzH%kp%n}_om)|;)!#h0|1dAJ1( zBwHX1)}|PL5G#t=<^kRarzsGR$?}His`5rKtCTRkkJj-`9M7oA*fWk1I;=@#tU2lp z$72iJxQ-7NP>w;iF!@VxDNl>S{!qvdmsyZYu?F-m=H>=Gp3mn?xW^cSU(?bQ$2#GP zIc!6~GLFA3k$UKV3rCI7s3{s1F!BZC3%PJA-OE^h3+(IjJIB&$&Vy2Hrb}487;rAk5-^a$K+}DP8GHKP5{}t2!6w8^xqeu)yIPVYY z@alkM>cHbODwIRF8CX+Fr82&kaM~Gm|HJ!3SdDb-!`;FVAH!`r%!=b63-Ru0^MOv$ zxF>pylW14Q_mZqz%(OoEPtL9v+AlTQpH8_f*MNDsJRQpEX80)JW|hMv7fV9eR^hAx zdOyVy6C_0=UxyaGAsX_xr4+Z+ovq&R>2B0%nCpol>_c!gE2;4 z!lXZZF%-=+c)}7}3fb~7y=A}rFJ;q|)_|#h=oZOimEa&nS7jXOjmB0u841M*US}Y) zx4}MD?_<5pLa)4uoQm(a)hxE2I())OatsS!j6qNV}NhZAa8NA zWpGcSpPvZt87}Ql0qY4>P_{4~DE(>TVAd&zni79)k5pO8RK~BFkoRudRwR~DOhbAd zzE8%r66Te#IFCD5(F}1ax%OTvCNL};!)jJY>dhw#;*ar$5vIA~lM%w^VyyV;F5uB0+HZx-L%s{G zub2_Q*5B-Sl*fTk$uk!fv@eTw7;ugQD!{go6OAykC%Tt0ew(%pG?mOu6U0rz^WU6h zf!H#>xU5a5J*TjO1?~^xT?_o4!Zq?IeC2*4(WRq?umHBT|?=wXTAvSDj-yX(JDyl5i-kTMkoR#A7-h7Ts2(V%1sF8O;dPZVVEiES4T}Nr~;QX!>wy*EAZT1kP{=#2d1)R&gPi{{)%Af zZ$<`ihJi3{cuszooph@JT_Mj@#G+AHSst>p_R*os0d21Gc_X9;SyLTLa(VkPYvl3F zQ0jhhsdNN}n7*9fO_A`JXN~Yd;6X;{v;k90k^cbA3?VT)y|X!^I}-94_=S@T`F@Rd z15WA5B%rAuYnkKSSzfon0s}M_jn+Iow?ely*j>u;hv4^@ttTS3fNmmv{Ka>>wCTix z2^>wfSipdaxVxIGOttLd^Hvb4@Mm)bp2S%bT-*)+9Lbo#q6{u}M@lw*qd7(xf77{J zlm;#Mi!2VLjSVE;cBT!gb=G#5BUWj(O7R&qE@1y?v?Dt zzu2PAP~NvjjguT#6`}SxUkRgw5N{0M%UW-evL8K?IC~00(zGaYNh=E6Lvxi+I%_$T z2_58K6yG0H9gvqNA`{dei!+9JunS|Qn|q1TrEIlWY_n9wbRc``(@ZVqn^AmV4#QsT zPzCw_YMEpGl9+9Sfj+n?=)Kbj*WulHR7>Nf4d|K5K`rnhll7mobuk}IrN7FD4VYwv zjWamX8L!XL&jwl5Aj$A27vs3fZC8*k^~o8`lxW}HkjTgH1^k%Dl*N?pX8BOot$@qD zC@b*QJ2<9_Hg=PH{R%Y8Ca?Q z-HZLU3YR~cn_X`X}A<68QfVL1GBjBI!g+;eXf@K_jv#5N&WJmRd){nk~%xUCRfS>=FB=^Nt_KX48KJuSmMoGR~`v zVu5y5!RH3rKByoBMdoPq0K*NiZXQfh*}on7{p9Odv2xQnLhGSA_2MWXV6*@TkaUyf zs%eEvHAl=}1fwdNQf}%W>dzpffOp5DSu$^w!|enP-oeXBtUZK#Ej@U8GlH~1u2+iTWxd=_*)q2>H$VE39ox=1vED|NZ58D~z?{aRbjvLPy zjAd2|KZpiCn5Sh#)|Cg1aCW}-_dRuj8*1TF zW&D&h)Ul{v3HQ(8x-pg?#jaoc?vGuvaxz3H>~MC==Zs;rtc;TRyes?YH~hy1 zTiW9vJ6NvP&hynbw0W%AX4Lq}soh|o%-JuwF@-yVc~oxFAzDov<4dV<#++fEBLs9_ z$Bv?77N?HJ*B@-0rvW`9Hi}G(tp;!hIczkqRmJbEjC9nZM6Wp` zc{p}gK+iMyWCr(3h$^DZWF#bVh7~fB>2!j{xx6}q*Tv!3iF1t6bQx#Z!7P!jT=7~k zN!C~rftx~5I)YsJ#x_8F)~6bxgG88qVxJ^#n9inIY&@9GX0V-23wsQ@$=}Yn<&I@e zh+YD}s+f3N>!)6YqgMu7`{HOa@20c&4<262=f9Zb%ikq@Iz(jNcypF%PAHXQ&=FH- zAh9y`Z^!1pbU%iA`81x2mMN@jf>Yl)eJ`ISa^grXEoHr_9B7Tfi`d#0^M5i(GDW*U z@(Kp+!_z9zAH)Nxa~5J`GTS!6yhQF0h+8sm1yfppgWdU(+`fRW_L%ofLch_iId-@S zc^rQvFL4jDi`jNRMr3oI7mmm({2RZ0;r&JI`IBq@!l= z(aHhC&SI=wc^h#eomVE|SR&46Xj zypZ0_2(@lNQbD!`qEZ}dIwJoa_ub_Bue6xUr#ZYdisE;hIYZ;e2|O#Z4sxpDR(1Go z!%i27R(y?~e^=mGDz|oqQ5^eais6x!HnK2{%eu3gbd*bI;DWFdOs$E3jFIe)UxTp1 z8Y>TIbAu$ zc{|FZa|F_|84`k}Ke@UFykj{0G5r%*7C`Z#h)(Q{Xy=x(t}D(wVR&_%sEhe7NS%uc zR`?kWsb?w(Z#|CvyQB3-O(`AlfvdOiU^Xv|Qdpg6*R zy>|OQIfkG-exD@KPfRq&j~G5b%JV5S_v0Yx$4Aq_7E;5w3jIW2F&+r_!dW|1+KwmX z!6R@hVBky)h~XSJq<>;(!NDXlZ51cxGovq4|8jhYMh0p7nx|_by$QmrA#5q+UNhc< zQM zwUlhL6uuaZvTt0Wvg|869pJ1Kz8}V(`79jF6&9$qnIUfIoyAI$!#@_6oRG0e0IV7{ zU__D73DGx&KWjpk>9x-@HIAN>d9j3t$I!Y8vj62w0R%r{MN#HA#d3Fy*?^VSa6620 z1+2CnSK?{h6^Fl4zV=h!*e#6ZGg+lKz5jB{1irIH)eAi3j)v959*fm8U?QyNJt+Ij z&}hgidBza5j^T|0cKOPoo7gXd4wKo?6ov!2sS2*IN=o`t2u2}VvRcl~dE7&{Z(*mq1huIg< zT!+@naN`GkE9EtRt^Yc=n%8V`<~uvpL{AZk$ukp-E|u`lDjbzM zbFDVo{ZtpTV;K2`<72pYIX!=9U5RW2o~>l(DoDP;agI3ej)-aybN2{qO^>n70#||o znOt>6)db#3W8+xnALA_1BX`m#?K*xnhJ7080YMr>IQ*try`hNvae*T0M# z55Ejnu*A=JtyY`P5L$3otSphqQ$yferVcik}10SQy^Z$)(9h*t&(Ux$JsUK2p7 zfPhO_KAB~!`5=WoXVa4m4dSKp2s^^fRk5p#&0KJL82UM4=K&;^!{#MuoW+O$6s9o5 z3LBH?EzsK(?w!iAU-TW%rpAcb$&L;PN#tDF752hwdj!r!UsH77hd@#51Yu7WjT<2_ zkzF4$GKs^MuvQT>XYsrRzE7iN1r*$&nJdKdv)K*jgK$##P@5qdW2aDf31hJrCZy0Q zgZmPhy_fs4_|}h&%QVJisSPByC0pO!! zFej0}E%J@EL)tfvNn~*% zx36IX$x52UT}E*Dm+z$)h-OW9WSTo1%(TZnlW~#sM{jYmCKiXtWH^Of~g#m0ZbHhCYiDR5C4*`@hg2kpn|T zaK-J@I8KiDWt2?mpVzs`9v5BF#tx1nVIi}r-MA%g?hv&8$s0Yj8_+U`CllHCBw4`C zK^y`M9?MDQI2_F-;!GzEs>8$=@2jBAe8`rz_Il`wm>dA7G@6*=K^*fXYAlrj3;3gi z-pg24LHZZU&TD}jQ;z6Y4;I$Ay$IXOW7Q@k{^rqMqi<@sG>}eVvnpo=w*XR;czm;mnlfdVtRjVO(0U4=|5OH=>Ecuz(>GwfcAjpR`w$o%325gaD*^dpu}(kSH5^H?Q>&kT?m z%(bTI_koLTaMT5!RZ(s>hF8Gz)foAQU+3dS7R&iy{7)uiGB<^%qu4i(hh<9hm)^kw z<;0tdlx`x~MAPoh@xj+hh+Kd##zSQoOoPfbjj;)TqPB3+b*(iv!%zq0i$}KY!n^V}#3-e>xDwQ3QID9XMlwY>M6;%GKD!2#Jp+rc1) zOR8yvi?()H6NJu&=n*QmGCo;};&c{>A196@lK3-`M(emWpPOdzlqGVPv8xl>U!tE2 zZdto65K(P${|oE5VPaEE z67j}dtTe;lFjN-R>q=N=aZ!7m6M{(=8^q8%f;p+|Igi^7@iUZjtKxn%-?|8sTrfsx zJs91cF?J&g%y1wG^)u6w4*W^jyhUOB9APS~P@jdB9Z@DaqgZ zRv@ZZfv22irCc)uheZ?R1+PR7h-YyEqYv_OCPz-Bs4spj=IhFMc9ZX2kY|fLCrC2k zQA>?dq1Ur?D~9HA);~BJM`s0AqMSKOxzPec`Je17gP2?i%@6X6GuDW>#U01mLUgd6 z!I)-_l*RB8D*Z@|OVQYlvEQkG!*ka= z3eMYcM|eH+(JPJfnxjq(>m~7ZJVUp$XEuKx4ertQI~{hgOAa08^B3T|nN4iPwaPBF;3kk|53~+MQB_o4 zgz@BoIoOgb9$~bL<%ndhM$L?%!4K}7L!F)%LOHksa&K|6Js#M?rv@qvK%^b+Mk3H0 zP7+KisGz>c7Om`WcKF6VSNSNBNuk`I%h|K(Z-{H>IH?+pOC%f>IlUn@c8BTku7qKs zcy54zVc3z$Sixj`W$qo$jAga?8YklSeEu+i^Cq6Eh*n8FUk%UQAw6uw0yGx2&o9Wiz}7Fput z638U#r(m|?*{_t>SR zo6DqO_%BJce2DwPh^MUojj1d6Q~YRiwLA3jVZpIMKpRJEU{ePSbeDWE4L=kYj%ah_ zPsGU_ZgfX#JP$mebXDtv8Ii8>o@B#fA3|{f8$V}97wwZDbw`_7cYmJ0?6L#j5O_QE?i3=TqDTMS)^>nIGhdqSrD%Flqs6Nx(4fU#FndHw5!B9I?4%5!_1~aiio(eD2 zk}RDh*8I-H`{goz5=qA_hA&`E@kAV_Y!7Q^akwku+MtIkYDxHmH5i0)rr0(H9SV8X1^)5u z{7K_?_ufR;RJw(0{gkqsswuvFr@aWP?9sUz{vD4-a<+%UzzQ=bpk6V*HpH+DZcpMp znN6Kx`Bbi-!&$$$K{8&95r2&*>>zgFrOx=<7X^-(Fb97tVC+i#2Aq7cD~*N~A!%Bz zFVQQGzr#61)B-D*Nlrb)^P-$gW1H&u-VA>vx^OAH9B_0ox&q!45SYgjM|={;?t2c8 zW#T&CNoKj}99qhan>e)+n!RB~M_AQ@p*%1nAXTgP8n~51Ne~(ovQ}Fxmi{r7eqUI& zpM4S;xmsiH1T5oT2(yf*<&z6&rY5fWqmeuQU4gw7Fk%U2h(EC}nrE{OX&B3n*E#(= zkImxoRIZs%a|O~B%Cx_s1f#hkq7BZ9H(@Rs+hE*!h!9#Cffkt@R0X@@x%Do+;u*Y! zKABuTpPq%RvWqh1j!NK8nf*4zIyX4XM;ChmnBk9X++-j6hsOnLE7YE!OiSSH4eXh~ zLDTp&j}0O;^4*$;jIDwWB8`xl$v?Q}fWHygZH>1ZrQ9U&ex^8yygFMgJfq!$a( zd`~l$Xyn;<7x|?!M*Zb=XJii7W)5u@qk}b6(ZCqPVlZwNQ$ofT{N%a242z>{5Ci1r=z_Co`j&gIYP%^K`}SgM1BUFeq`@tRz5j@YqiFbFPTdftV_4z}8|gWOxn}g(tq79`W29tm*nr z%WqxE{kNEE1X%=El$}Lq*w|zHGSs(0*aDOo!At@}M0W@69l!aQ*{PhfjpNeTa;&|{pDuCP=~t15GUFOTcl;s{u9S0v&C-qPN%!X>4;imA4Nmx z^NDRLqqf9lIALjj46(zOSy)pZDU)zf<&I{!o6Yxf*Q7G=3{U-J4S^Qr@$E*o6V32_ zezJrV>WA%+*aK~=!f!JCL_;+hAIXv3@F$<64Uv_^-7mQ`nFSH7nnj}s{uFh`IUcdX z+9Jv-@NxqfJ3&IHN7!K95_C03jX{ufg(nX1PiN$7&i=`#$2j*FSA=n+XkGSc_4(y5 zVl}}_2fVgMmyvi?0i1?g!hV{7R%J}Bhx%E}6{)|ZyPTm^p87Dh|IJ_98DoaWcUZR~ z$2AKynu;?DnhwB(A_|sN7X?-X+i%B7p;fAR9gcg;txiX|T zsoob8D&x*%te5IK5UoXX;*D+DtRa%E6h=mCI{c22v=$BCW~PeD_Zt7Tz&%;1SH+Q* z+ER~mu%i;74zLY3W?JiO1!A-^X&skXzNN5@hK=#Re-|z}On& z{4l^078CGB&!P_S%i?}Z1j)SR0qs-hafJKwIOYJQ3bnaNgK`MZTq zXquL#!_YCGrVUUhg#p>zk*M{~1Jh}Ani-t9)U(C zcr+MNCe>|&Te-ZZ(|VIj|FK~f?f3Ch34;zYNOZ_|7;X*M61H%Fy$1sAaJx5B%cE#C zK9FtOX)peiHU}z!UA2UYbAw?{_HT-? zU)*Sn=INaAif2VVbzW0D&p%BIQ+&Fou_;9&B57dJc8ITxTD{QT40ZaWbrGX|FffyQ z~opX#+dzF+gYzH;BFh-bAt4wUILvLon&835p`TkEc?YRRiSNN zQ+X_li82u?;pBT1R}}a0cR8GN5kM7Mv=FEeojc&YyU?O>s3JZMP^wkNmW|4%VtQ=n z%l%CDVTH%+(^qZqn*XJ#x#ckGhu$)q$JVlkH;Rieu^U``b^AIXV3y9)3U41M87Zt5 zs0_+y+*pd>$0ty|lh4#?e`Pe<_>lg1Cv3c_F6)7X0)=;nU#^zvvB6w-B#A>C8#upb zNE;<;CO?ctgY6tXn+>0Ha!Zz0KtwIo&IxbZs{y?+XE@X5;G4{LrsGL~a;GCsg=^2} z{-H`_340}D_ItK>h5a4Ll+_>E#t)U%X=xnQmFD&F%TL>B{>q?#MU*9JtiXM~I_bxs z+*003a<91Fr1{;$s895mM?jwC( zp#Oh7*Hr27iu;n#8ja11ywH)$#X< zBG&0v>y>7C95w_CWB4FMJA&eYTFtQZit5}Dp5OH4x}aa8x@HYtF44pl$rWMH5c8K{ zf)!@jDLqQLd7aWbmTm*^X$xce(`Tcklc`Ii8Pkw;W2kp!`UiSPsvfts@v@@~$}E+A zB@Aq!ll@nTncJ5wfN#Bx{% zz#|6(J&|Oob8y7I^~&0JRNJBNA?ACq`)TGxs>yHY`(6D~%oa!W-(Ip+cgp=6IRGch zp=Y)tei@7PO7A;7Gfhdj&*0~ZxK3`FK@!RQ+Ob45|BLnF21*D~_btHs(`xr=@XX+r zK(w0*^Y%zhQ--&Ma#JVHl-H(8o&9vRL*2u44^Z3hXY3wTJZ=x|^_3p-<|tKcGCi;I zsocWu$|@7g-=sT{!!Mte!!Tr$Dx;yP zHF$O@CW&`)9(qqzF7<_hnZe06@T{erxWrk`%HG!uZ$je}yg6IF^qN6U^*>+o+7$gr zJ)8IAgHaZ(&;aTPt=o>y{?qI#_Lu>5W{ zw15{BeTx`Iv``D`;?^cUlB|p}OmM*Qri#-W=B-isU!dh?on$hfOG3>ROx?rjJLuL$ z?ec`hIcirg^k}7Sll+;t>Wu(AD~~=wDDJ29n+~_>x?4g%>ZELuiMP3u{DYlt^3Ds^ z=|da28Tabzy%jx`ezPG;J2Tz~N1G$BI?hGoP&U(Slnu|gx`%GV2l34*9+Awbi=W|q znWOriWWR=-CyH(}9`}U#Zq=|U`qk%=p?KT{?WSW&>8sstZ5sFEd zMOgZmZf{lmN0&SLHVF*gsy3+&vp}j1a4mt}ra0XZ^OG1gNqH2{#xIolH@Ir5GGaR` z9_QUTY}%F;ZgEo}ubZKrj^56AvsRr^ALAFY^EeE4P{NmCwX?F{8|N?Ks3;jqFaJ(i9r)}eV$0A zgbP#HRyO0Cn3hk2kL=WpJ47X`s7*`Rca$309*-O2>Pk^uz;_6&nkijcz{OMo4zAoUy-_s3UeJrHqGdUT4aA)_H1R?G z_DTm|`1&brrF!%>DD%bud2(K{>lSQ@;YhH35{sSGV{(kd>D>hrJXSxxDY8efzDNbS zLQu<_H|U-`h;d#xk*ildVNHEz zcT^7Gs~ONs#@7U74_Ag%!2M5ncAvR%O4MiG|BA*tnR((lYWqxo(ohpP>$z+|Vs zt5^c>sWDTrts4Rs<9a$)w8z0UnEZ?HLlpab_8PCK4|!z=uJ55*i5*w*pIK_=B}(%A z(lQ!}yU+qT)j8JyGj3B-GVJFnOh_Y!dAg@@bm+!n!}x_5Rsx=7jel7}WLP9J4~tHo;wOs9Ps zUu;+HfAf5(e!2smJ5yxRtBtu!N_tr}`Xcob_IhF9I9+K|#9mQWzU7~d=(eAWYM}g8 zzB#8JJjI9^stk~~j?rIwFQx-E&J#6mFwYqcZYv!cX%&li9_Oys@>I8(>z<~t$1se( z!N*nT_=!E8HI~`)yXv*MaM`Jj@E7_g(c$@&e^%e|6PI;X#ZzM*z;F-PzQ>7Lh-#&iGgtp*6Zd= z&%Q#LdY0#8wSACx-l`c7sC<^B6}DKZI`_e~I%@SU_@|idmSbHQ>JP>3T{_QkVlY?4 zkjGs;E>}A1 zWIN+$she`0v({kqIu`9=gD5_+R_{G!-Ir>369l$b&6_|iQu_}_mYAUb#g89K@?30m z)^)6d4}Wm(3$1!6?nRU_e)=o6%T>ePNm{jj&nphAs@8Qzl|1!vBg_}EeNAksq=cEH z_Y|f7HJRhgt1CTFZyFOD!MqL< zZD1$*t6V;eRc00NekWa>*DOxK$8b9M@mx3uj-^Ow8t1FxBN$|+CYfT>IyJO2wlsp{ z0@UuTeDp`RSxRUPv>mLh0nF`{3PO3cI+_U!GNc zanTRwC*WBt$Gx8JB2MWvabKU~7jQR*~rv?@@e z>*1vlSIB>J7xWfqvLP5J!Ea{H>?UgR>7~e_f;fYNJ%8tKGZ;#B!oDog)UyQ!4zxZyi_6wOu8tIivpYe?hq=u$I8k}r1ePH>d+}meDY0Qx`za&Na;KoB*Yi!Gy6^&< z)Y5mj%84QBpXRV|W5v!8>EQD&cp0g5=!F3fm7J>Bv_jXT5^~;S`vdL`MNv9`d#lp& z&mEw47L8m*wMRq5o2xUMV(~ss9SU6zmbZiNSlxGf#5Y!4kMNXUQ66&cF8p0DgeFZc zXz*5F?Ih2}=ySwz{9QfWPGUOvR&Kq{+IPw6E&+gD)zR2&xk z?-`xPtCMwb3sOT{!1Z7K1UHrpou}9hI z1y`=&$am`6Q+&Ex@BfrtBGtzpxEQI9siw8AG68t~7G<>%&`CF`p6m{EJybT7467S- zYmFrzXMhnzKV{r8XqbB35p_11NujVQTjB#gyvhqK+zNb8U z%!S+0c{|Uq;I-X+^`E}rAl>GuqEGWnP!EXxr!hOZAXs!DwQyj*GN&QlM(8dGb#c6M z<{6v(hij6V|B7!Dc;A)33^2e>b+3Rg>3aEnt3Oshk3ugATpEW&A0@IaoIDgU*Bm;h z+)Q9{u(Iqfb35SUc7_G1^Vf4`fa?EUjw!WrB~&}8_AJ#>#BZgJ`Na>*RY^zj+@#7{Ats(C4e{^~`qxC% zALUGWwECh%JMGg_lzI4A+jr=Ob+D&gG|=byfZOl~#RK|2#grq&F;Kt2Nx& z3CqslO%s^>s}ucz$uMQt9hzKKstFGz9|s>XZVrnDHQq$ENMxfq`aw3hoTetvfk9&i z`D5BgES`zU-bz<_$M@-zhRcYi1e>+{p7;a8ba&C$Q|+)p^N%!H0Etk z*UZAb<@BA6JEO36FmC9TJ3UZYG4Qd$=!%NdHj3Wk#8Db1s#Ev!;wE**Jvg`1n$zZuRxQo_1o$0g-~kkeb~wkETSx$JS23}?6 zZhf;No()kKkHR>;enoFIuSv(*7`qTNrwLC}S91`aR?>+hGG?=q^?-?{%IZrTIGAQz znEOtBevcTfPrJhb5A;dmg8ZqjsEhYYFtizFj8v|aLtLElC7mX}bW%EYS*8@q$U2Tz z2e@FGI^#Jvbyt@-qwuNzgVd9a)jFfl_&HC8!m_23HWTy4>0&zK*hgh&3fE3nCOu~p zNoS2_n0Ui)a>jT4v1@ek(u+DeXPsH_6 z&6l{km4V@07RB{}s;Gm;d1|@$zK``gyL7M0nq(>^Gti~3?xhpn?N?fV zXWKc7|3|u3fN)fQ&ruyuv2q`M{0n;PR4*rtiB;RmIkuDQT@X=SneK+*bVYI@8b|By z##0-QLE6!Kyh4&XQ>*;+;5ZO zq~M*6E=1*}Cd#CjjOxt+2RWj;`r;0o4b|JEGPsl4-V?81tG#QX-T*vmiuU!DZC2R3 zLMP7LwHI|qU-0^Mj9A2S-}ve<<8#!jZx}FN_0nN*1N|`BcwSfA2EwBy28F=UQK{gE zyHQG@ELn;bQwvl!)3uV6OM5Im&Xpb5;vqMN=*=Iolx{F-?T-_{Gi^=+!G-h1Tw{D5GtGGG@uB~x! zEZmX=ik%8?>5N6}-DR8$MHZ+~LCgN=y`$7^EEHh9pio#clP~m1h_{ zProdk{y$XLepr^Tnzco6Hxv#*Co5%<4~+Kf?svoSin<%c+-{`=zhV6X)_%p7|Ei&{ z=+aNEZGvS>^^&txx0@>WW`#BE)d{06DptKPQ_(FbqU|ZA&2^snrR=%I=(9Mzl}9bP zAd;~=^sZNV%3q!9j?*L5*^RMo5GVNJ#C41b!1i-Wt1c*>tb11-EB7mH;#ud5L{GC} z9ge!g;HT=HLY_+2Z&rCFOh3U6J#*Er1F>f--VT9FpyK8RWvNm7_i#gC=4cWnYfAw{f*tvx^>u9-3eb)pTe>m1p0+umFAn$>?>Yd>=LN^MqcTr|O z=2&a|e9rc*`CHQACaa}oOl+&KZ-DWgRp8} zdBT0y(R(e!7qaChp7u~@pXE(QHNBY6da6I2u=1;#-W>TIaMc$_E-Sh|_;6CU-UBy- zmDjJ?&`}Bd$W;|l?;XouQ#XBL`wjXfIlLRGe^&?V?AdFQ7VHx$EV=56lL*B(E8k=z z`HyZ@I{#c(!px#e?GBxq#7ON0Ks;e?(@~;<#$ubYMYagua&OT#Mc_HrhN2a^7 z$4zA0ijfX8cI&1lQvlzUm+{pWay|QRQkzAy)=srcE?ZsJN9NLJhpKOc+5^zv4-0E5 z7A>%@lg_^eI;~aY77jkFoc_TUZIB{F*FYMHX77^z@pldj(dFs1)>A(}m-U{h*GD1YoZ7$# zQMY)z4-(#Bb~~K))xB$k!?rqcM2;>%zeC(S8{^J%_ycv%J`UZY`o@a0Sf3xyQS;Rv zo+t>WK|56MsEq6d&m75yK)`&ZSpvtm*GWG4{VQ01iL_I<|2zxAho zGpL*D-vW2Kt3%sk@<9q8s&gos_eNN6-KlQKD^bJ((6@y$y{z5qZB-NMp~-N1HN}T*kUC9L=!>i3gicq-5V}S>k(QYlD8sL@dmKIP(z=$K z{EXqg>H%jA^Hd)+MU}0pS1(9%X2?jq+^UobRnte;ry6o*EAFq@e7bV!1*2NS;Uq`r zt9y>q_kzChO*Smin@A$lJ#}kcgdSpULlnCyPF}dbPLZkIoIbk1GRD5sc7jJ230#%k zGgZs0>>930;!4Ny>LAfR-PQlBjwDmg@JH*Xs5?`{-pWxQ%-*l)3;6!2B9quz^_9nu z>9w6FkMQ+!^~4Q+*`&YrjlG=Iq2dZL<>IEO^AA?EKyF1v_V0hn6bFgaII9yy?kj7> z_z?5%a>NNLDQdSboUm5iXO0eg^`f3_Hd5W*57{PoHy1sGlh7Y?pDKmIg1@g=c*d!fGc!k6as@xb5l=vfEn<|}r#`2AQXzO%7s zlo{ulFbx$BF#ZM?K9wt26_<*$x%ys#e0yT9|Idg2Oom%eeD#CrNhPQ!4xdmYk0mln z5kJnjK;@e#atnC*GJ76TKS_pgq`vKUhHX_J*2ju%YA+|@m$74Q)XTy4+UR7e6K>rF zU!DA4tn8JChd62vqHnWPRW7~B8s}7*8tpl#cM-)x2laJlY&^mi|03uU!iJ*XD&=W? zyjZ0qtE@Rdcid1)HJrP^;6dzim36ACP2X}#E47m)@&Z&zr@VAg-R2C3G1x2&&1%XJ z(OQJ-x{Am4h|=H!rz*;+GfXL^#G4H0$t)4r+o@syb*>;3W8Tdz$r^L_W8v(G-i z{fTpX+lP4JRw)LL$8$GHL+A)0g}jBSm4+N|-nI+sLBQ)|UnY~Rolzg2`r%h=PLdWVga zBzwY<8NpXU_N{MuC)Dx0JHFPXmh7&T(^w7e4J@BpVw2SxR1s6_Sbr07`WAYGadHt< zz0cQ;9E~r~ue)Qh;AuG>+S;Di#qqH)hAJq$E@Jyw1Ws8s(fXqnNk&>a9pK6%me6aA zU4^g%9NC7;j!9RvKMZGDwtZS*w7cl==zzHPJgzS08~z=L`N5Vx6|ld(wLx*L_|FpZ zntRTp(M_(Z&TnV=>QBe(kBlGg=-`Ft$L+mDO3A@tO%d7`69O@0k0q!S-u-0>e!&k> zmaX^Lbf{(28A>kreJe*jaMU=@D-Z24G3+wkakB;NSf&t|CC z*>bHCmd~*4i|4(wmZ&Fm-fa2d3QyF8-)1IVcAVPHHWeK)(cF37@mE#Ke__e3HSIV{(`jV3{ z6k7H4^^Av2+N)%TkusZ?&s#5p%z>N z@QB`59qB3TQp~a6#ym=iQk~Nf^=qO|TT7L4$R2DFS8ekwOT;&}C}@%6z2kQV3;WQN zW4^IhMThc+YVCAH)<qpV~2(`_9M4-sg zyn2Oq9FF_1PFPujv1`qs|-Nn&og2RltS)o8qT(>|PfcUzyktb(HE`1D)aSBhjwy#z;S_(Ps>L$6vKE3Y-7QKqv&>0SkgjD; z3|sU=^Si9xo{b-|^cKgWG`6#Hd}TzPcWfz#lA*do_s;p4wxSHgIr-*VB5j(#PPH_cJF79M_Or(mRR#s2EJHPEsvA3Opq z%RcbLJA9OVx&lUAmzRcn?{U##$Hk`{J;pIqY?EN_E|2tkEL{@Ldr-6pT7JM!cHaBh zQa+veJuO}2#%YYH;mk9GXP@(FtfO2edxG6cAnd!NdKHWe;eb{s(hDP-AhnI9LUja` zwQS7f_M_0YSCPT!_LNQbQ#w^IcQ${jt$)Xr4;)j#Oht43!w>mzp)ES*#ng9b?5?OO z%j;xL9A_CQS7azo+~r=mPoJ{2C$m1Vjt8syqe--5Mp?YD@_lUt-=}*p+J#|vW5m_B zJdum<1vbUXQ;G*MoLmN9ZZp?JZjaEr=_vl136~r@?F^I~O`Ef!>?0P(W+Vq-z-tifC%4{$Lo(vIO8q~sKc z!;;4hCl_0^auxm`*4*PmUqrrOazB~nSiOQiIy^Y(@X4jXAYQ47oO%J9!*vRtR>O1` z%aZ`KPeev0zog;ASN5%jqY(@`&Up`c`>o^qLpD6;NJwBnbJ|4JUdjX?Wj~qhfFB}_ zz4pRymV9EhrJ`*VC;f=O9vdI>s5dNIpU&|NQne{!WSF_N_bzAA=8&a!c|G)q$Dx{N z5sQCBs!4}e23_}|a11Mb=jZ#(*_OGUi3jCqBSdFSR`El>K;{=|Gm%dUV(@R6lM6@k zS*nR7SK+?JP$ZvGXtXd^qb@6ORjb0)@%toT%~`*JkIsQz}~1)1eMO1 z?1t`Lg$HAu$3`(uo`s!JeEpPqFAmJ+`L7(9;W&}WnV^CZ`OMvW?I;iskll)(hV4z*lmP-Elm2aO)H{u7cz{98eXh}DT_UuC)dFKn7{u5$4hE-*hzQyau#^cb`D4XB-%^zbv`8ShCNHf#}Zb1`OoWrh4`{gGWKa9T9iRWVsMZhiUC9YbsIwF|yoV?cgHwt|z~ z@6pihlW+!z-AF(uw{N)e2F%Eg!2KALXDIV_IFYEWB*(aWi+LM|6*!(Y zhqw7~_a*u{!+RiHllhag;nHg};*O02hf&=1Ll#zcMzcv=QV^e#X)B0f(o%d;dcC3A zMjXefB+fpG*|FSL0{=W=r%U|&nhlz;S`7c|!r!uaeXMy_ahgZ+;Bp|uMp&~CQT`Zg zhus$!XCo?w?K|RG2Ho!QL^7L<=YUN1%gc+->KwYb=%%i#sTw76;d$pP*;A&`gBG zNw}ECuYC}cNf~IxsjIx5TY)>3C@MptU^dT*zIPc>43qq^AU~$|gSQsPe#Lem<|FRf z`Ez$g&~kqaUp;5l-#AsXupe1Bk#7enrjQ9!87G3SALIwtPru>|yg)#1{JRl@(&fRx z^aQpRY~eezBRMCPwh;=(Vxc1Z5rujgoh<-x)6JvE?(L zmcxvfoDsujS}U7i+-`qOVE%L-ZNW|s7TU+GoT%i59l7v+v%>z6KMVt{=r<45BABgX=ml-in;E*5twA<|YB;)SLA^LXIy1ch4S!A544z{NbRnleQOsr1RnhOiQL^Dwcg_ zwM1@^4m^&v6HFgh+8O<3aGF2vk7AcRXd-)Raon1Tm;mE|vgSnCM$Aa#?x}bo6t+e- zaa_HQH=>!@g_;unR*+xQ`Fa)?_#oi5N&hJ~6e7kyU4xqzoD9W4O-~|WN#Tq$-ZonMwiK^I{K#r&--HIKHe&7TIQUkadZWm=0n+Y@bN&wUr{2By(Io8^6&%R ze{a}yGrx0Tbwjny3}Oc>c!9OV`4nE&8$~9epeJr@LPfhL|2tV35U<T}6pAm$tzz)riTXZ>n2V51CN0B*bpE6TOzpG$ z$&fgfufZ#cTt1dz?pU&p`g?zRMv)KQw4JAIi@&gy42i;g8-E#(s&Ck}IHKRML@1|e zKYbm`*m$D{FDXy3CGYy8a|}HyAhiklYsG#A{*p2|&v0dJCm}tdNqfmYyndgHKJw#i zu8gOpK7SRfVJbWM=p18xrD^AedY^k8P~8J{t|QqAIuGxC;f+=p5XECs7G81j8Fqe8 zpIOXK;;=GoBbv%Vjwp%qnXF$L8f_dYjuMMeKOa`>J@rKF8qC$~*bln#ON4V^Jbi+w zouEzwndpM+Q>ioX)?QY~g|dYZlm{a=p^;L>UmG~mjPc0-k}ta|5SBj+=lGh{*O-mz z>{?tT7xX};`eElK!#ca=ipAQto{!LCxc(PfdLzea)KD2SR_iTX&bI z41GFjGCh3IWiV|@G6Lj2GbPTIOUiVv(c zdLzc^ITVW{FKXXoNMZOj#rxuLn{K!t`;Q^a8}TQRpB!2iF|n+5gX_L>!xU=HK6jEN zdgN=*DW0e;5@vpc6g3M(HK(J$ANC%`YGBbXn5V?;(RdNf-d6ZM=g|Wk{f?_!@$Wcp z>diM6{Bf4O^1=E4%a2LD(Wo%~Schr3aq9`Zz43A|dWs1zOItkuJ4BB}_8G|bBIR~w zd$I2Z@|YYoZwxPVoPwnD!M+PgzId?KWRLzl4`;Qf(@SP59(c?~QJgr2diI(&rZtg6 zwsUa~XrZQ@FUsb}FwJ7-;Xpx@yozGlRotnHfae;)u9Uz`lQ?s829;*sSgt&!eFd?J zJEkvWV}IPX!Z`rnrWx*K#zstVLBL!?`@FvzRmF~0xN{UAY~}uEJk_1~KJfG$ZnD$9 zn!DW5@)3*X!HR)cTNLq2uqzkFUV@Jg>Wo2hIww>_F$Gee=A;A${cbWr7WL*SdHjOd zQvtzIEF+{(HM2$gY!=4&!eu732q{;**yct0;E8nFC;a0HH!SCYH>@?3-?h#9H-~GU zzJ-O{%~R@M0I}W7HfXmK*qj^5TM%Kv?}6Bp$obC5lf>YIJff_bpBbnG-nta|Ht%+d z@iilb4aBHviur}`cnWIxVb7lkv7orNjS|^G3;3_;evcF1u-OC_Q+>OXvy$n*oZ6kV z+~gT=$mVxj+?9pc?vJ>msG9?im6&W}w^j(XQ7e9T;+eRa9h2BlCxcj{lUPitBDYyi z88-?+D}vGk4NDMXk<$yUH^9q+(tWV}Gdrep%`4Vg%eG1-UPNEjeyh1TjUm5rww+Zo zxI7mYwMQ#|?A?Uhd5|z4E1i%%65TXOsR)-O#)WgmSBe{4H$8Qnf${!!)QJIiNgvT zFl@6T$Q*}K1(2}^WpkiqFT76VH$OD{K#zxpyQK>GcRBoLaBMtlETEU2WnY>Mf`b9D z=ftRmNXmv?2n7h5I3;J&{akp(Qn^i1*FAnit zhvA2OShE(BHJurV-RZPe#6>ZzqSS7)#x8n%WZsE<^_gA9QzyWoXzuaC(+aTq!sBP0 z$caW1(9()8zu|?0zcj|e1TK5QqhEM?xk-M{H=h5fZr{!nZ#=xjp`vFLg>!M_>TALs zT7;NgTG441I%7ZpawIeM4PCx4b|bGVmFXu2e=(V)UB7YU6*jTrubj~Co7)J;Wf8Xo zuCA!N2)P{e{0Zq&p0(5*&wy(jlfWw@_(sg|r3`Vws}Q#K!?_2%oL>__jx4aeFWpY6YJWJxeJG}Lg|0qQ|mV3u=&1a7LotKpU@P-APkty7Y@}}pQ3^ltL ztxEq~iW@-jj<}f0J1JcCo$c51=~o8-Y-)xgO44*i@FtTJ(LIgjm73QU;e}!U8&eCS z|0q0gLvmaAIoR44QxiDk1e<^4;$_?&%c_I;=V#h?@^ThWeldA8wL4;%AACk4QF%iP zu*DVO%b+JYuq2Sg)O!rmhWIk>`_7n6yysw!Wn80#yX%G{@k${6jek$p1STVOQvQZp=Ty0L6-_+= z1*)QaG3+qj6(0P;&8r#xk-dLl4p9*fu()!9?(&f{Mi;>{KWyxVFh5LNgqc1V`7=cL zUs)5c6L>p|Q{q^1Cx45f>n7Gs;H_WyM+QYZ>FkX#Crr~kt`9;=AY%sR6vom)DBy#; zz0uu@Vp_?JCqt>7%1M*C>?241N$(WyImD_CzIo4E?uPLanhSxW4e_%0I#@h0tq0M~}0c1}3Y!74B0 ztAW4_Mm%BHcsi|<;LO0W3>Rx<9am>F;*?tLnpj4*iaP>ykZx{#6sSc$l zpUdROIBq$`kXWwTAe%kQPhhktYPVR}3l+UFF*h2v$IASoo??7nyqS(M;wlfsbtfE5 z;j?6R6X`3c^O|{5uK&Co-t6XN2 zPaLw21;4QM9KQZ;D7CY!SeC&{o*3UAR)5IGGbJZZO*I)+?}y_rPYiHD(QJ0S!T=ku zF5*Zff^BDGQ8m_>&-_*-tIH=K-PjA~2V=c{`Yg2Yz@F9!%i_)&7@uaA#7lhRr5h$M zDQ+QG#_`)G2BxvkTceq@%ZIoeu(gJ7ZWQc?dVcu25L0s^v?dk-$Fz@}!3u}T1pc(1 zO3VGUj%za5dl%)^`5~3L#S^ZC|8n5)6#SSQz57B;%VkaAYG=)YxUPQcJ(o){4q;F% zlb3MS7Y?|~E!ixl;f|7jOQLNKB#On86H&AAC=VVCfxj0Tmc$mZzrGqW+xZ>*9M5?> zIVz1uR&ZlFOP}IyD`sXgj}NpkTP7b)42NAA)~%7l+0d9rJJ?cSkR*oO=DyGDzJj+u z@y$WTi_>z2ud}!~kvH8iqPC&&4DF6T{qWB;^R~8bg90vSD|nhVo9@!>JAXOBVoBVy zg^K$*zLLW-SwV&C|1sUeq=R+A4CM@VKzT15AA|WhpP-qVE6%EN#!3u zbE6VcPMX}<=~+CS6N{?DCohh6!`_@&Kft7kb?%8@$$v%ONMx6%hHjd@ia*D4=_cBe z=yRNJMN5lj7dPBh3n{gKs7ZX7@`rg-wzb8lEcUI4al*R3XZ1w3J;3!MCog0Hg`})B z>Q>3OChOsrM1!1I)eVMl+Xu0pn5~=$7c8ocCrY1>V~#{R@8GvDoVJ1|5)6}2&*8hH z94-Q$yP}W4UmYKx-RsUgg_FQ{8owQnZ^N(ztOW6H|FJntGDg z)y5k)Tpxs{?iex`Wjx^59?u-CRTDR}8UB`=QaJ1)7uXoMh>xT$uH*1*1KTO%g)(^n zUo2_@>3ey4;ARdKor86Ls9g%V$v@xnN*c=?;L}7}H}HEBt8Ail(a2El%3#Ahh9~0^ z2+esLFF_q2v>1WOR;+A_%hHkjOwLG8MW`zoc_UMj*mMq+pfn+Z+Bly6nbB_OP!ij6 z;OBN|?1u|8kvlggcEbpF%<(n}WmDeLU(Cj>EG>S;cGgbenKj%kMezx(KuRtXf-`*( zzUi7C4<|1K1e@-=zTOmaGbwdz*cCpAXV_x4|H8PV=9Js@K+(OplfmnrhT0L86K#9K zk_TVMVMuQLRKq;ya}}49#lfPoC-Ces_D^R0Ev%Qs$ZZ^{zdfFptteO#HNEgq+LSM1 zI-$J_77Q{}+lWd=lWh4}k+1xClvTel{a@~i=k#-Imc_e|IY+4+3XRQyiFHs_-}QKe z`r}j&-1dg-?p1(yf6q;@Bb-4K4{+$ zi^W~7yolxySQD1}73;Z%MM@Wr(L_~MWD)37Et zIyOQxXM6~NPTk4x%y4JD(sq)Vw$z*|`L}S1owJ_ts~cK+p{G(-yJ2{4B=kaV0TqYB z>Wp52C@xxy{_*d8e1b(|`7VS}iYYn6;Ti0F(vUNqvN+ZoZq3j%FFKD$J%1#0gGEV^ z4WU`af4=CO&VzO5ZB8M~kxsuT!UfT#O){5P7x?GGl`&|QQxgOOsx4o` zXt{Z!&0FbzhCykZ{5z|qFlaSDWm2}x7yADQ73GcJn_`wPuJy(jF?sZkc%X4@lhzlP z!pW*>Z}9RrR^7!N@f^3EzozikF+R&=mM1Lk$f$>|UYI}0WRFhj1ApaDHA69HY}J5? z?0<(D>1@7{&C~c~J~yXQ#>nW&Lv7>TeoEzTO#|S5S8PY2?=YPPm$pvE)XkijZHGN+=LQT^lj zdN)%tIPV<$TG06wceE$ju^nIPD zP8;tt>_l$J^XeHMcn0YqRMqk0`V620tn5VG}dO2{~etB3pem z?C3T*Fv}2C_2N^12`BA=6Uez9x;`iu2?__g`01+OD zXoIymajvJii5dveBL_l5Slv-cBgiZUpJO*Uoi=e;0tahdOSuO3_)(vm!a8+&G($Bd zYjnYVccl=*#~Y`rqq`G|d0|a9Ti!FZ{hYH#Uy9pLw`7W%6|PyEgE|4~mB&^uM7F_f z=>|>VtBA37=0+CWN$lLd_t`j;8;?unW05Rd~+ z+sSfaJ(`!!?iVrBEZ&lL8K2lD$C3p zvlQ%NG5M`(U{j8BeL6p#sE>Z#2Mv_l*a&|*BefL1 zS~2ZAH)XSeGS(k=YX z{FH>#T$;+_dpSKxR0p2RV5qpMne@&NS8{MOTy{ZI(S$tFy0Ia5DKc`I6Qbid3wVBp zKW3V`bd^Y$q0FVUM1_WA@K|2+7Vi}iRO&_2Jkzsy z?Et%`bM0x)vzcJZB|u@pNnFsYD?aMy2Aa&o0}XM@4O_iY(Fwn3IZpNeG5U+@w%;oTX93P4WZiugd!>W+o5$c5e_n1p6(Q#G~4Kl_LmK0cvwu48NHS8Uc3vC+#UN6-2 zK-cCN=7Bk`=iIjbwQ5nh>=Syv1out~3qA&T6d|!$Ll?$;cBAig6lgY4_8m10(j1Y6K z7$~cd6DnWjBjDOmbArtmDnu33B_7M>-!AxUg|!yKl!M$G3C?(=4hWdm4C|Fs>5Z$| zaszUu&73MtRWWYok`!K!Fz?9yZziqMqmp^f3$?^k3!3(aTp~K(c6gwqyGi?fqHqlt ztU1mqE+}@0$Ml;B0Ht*1&+H?XZy^(cJhY|BIuGs!ha69Bpu_5XWfaf^Hje*g()kwK zXR!J@N~S$6{6hdPH3*auN{t9AdmD!CG>q{a2DquGZpoO z9du2j-%Z2YtfHZbd?Wb{jVin;N{MDX6x&?Tpe58B{t$pl77UUt(}FGM=>u%pV?Ogp z@hTlmc+LjS22*_A1v8tOUhGvzOmafcE~sL~lZN;~KV88qnLK-k5#pKeGE!2V^W3Ja zmK%JWZbEpM=$x#CpJ+JAQVISRzfOC&)nvp)Ge46bG(U+6(0Af@%f{@EUS9>Kc)d&AF#7B6O;MK z)tI%UGlU1Bj4N*T#XDDAYli)bNb^NQJ0C{#b_T~D<&-pzI?m_}b~(kEbOuBlesV7% zKFDSraMuY-`y$AS*MV5;FwuC&fWKtgR7Q4~NqBeL!ayaGgfh1ic0XiK7yP4`52+k= zu~~_|9Z=WZ@G85y!?!xRS&>I?sGZku8(Lufll(b@mSc=h=lcuPtW6in@oZkMjBm=M z>xxX$rvn_>>=lHK$}aIoJ>8qn>6T^wsUum;-oc{MPqdQngxv3W+yy@tHFsr9WAlyA z>jP&m3~!5zqHXviAMj5scVu&97`v(nKVfdY$%oiFm42_y{p*njxgC7k2=&RiU9d{u zW?NXvSvAp&ERxBI3M;(FR+-e2)Xa4LcZ4x^PFA|H)kr9+?iyA^3H93TG0F!a{gFrN zL1j3c(aytET(zFCVm1qhvar0ULH8s!7Yn-BURVHd+lrPS@;YwZOqPZu}a}IO~g4D%6#c|4l ze3?d*zkZb!Gg#~-dt`9`Ue>fR-xKq{H*-ah6SOk?%>_TVMGGf{w}I7)tVURBF{sjO z>HHkYBdI)gfTvPf<}45BJG#ug4h~G_N@wJ)fM=d4&A1tc@5e|g|YNPk! zIg_*b%VmZs(RC-6=r^5@T`g(?uhAc)P@PI5$BHI${<~=HigSuQR)GI zAqyPk&P>kuk44it_6Zlu-X)n)Ra{w2v0_UQ##k`C5B6H|OH*jFeAWkg6hD4o`7{m+ zAU1DLBmunT*&; zd4@B?Oi}%_a$_y1Qvgv)j?nU^1&#Zot}`6%5kv~Dus5B#mTnl>xmGdoLo_+jj8msCO^mtrxp8a=YX^3O&PS8ZL;|NJXOheh!iN5 zg8s+@`o;uQSaF zv#lC3T+zC<`HBCu!+2NhXo)&*I4Ib!d}~iwBAYvpGDu$iP^zBsK1V-N0UYnGxG%e` z8&))hJoE3nBh?j81F;U6SsW5cYJTAZ8)w|+%{1me$eStbbey-+S^T|8Z$IY&nIDGM zG2il!tuVwD^Sfh>8+w$*Rq|qr=>=l%m=o;8G1gHvE0Beq-EJ~W-H8Qq#pExKDIS>6 z75lu9-5P(&r&`VUVPbNa3tH9)sUqjja&ihA?PP2Uuis@W@{D5mtWf0cQ5X0I8ufBU zXVi7W-e5Fzf~+Rr^mD)SqJHiwoYaK#nB z2BL$eHnp(9VVb5nHpU8WDN4p6)=y!MvmBhshEJ#-N;=723r<(VQxlOk!$&6skHHFW~-rquhRVO--NUNBJ#k{=$(FIUyPo1=Q5f{V$?a#1Z^4EdFW zp2fA&Q6o7}2~7fhWHWdtr>JCqOn2=LD0JHm!biMy$NAPcDpx}X%yvSpMpy}W>#@n? z)VJ)G!9Nf3eHwFw^0;o&n+yjADON!I^TLp_*FPAY^cnWYpH8sX!f^ep0+^c3&{)%~ z-3&9Qf#5J*?A#b?zA4#y8@L&GPP!Yini$6QsMe6yG_)hCI-^WQT{X;^O_wbGc7uIv z>@LT3Cfi{h9*-rt0BM*^;+YuD@yk;Ac(jMkWQo5e|9#zJ)~b2uN*f$$bl24Hf|Ko zx^^DWk!vvlixZu2Rr;+nI<_^H`MjDY&Fz~QR(AgN&eY*YPBB+HOYJqqK>xeUvY<#D zRa41&>gR&Vfu;e`UY>q>N^?B?|2xv#!2{n|NB!CblVbMyG%u2tGi>Exoo9Mp__Hr+ zIwQU@&N`!IM}z{lMkcyqvQ$|m-Rq)MSALhC8C-INO)~iLEXz7r`7ujcObnJ-Lxn3r z6?jM!3~|P&PSB@ur>d!1XG>1ZqMHJbGWqLO?#!m!S=Lv%et|05H8L3Gf<8qIV0=v= z`nh0qQ)~qCH&I*wdlW+1Z03k%yL9%r!Co2kKgNgZK=oZ)vHA@+IT@L~hO5b7Rj027 zQXe-t!7#xJ_kvij^IT;^78CAsVm6OnHriwN<8-q#-*ZYE(Q&Hcd~kW(azTsc7-vOO zL67YmRMX4>b#xTA@y~aL*#Gi0kLm`zDDNs~>#T5wU8YtSe2^u>4XYa&Ps{L*Mqw*m z39t2R=SC^zihiI^Hg{Yzai}3$b_RA{WJ71feC9NFT}x)4aA~BSe3;Sx$w6_ zP8C{bY+VDh zng0Qg>K0a7qV7KV9JHe$n4Z{}F6gSaqmtR$XcvUasz9W~l9kJ&dODwJUq_wSQ>Laf z?JRF(bNpFTGyD-jwd5K`>(;(m%sA5~wncR()D1>+sed)FC5ws@$YJNIX!_ZC`#iTQ z)qOwZ?a3Fx9d3|4W`Y~;>F$=Q-v~oRiSLXez|`irsnov$n5>-oME08e0W{w0dCZdhOF!;RE3g0iHqaC;V8A2q7x#d9pL zeTwhwC6`fQ40pqNX=3_KT0+(Awm|%yMHvP4+YbE50~t)wlp&ifq*qu``8wx&AowF! zYj)`mee(a+LuprpbvFv$^dP7b?xffY8&%{_01;Sy^jORae-;rEK+_*m5ALj;37#B!&jApVY(sLWtv~WXQbisx$$fMgW z5cksfSt*|>##XmCnRz44-=2AdKV`8+DE-v$yyhlhIhE_-hEDaMU$R^qtak8SH%!Q) zu!OFuETmRT+e7zRTGP^F)N|N4l*2XqxMm#keQgYtMqL7SPfTuazVRUOAEZ^*#uyv> z_!-C7yLV;~+5DoJZ9U$@M{;U~bF3Ts#xY700YMJk@ud-ts6y+AuS%wqMkMW}FrKCG zgN9IPTz<(+f|{H%{eCOWr(Ce{DQkG)u|_jKcvTCLo~YLWCw1>P!%AHrRWUD}P1J%V zv(RI1PT{hnJR%3nNfxyr=Y3Xkg%0@xZWvV&kKHl8IST0u>W(u~9yI&5@p2C2NM*@a zypYB}!q_pJYmae*G>|i#=!SEj+1?ky`7l)u0VypW@Nb0-;6e-Yw+}3WMH;2)IZtNq zYsT|)Y9E_tvzDNI&ba-|#Imb_TI69W(m1{2UE!=Vxf3LDxRyhURE~4-AF2BHO)s$I zEFWaExt7E&m=#GWZ#t~*`e0jG996Dy3y7QdToWR)SOZ+i;A0Q;NoJ*Jc1_{DQ2rt9 zH&lRV47$NbQm4Q0fIDmj(A^XDo59uH!029E;3pN-Cawj(3Jvtx|&+M+6Oq71ewNh-taZuGxq<|8W(aI*T}Gh8ee z*&R04cAJD&*~lwFH1+)5aa6rYN1W2|y{6f9n(v7{>cXCJNE+SFP@8TC_wttN`I~H| zx=!mkUN~C@^hD35c;SL;9Uw)hL^A~0IZ*a31qQw2iB!%x#q;Uhe}ak;GuI@;dO3)nA0Nu+#v-ryyCgZRFBh&kTI?T>l+^oCX$*kh4^Xex- z+Y4S5(A)!eniwTBxjifvY^q}RZKt>(BgHtqJyQAU9D8PHmz<{@Ja^N$tpbvbGWMzn zUTbpK3O(Eq9*hMrjzsq?PRonU=~PVf$uvH^$`Ll+Im`+g1c&pYGqM#k?T)v8Sm$Z{ z0GelM8YpLuI%;1S#Xt|k_Ei3;iuxvD*eASNTcm;}>_l zZif^4BOd|;YcnpIXH)5Qoo~}wOi48AtOA5K9$9@G#1oHm&CI7`AM#T6C9~(GmNQLtbf85@(`*S@kaO3uy|urE9`McskZnR zxKaWC=|oi_m%=yC&9S`oDu1?d_ECONm3x!A^cBDR$P-RQjH@lAF-p1X;6Pt1(wZPH zn@RbQU%5!}{56$dFL9@hA5XHDl();=?Sfiw*uxWcFI4q0v!vQy7~k2{douztN#}WK zsE-(Ai8Ya!!JBGk z^^QMb9qsp>rk?*{XE{$w^;Ir*!I(r2_rl5|#!-1S*zC^iZHH;V#`@Tu$ydr`mx`s} z`xI`vMh_cD?Psq{riW4Wj@hgn0nb@%`0j!>O)%XV8QsxLJz4{k{N7PEs#Gcz<_9?|E_0x` z1Scq^@$E&E>De=mb3GB33!A**)ErH{QMfgpsAg+`eB_?O=&fL^WTRR~h8vZp&jAii zXKWa|1I=FY2Pf2bg?8stE1A2yO?xbK!nL+2CeKDi=;YsQgw@YozNy9b~_`iKJ?#PcE&lKElto!9gRZt(kXQ6BH@GXa!EETE80UGc#)}`u;B$~ z{{QmrC6}-C0p&7l7UqOwL8b#;R1xl(Tn`LQW78+*GhcL)Wi#0C0s~bW3;65=$@OVU z?o%wLJf%VA9#Pj{SiYUgsF1jaMY-3jj+K$OU`1(2i}YckuW^4SfpNMXB+bYyV!Eyl_5_?EYwP(!kkvRA4Y zkKf>6r2JoN+HGf#l8DUU-7K!rd_Ribs`@T+R2HjU;BX7hL@`V*&@9%HqE!wjyzp~9 z^mRqkX6R`_jp}g9;xFEqon}_T8>aE?HKUnlYa@fq6>id{RsOzo$LL(>oz=4OSL^AllR|L-c@o85Q@50>K+1MKglt z2zHoob8+_8Wpj|Si65KKt<4SkIynC##oZnKm<#2(5*k9e;Q{6xe%}y7tQgY}MY36` zD#FtFuLu6nsq)$Ey8aQ)-3sTuNS!=2AF!A^0=_Z99pm!A)dMb7VAT|pMiu4%Fy22qY$ckZIFX({M>GZuay1OB!uHs6}le8GKBu%S87K5DqXdSyVM?IMK@+f1Gne)iu)$^u_-U%H zUmLHl^%k{dQ0olWsWymUPbpyEO_WbkL6be|QwJ8Uk~KylRrU2zH-o{sp(;zpq3fxf zdefZb%Pw<(6+7hwcEziYd@tvuubGdCg;m!TzQMR{!NmF~m&u=sm@=|*n$cbDnl7oj zzDU*HFVC1KJl{)t>$9@rqdqr5kepGdzVT=cXd##q?^Z<1be2^jn2p)5`7Xm$YekeC zcbVDJk?wPqbaGu2?x?N6K^KTbJ<*D-EpRf6eYE|Q%8fapdSke(&KYcX-wekS&Y9z) zLL{@@(fSMhl*Q(Qa;o#S|6)OOedu;BT?Zi=VdOV^yA9H*Zf@Cqp2_6R8~h+|?H!JE z$Aq{1uF6o&y9Y{@$9h+EZ-%nKq?(X6eykY!r*W)ImFk9Gb6OTFUgdq@(q%Jp^>}F< zuo;==GylC9=16I&hJAK!ua6Ds9IrYgm3KXH=KoIbO6gpwnJM``f-_z5(|waTul0}^ z4?NFhTq`A&0cSBcNWUz$sth^n-us)-s~d@|k;!3C*-+b0SLo@2@k);Kgp}tqp19)y zISi+lH0}S$noyVRRNE{;?DIEk{;$&6A&shLi)ZoXJ!6Qv|O=Iyy-brWR6aJEIcHG)pp=gW?F7W=wx$fBP4o_FiE)Ufq z&8lH(Hb+-P^9*h-0{z+->^!40`5iN*O+_%$3VA6tuG;>FHhpVZZ0Bx*9d(-o{$D=~ zuWI^k!Q9rTagYbx)7d?ibu)SBCGY7Rj^t!3`aWZ*3pBj0TsTM0wTH zZjer%{e|lt{Pc*I#Laui2k!X%l*8O{JBdMV#s(?fK24vqg8|j?$;RuYAl^d$TzHbo z)HL4DU`90SXfk-0n%T99Vh;~o{7kWHv@E9YYkFbmMs836nVIyd0@rj7D2S|7Y6PE< z&OV>mU%Q9*>Ft7FUQ#=Q0S}np1D^^)8{(gK80UlBIK#Y=OOR?Ss_uce1+$M~crt(3 z${u2Z&8MuPee<%sjr|tzrS{oNTIa!!^({5!{6CG6pBOk3 zFXNee7`tn{mF1X{#ku($g|q1S%`vnzx^CggqWGZ_7Pw>e6%^2Dj!%yI0KZflB2u&;>g;AxtU5O)UCfl;h(p zN4-FTU%26KqfO0N%HQ)Nc_ibD;6^z<^T){H{F>kF);-rq@gCCUjBSS9kEo!HAD(mR z-|&0G^zJCF@$r1le!!obn*E5dRfhcV%RX~D9X`Z;?r3ahQxQw{V}!{3iAc_eDo1hC z6WWQm;R01|V@qI=CQ2pIr8b91kdJo66hh)3d?@4SSoAE3+7)o=J9jE&_#LmVg-n~j zTrdfGA>O!kn)8=&uaK1^`0HaH-N`SXdHn$AeK8?Qtv>Tq2>~;eM1v1GajB%`buKLF zhdtVin2LS*ajzXBiet+%_AQ7y>5lEil_c(HRs(tVvtlc}zQLL0(Rd_Q6hY{66axZ$ zEGfw>c-XLi!zJ#8@#I7v4rlE^)_BPq^LQ?vSt*XjF&wD>xTDQz^mE0jk`^adtc-w{ z0+O~sW}(5SaN6yE9G!Q3kM|zM&yy*UDF{JgMT~?X_AF}GZc&Ps8dX#kr8SCHsaaK} zR?XUK@4a_Kj3fkEGDspqWIX4d?|r@Q>;7>^lix4T^Bw1N&ikO<1zs$qzVpMXP5;|| zYOUopr7pM)Su*_mj#yQAJ%D~D|K3@Z~gzQpD{{u{&2nXJ8uuSy_gwSBJ? zoKo!GV%c?JQG0AS#UmmRjzMH7_Q&9^FI*Q{Z8V4+i^u1={xj^n!^@u=t9E_Sy<&NP zg&qIQu;qU!WRwCmet7f=I_fYm%hIWJucze^%Ye?-y%*f_HCjJmnM3?Di(?Mjl{LFu zWDgHRgZ`}26D|5uhoytV@n361oy2wZ9sF%I^g)M>m=?wSv8exm{m*ddUABE^Uwyzy zxpv*x{B^TETDPPa-* zKH5ZP*dM3y^N%=JhrLqxMzE2q2HITj2D0P$^dY=s*s!HFJC#LEVcloU0%oT(DZ|$K zucpfh4RNFrcZK8hJnm?U&Q0Ll6dUhiehs9xx5}tp`e!^(V6i?(OJ?qR?s!U{$-J1! z8VBr&Dg6DJEl&R2`ozN19aJRwyk7eiLlfZO#j!IsB<9 z-#ul(EqiVn|ND}zu8@aHo;0m&$1-_Y`%MBH#F))JCJoI*69>7Ne&aXkS(pcTitjQdV)$$vRK)#xmmETX}nGZNJ zof%cwCzqS1^FuQ37FWGxvl#x8%sXQsCp=Q&k;Y+7th&$Hsy6a2@`ET=N}zuvrzq+^ zjkCN^{hd9tBs@DXq#kN^!1(sKI0egVYNi0Ourj9LncTUjG2b1&VK|(_Ufb9*lbhU_ zsw9gu6ZIZRVlKk}H0|m52On8NZ9+ zkgv=Py~|2l?1QXKEb&InUc|%pvplKe_J? zzkFz)OJJAT_V>>CWeY1e!O(x$Cm26%Ku}$DxQ}l{k{n|-E`@84aq|t;2PBwe-CfMK zdF!eDpCepC7+wj*Pua!%;Jk#ht72wVgevw`#hM^maw_uf@wWqLd0EA;h`&R(*X(kR z5sjE8vEo-dOX>LUIk74lea~;p<9lC31Y`bQ=zvce0Ot21LY)ZzgKwR*$p1Gs?1Z3uLpf@h*sO*bI zI*R{B$T&H)+gHTdEv!=;SKDDv5H1hKB2B^8nSSKM7ce!R&yM1O;%u>|hsCcu zTi@aOek_sA%d1#QYS<8?jt)M==Uy29m&tnn{lt)MMqWiw5tsEwv3H`QV236P$-G<) z(?)W%7ivGXYpO54ET`2%^<+M;4;KgQFM(|zK_}tbyYTfJ{@vLUVaES!iXtlr+}au6|De7g{r^6Anh-Ink~Z7D32FvVDqndRu-;aR&OaMAy%nW2DU@@OB_=j3-0s0 zA02LT@5hE$w(p7kZziYoFe>_;H!S6g3cGPp8J5mgOGk{_j~q3*)U><{4T9#6ME<_O zxD%J|uysmL0Dl){u@U=3pl}{1*MY|x_|`{!6uwjnX_BRRP5F7~dYjIJFySGyzhuwb z+~1HJUg$b%PspO{Wm~h4X!VJgGhNtCT=f^UCm1dl9~Cp9hS@J@pU@>T2L(HS|N-U$E^JOD#i84j)#GIlsqMPzSejpxN77O)ut1&@Y*Pk#yjT^lz0Tx^WEgd=bcfiQojQ)=9 zPdIKa>$&5>V5a(GxW(Q9_;(oVhT}qBnCyrj&U2X`4!q>|%E<kW4xwWPzw}3=blzyg zf^1H(C?ZFA9eM@f!A1K^A5^@}+-gYr5|4dxF~oXX#J!g>FOumA`1vmVe?smJ!vq+0 zlfO6Tt0eX~Znw|p>A?)Gf+yec`#@~!jgvL7`!8%Lg|W>o?J3?7)?Uq#yTT)p8)8fz z^Y^A)1560xB!2{~vrCk~rXlPfY9fK1s^Iry3{Lm5G~aBw6fS9^*cn`{Jef1) z{Ts-7UYPdEULh8GfqgL$4;C=J8hpAVI1t^Up}*0ho%lSS>mOlQEJL>9$X#B{W~1x; z&YM5q4Tu~B3$1~CgH&d8W22D4ihRhyX89Z?+7Ej;u)?^&W<|DOr=QCj& zmH2S&!k*q}^uo^f!var^S0mLsHV?!2DdtrC`50Wu;-GWzl%><((q}w11tYHW;v{~3 zk=;7*BQ-p>}vc}zuRGZb185kt45$*S)5j@MHocOxw{@8%Y~AUb+6d`8R8xpTV}`w&Qyg^ z41aEH2yeGr@>UVY)~6KUb&Dx5-9tI$Dq!FfT=zv`9ZOpD#}g2&|K&==7E%A2{hlj( z%8$%%57{?8F{dp5Q=`OXW<+4zU3!XZHy@w*;;%EPDz|V0>#08bG<4K(%EICsZ2cu~ zJmA=?%Io zdw!jJzUI=a^l!#m$$YTJF3ez0@JA19Jizk-II#f7{P8Fqvn>4NYt<59^IL15T*tpc z0`l|?{63k7U)wJn&>MVQ1I-@WCF)~n6P|5`v>-FA-*g{Q)o{6|HOvJqpJGA`D=k8U zNS4cBlUu|T_K8)}!IqrmvCcl9&%HBQzdVi?N8J+mU@?N6a5fEl-lz)48kEIVo>td* zUax^W_t|*{OTLmlgMHjEtI+liK}v=#^NjO!{?i<%d*gXy6?$4tt0Jhfr8!H>qo@+g zqw^3G!;P1@=nl)!-t&+-i|zJe1D0WJKlIwnnHsNaJ1CAb_skA*BFMU=4$1-6KdEf@ zFBAz+jpbLdyxy6?Z#g-HU;Cp@uI&?ygK?(!r%VRlbil@w2x)}El9tq}->+l6dT-T> zn#Mt&quPBA{+!KjaM*J@{VB_Cv=_bOw{4kG3gxzNt(Y4VQ0R?8_wk1?l0&WHIlR`x z8W6{x)v@FPx9&BefLU!#(r{ZjdX>eTKkbTvxbicF>8RcpIUk_KaqOvzx!LIKiuyUY zltaHbe38N_*O+mGkzcUOQ+ltq>!h$}rhVQM^G0!fX(XLte=i)F0lPS+SF%RQseZuv zDU+S%;^$MWsrs#G{J-Ik`v2d-#c4Af#3e5hHJ}4;GN(hrK|+$ zdmmfl66J5l&Wjwg6d}>9e4I-k(wb-ztd;+;57``&Y0uPHWD_5?hDt8VHb$LuI9LUZ z6VcTT?I&5SoiO5Stbf38F`ynZ){EB?_~xaZ=71B4_B3yN5WpWwVw)@8mdEKO2r35O z(N+~PZC<0)O&&ap@;BJ04Lt9%++p5+&V$eG1z9Y<&Cc{g!+9)ffxGLtqYfqvK=nF! zei(6OF>#b7p8UmZ%t+^Xr5&Ge=mFMB~;y9o?_Q3j$_q$O=pI&+#QO@W;mp< z@FA?PY}!4pltNe)>x{S1L(CthZ$6iyt>*gQ^lW>3vtm)ZKTliJg+ft!`FsXAg- zVXkJBk5N67WBe^WlY5KMMwXab@HxwgKk;ZJkEhzw21A{g3EZw=Xkg}`^9jBZJxAsT@5w*Uq7l#tNANl7c_n^ zjwveG)_PD1uVIzV;-_m2xzV`?=A^UISDf&INhLWGNS<&1>5lg`IjJ^&j^U#)oSKZK z<*@#|$@HaoTY2g1(Z<>w$DKjwf0T2U()}hs38brN%*~9`;CYJO!2^eW z)n)urR-L(4b}`gH2Im)a*oNtuZ1;rAl38&IujX;XZo5Je_nfgC24cZ>?rH>qyoOf9 zuiv7wuY3ql0`d1>t*&qQa3!9fW%e5up6Apbnf`#+{0+T%AZ-U1WEI%`OCm|Yz;N{V z7>_ETST}2PIqc8JxJ-r|fmaGwjKjz@esPYCG8pKm>IzQ%#m;v}^KW^&F@E@->w#1{5tIRM$Il(cMC0DtlEzl+Y1vfu#p>z?s9c4{_>CxVaAVr^NbzhgpYeLu~_7WuDz_cIjp-4o38U^KLp%jhuM61i?auF$}9S0*eQAJ7;8W9!1yCv zT?02dz_BvUzs1~AaK4RC-q5$Dr9?xwl^FDl)gMu<>ro-bMxJxuuI;Ul$%tB*)shdIkfZn&TQ4C?-~7^Su#c86 z{LmVk8{zpuWCY-tgSAUp@@J@-!1N;!Ky^cbs?Ry@8!maGeowo^D{hIiPl``9k9(`4 zS|lCH;Ne&U;2QW61rE6Wtu_7~s~(3USD$N4f0yI_Wm*QShH{>gXxVo4QaF@qe_j>Q zk?ha{^FBjZEu5l!{CJj#FohScU~q!^Kr!_(OKB6o$I?O6^sv!>`?|th{n@w@Lce86 ze-xLYS}ivFFxwkbs##J*2h_0UC$jb^gx+FNdC0pWZ2?owk*YQ;;D9Er!r_X zmub2#jYWKUOgfDb%9akW%Dm^kv*@MI+ZR)!8GDsCo>DUr{pWtK?dHxHvd?xdhsCEE z-UfO}OVq{m!&n-GkW2U(@M&d@DPWa<(Dji?(_B;2liuY7ZoX>w%i;b&{#*)&!+5lm zsg}4Bgr0+N#uHN-Stpddu5Oi=acedjDBRZ%72=q;hvA7#sBJQ#{g>L6-7sLH>g~|! z66;1F>_aTBfxoVxaT%N}VI?Vb=w_+Ss(w9mi(+&%J3QqFX?E)bZZE}yZYb|d+XvnL zPsIV{`dQ~l-~P& zd9nhY3Q$=YZ&u*E4+>wRpIjDq@VV?yXYsjkNwpCW#kX7e>Mynn7EGph zDEb`co*FpY1+S|kI2x16p;!Y;DtDh1_~RK^Bn^;#pJ?Cla`3EHjeWqy;bAY?NlJm!wk- zeo_G~{N3#s&JLZHTy6?D-e?{|lu|9att*z1E8rLu2nkXKr$AwvJ(S$R7;n$3f z0ulS0u@5~Rgtp=6y9={a_vUF8SBmy9>Zh>kV7MpHJ(lT-{B#P{X1m*qbpiJnTla{M zcJV?OcB()p5FryFhtl3X`0@?!_*$1T__uHOJhq{7!Mk->UJkkW@G6Gr`RMwB39GO$lcByisngDE3UT0DlI_UZ<+!m7TCAX5 z9WSG3Rlu{+m{Af-mZ7X%O!=5B0{lkA-sfl^G{4QzzvvUk5rY^cE%Z~4cf-CQmi0pL zV>SxG(Jpurh%=YatOT-;W0+VUd(k74u3tgzx*`U@O{G1V^2+W|l-U)72XKB>lv=}$ zRgJ(BR{>K8n(zE{6lN%Q`2tPVRNfpXu5-Skkq>xs87D|2|B4gebC^GK9Pr^n?yZ1# z9;j9sV}{{mDRjAlg~d>8BRn*J*?{?(JX9Hb)HHF4cVrX~=11>&uK_zMuCm?DksXQej3$1@b6oU%4eG?xU2Th)@b;MTc7g3czRAUQ*W^nP84xr8(#6kx#OG} zgoz&LRTd|=U|~soyAhYw*|!@nve;xkK22wY05ZuO`y>4{A8E{9VxP|7YDtA()2}>s zM$=QLoqqVPEXJHL?v!Plk(L%rw@c9+l)PXCJ7 zQr5Kp6d#W>>Zm%6hlM;fAGMxSjd&B{_$-%mV)^$DmVH5w_PmzEC(XFr4Q{9SvLZA| z3DwzH%}RbKHwU$pEIwmM@-4<jm-zJJ7e>jyZ{NdLQ zJN$4;cWq^IS7XzA;$Mg-kkt&=6FDP>ei{7YD-Mx=@_SzN!|aKSE{_At`AD9kVz^WV zeSUy6{KPFdAVSMXR8HW^K~RHdmG_(y!}v8OJ~n?a$Gv9bQFKvuMs9F_%*Zn>A-|0_ zom^2fh(^+pBtLO2?Y|in(i2?K!SMwkKT@gz?vAGf&_QF2h z+R%Wk-0<6sp;7e?i2Up0EM~O7SBz7=-L@ys559f z&dGj%8TVxG8Hw?!>{u4Rr?SpyW7r@6C zcc-HHTYmB#7Rj;G3J0FCLkv5{bKGJUPi4Q}-1(M$HXChc*g*z~L8qFD68L^FM!I9q zO4Fz^<4;V;=IG|o8eT)i5E&e}hL2uz|6=Mi6*`2Md=T?56DmOt@eitCYJWroV9!iL zPQE%Gb3{a+gsJI_%ws_uXCLIpYHS?IzcaaW4yTJ__77is;xCDBzL?P&$J7!0E%HSi z+HJhV-+qSB7rYpZfvFsDhr8u5+sxr21AN0>ZipJE+G3o2PN|wd1R**QTffE>JuRak zw|tdFSe46}P0&AqS7pn2%3oGu(J7sbE5P@*dO_~7TS4I?z{5A-Ue zx2kW|dJ~TGavY_zl?2Iy9P*ZHrkEV-%T=u44;9kAs%$_d*D4sE?KXc*Q*E)>fZxL9 z|B3JO>Kn@C%4B}C)pXBY`rYs-!o`pjw|2W}?weh}u|6o>6#YGr{ewy9SNIG$>L==e zjC5Ae%d3BnV!xM$A01BbT&VPhng{jNH*=MqWzi&>f0e~cZ9U@2w?D zD5>{LOp@r)c8b29ebS8TzD;NK@;IyYr#xP$cV5`23Fz&N*w&~b&SWY0zT&hSrrl%E zK8{c#^>0IIyZ#4lbsy+4@I(J#R9DI4C#dR-`h8GO)V>jTnaL#$kgvJy1d82l_#=qrC2Tr*pW42hZ8<9%EDa@hbMs zVg3%&AGhNSyOcm^2CtMsk1%9w104Xho7I|P`jY+#N7dI{prt;Wh4)#ghOeC_%QJp~ zWLS3E%$*+Cp3AwVu_6#lyb-H0fRY+Lur-gD24bq>TGi1jnKhDlE0IffQ8SChn|Y&% z|6Sn{brYQ9E8RCmH3>jOOQs6|N6+V#*!fSvBR?+r_AaCwHqAhxHd(aMuPf5>LpCP$O@o->v+vxp~m(#ylx zx(=0sR{%cMq^Yq!l0)dRSG5E z^NJ7Z1*3*1Tv}m_lkv5_E@am>P{V?(4)F>6J%Ns?e6xoizT%HZxul4>yJuZQaBxs zotm6}2F?9{ZHHr;?S2GN!t658^l(WGJIIf*ot1R5T}{!7mYii@&9>qhS_=2dV_+$4 zYK|Vt`i;UV3t?SwQZ3{qB){{VxLZkFd4cLq$`nWUEkFF50a_Jh?e@htr7))?p0+_7 zA4vV%BNAhGXs27*5*6|cp7gb}$Lm~>%bI^MP3*5-w7gLFIyaWYoJ{r$#itoy}PGxi7kxFb?zVHrVe0|DF(Uq54>8 z`qQuqMyK)LWHwJ@iF5o=8RXr@$J=L*S)$jTV%t)1_d%T?9BY8Fr4cn0g<9L&;j*$z zwN0PbEI0V2@?a$0U+~VqT$e?cKlqynZ?UZAi_t0y@xf&c8hmiQ18#`1Fbq*PeY=^7 z|0FkbOEET>?_V);5C6$!Mn)a_G);v!6ax1Q2JtZ_x@KrH*Jz^Cv=#{zT1?{tOo>qd~ z*$4dc1)J{U#%xwtZAcXN&zokAgO3@mzjy`wrigVTI4J`=64OY{L+@xtFN;VrhrD5j z4AVH{`-a008YaWVznJQV?ukSoW_v*Rf`j!KKd`d0v;XZ>k{ZFB6K;_E`_;rB6Y#Q5@_8CF^)*;P5BfGYO!6PTz^5WDcgEvPwyuXuY4rC-jdZTQ#o(6= z*~{(kxkGL%<(==ckJ>=;4C3RvIvDDYPHjyCPV-OjR79h$7$mkNS(HX4p0t)W+sjRF zx#K9WI-tpZ7Dzplbmk4mVEpcj4?7t~`sx9Q$>#@6%^fhRDyF8Hj9&H&Hon5&v-onY zVYEKq$L~EbES+b|pk#3~0R5o}N|%Id5A1YD_b;HVL4F{jb%xKPzn;>|{OUDN{c9Q` z{@BgtPMCU@!_^?;f)gbW9}ZVfd^QNuyHY!&L@u|~hcFI<%3xO-T^{nZ+V{3|UpDjB zQWKD!G3H5Ip2EzMSW^zRKax6PxF5VfM{o6Xcff943_Vfo6`Q29L>Bjnc=?*29bf`k zW(Rw^V3$UO0t%JF$FetfLU#`Y_Q4r(lbRwRiz&^q;H3c?yh>!5bNp4^Qk%Hz9q*qp z?T~AtIkg1#z2|6uo`cJDFl( z&@PT}$6JXxx=@6mlR7>$aCgSxRv4|mo9;N6O|L-6&DboC2PNp8;J7Sq+Ql?E+ix(@ z1Cc7o_Qlw87+wayM!-$YHJ?c@13O~X|64^)W}0#bFEtBa=Roy795?)>P5-Et9b4kX z!AF#eIQ8#!Ovc|$?a~go)E=Uv1U1G@#Yk1vn#5xd>6^jmT|AY;66YzpRHxfi=gu6B z<4a&+HFMv8*%i*781Sj-6l+-pcXBw{5Ak9rK4;k+o;gd=6#}+$wKBG6_=hLPykVn~ zxLOL&)zr`e9mVf$j)EcC@Ty~RZouHiiB?SPz zaK;bA8$wRnz`k%4`J^RQ7qWGAqqO|&fWGR6h~yV&~& zFhUUNg&q+Y?u@*S5YbEDeB^6ZcEHROCd#mv#=RHK$|eegRO^dp*wq2wWwM;;@fG3Y zhsDhhqmylC{NsW)b+Mt4bIU^-b>d4t&SCdE>>)D1cKR2ZmacKd&^Mh^d{IJX3~$s4 zM!4K5ZSau`zUhOkLQbq?PI^mKJ&?xF9&xPZ4yTPXQ!7lO+ET7lu3eRM=$<|?2&;W? zpfh@@{Y`9nN8Am=n|z)r4Oexkr0_fGQD+RZrR{G1F1*rxdU_%{2fr9iW$ zxL3f>Oxlb69&7N=QTSxF(!q@ zb2;fGzj?>w$0;If*JwJqVQwsS&mAOez8Yle!%O8U-7(J<&FbOldt(Am&`tS0kG^92 z+uV}DUT5SoVRtoUyyu8`wiN5q1<4|=G{hE9j8(F}7&f)U1_gPVVwwnJ3Jd3Q{(U~p zXy9Q#*O1S2;-eS591xh10FE%n9KkhNYk!>!chyBv2Nkh$teH zl@Dti#y>Ce!$R7U^~BRFg=ja^fU}}FIy6UDS1c0eP4B)2C2Fb2rR zn6Bz+tnFzoafLpZsf4=^4t;hxU>b zHW#hh6%*CJ6M-YeQKcnzy5LwROmx6GjimGVUm;89GWH(d=kU*~rk}CHIf}0na*gtL zEfTU!_upXn$T!;>L%lGs9tJz1UIZ4}yzPb>Is77yQ(tk}IhM}m`a{%8yW~DahODQ? zB4>1{Xc#ZI>fy^`nAyenmyR`uPoY7AG|J&`8K%YZ%iCryqnvnavD{B^xwu|g%lj}&42LwL1C8M=dVd72IO26h(~UJOjoQm*-Q$cL{~2+zbWFq=Z4I4$-yw9 zWw!se7*4b^Eg?g?BTia+8Hj%?lcC(*$xnDzWP@uwk;kb==xDR@J#Hz6l}^~^4gVln z){)o(t-MS(iiZo*>!PA%tn!KPs0*x`L4G^Q3A!~OHx!HLSh`#2oy-;e(&* z!L2wNHN<2WT&spYV(_YhO-zF*Cgt+`6HF0LINCU1J3rt9@#o(&+X*Ww;kKqN&CR|a z+zNw%;(^$g$JHvEP;2K4>im%QkbTA9ILUVJIWdYNxlPOG-QuRbF2M_hwXoF-!)wD^ z`+gHchHomdNaXf(L-y8MS~HjRuPPeAzwVkglO@l&Q49@F^O}^d4j)&HX@`8#v%^ep z`?vmREPX%}QZ>0G@~B!!u27Voi{}kZ(SAusR}2t9RXn2dW@Q*!6aRT&Su?}MMK#^7Sg@<9}TAcJjw?gGEcIO2+D4KUgn6KY{LIlLDBmg1oeQAx=p z&VJ3mZ&2%-Xf0|{=^0P47ko8v7gsI7w6|=iflBV^)e_sqD5ztk8x1>{&*EtYrnLGl z^&PgkO{XHRyI`8FRE4wH1H=JW2`K_nIx^IX#De zU!aH1y)u^;@rxvG(G6N#kQ>faLQ-+~G&U01$PX~x1?9>hw1~n-w=3XvRU0eae~r@C z&zxpeF>@k$#0fKPE|z;rZ$FJ#kjmr2BzG9js zw>)KS<+F4Ua>Ow|Y;i~1I%w*SjJEJEhRj+R3iMQ7uh0}U9DL8;Z&P|pt1EO7)#nr^ ziuRnuX>OPyP>F8AwG7dyWi?#ohVz(~#mFJ4A6_0jHm&2)$2kv%4w} zikMLxp`{TdcU%oa%nl33=Z-kk3`1zP^<{79pTah;x%du8=Wy3eu6f5T(R422`V4`UUSnhq=S5MIvT64?L<~=vL!41e z(y=QpH-*H3yvBxuA&aT{1zwecR=>R}Y|Z0Oig@R-#R;RgHc(m8lyR1Kxo=lHm zeBzGJ>*1s;`ZmNIHAncOLxCxC-1d$E_t@nPzm7J{&mnht?LBM984Pbp0fd#n4L0=V zZ^W|G$+kKc>z-B3I8y$0F$Ci^3EZhu?lq$XesP&iZ9}R3&vsrpOq3L^5L*_5y>9pB_}NgPG^S$>8s-!uL$7s@5{f}KQ0wb@5MF#yVZJ!=HzE5n+Z z&mu$eaj}bG`hu4|B1So5WCW&*Kh_-Kx*^su{o^;ZZ0axdg2ko4Ut{Jw zZWTw!rpsN!%siND=sJ@EaM&GA4KPr9UwzOSYo$yA*OgSV%{^$i=;F87J&)_ou%jA( z9`UVuud6-iW<_<{3N3StY!pO>4mSx7oyI z_Cp@m)v`Fk-LayIfp!+v!>(eO5RA*ZU53C>znkjJRL}av{N{UaP^+BpC1z?8{hVWg zO=|LWK=ZN~uTySyxH;ij3wSz$wGdIn%}#L47=jmipgA8f5)%Y zF5!zG+~8jg2i)*)m|4&NtBC|BQ<^-Ie5|pK-iw(3cj!oWeb33G@!7Z+H+$Wn$OYK&1~hyeGPE1K9*1QG^%jK39dngc{QpY4mo0QLqjym zEr*>2)YdQ~pHou}mF}1O?4QrHi>625--kxi)5`EzOr(<7;S6b>heWHchX6+mjezpt zvx;MJKJUC_^m|sl&$apNbro>Jt!-XYSioj_hG`Yi zJ0)LDjmz9$z}#3)B*U}#21t~UtH!`E%y-7Ty149s&3elmAn`<0jWMzjsx zlC?Ewzhh{+Ph(6U&2=}daYDt)kkj~39fYZ)As9QwF{@(QWJ*gM(@OG~QWr1YrgYIa zw|K0;7+N0bSyhFcD$3Q??TD4NaNol5CgwXYQO!`=ezYKMTmr0C;OuDA@o^!NQX%F_ z9a0@wE-fd-_+qD%c`F+UP$|vU3A?Hx$N^7FBZN%Iq@_hQj^1M2UFS^YJ03H&kTQzw zu&J@Q&asoLnBTd4gdxLsjzD?!i1}cOO}*WxZ7Ld&uU4ig4%hkTI`698DV~uU-wUkg zg#M)sh5nD4I4?eYHIrd4Qwmm)ue%37t(GVw?8mVRDEt?c@Ks?VDaB>t^iP;syaGQCL4XgaVAp8zE76`3QwbVYK z6}}#Z0Vm3vjxaF{b*^ilVeZ;8WJc)cUg2Kb&~-c=(5=8wQqOwfdqbns$Ssj9b*sNY! zoAcDvmc|aETSu{m%?kIq9;g`0&0+=T@>{J-{z$ekHWWL7@NmOz?ok0cz5A%sMBQ+& z4Ksh#BlB~mZ(Ic&DygkGyD$UsPtZ) zl7P&pA2~wTiVqyntOEAy_EgijhECTvzN6;l(T$v!&zD6kdTx3#zKx`pSbTSxK|Z_3 z5jJNE<*m+_G8&SgbvWAS+o+EoWYcQ++UCO2*hXH+GJoGLkEok?)l1B=x$YqY9Iz;Z z9UY+KR;-2VL5A$@T_2ZpDy|1#%iQdAPKzw$HucKIbC6Ci_lyH)!fo~u<5Vy%0SO!o zWnpw#JO;iF#}o%#QRGdXN0m*FXJ|1v0dLa`L*C(@v8$=wNmHBLB%@R;lp;so_Q7lm zld55cg*NrfU%Ww}X(+RGEutwV)(LseL$;6`@Gj$Q%77grs$@3*&`ACq&2I&fEi4=jaRJt8zrcsj> zl+$IoER+sj=!{xLj7#K2br3u>r{aA#8CArGapvwR33t6^>S8_ACR59(I;TTXs81S- zI1BgP@r9DP8C<0T$_GZXJ%5J{fXfffh;3aO)zQ<<8;2Y)sVXvoUJ+<6Z%UVNDA~p5}LcQVXpu#D$|Fxup*BWB~Jq((#Tt%q2@c;u_$; zyGFVnaG%rEccAgNWiF2$q`Fr%XX)eh&`fvyswh#!qX7sPy{&+4idaL>s2W&qQE%(6 zTWq1W!8A_NGX}IJ(<)(}zRxi8WTr%*jtKu%pg7D_n|kIoUeW1gL=<0&vK7TQj?nrb zT}ZWe^BvK@ky%QiD)Zui zN)@1YwpU#oAa7MiSGi3pLd=$XdJ**ZO*Vh2*juy&^uEbuqWC5;MJ;J6_p}*b8hYbA zYvHdVUTI|ZtiLNm`&5z}sP8SCI{);$&$AZx-8Oqi&Rt4~tNVuPl^E`ceu`1lz%xBL zq2~8r7lNfWOO?iGHFRWizMe6`<;dh~Y%Lg3EcH&kN~WGARTUhyIinH^i#V<>_7^cI z93R^Tl()FZ6qhU!8di7{V8a7ebwrz+?BRfm@$8|-H^Ey(L@a~9wNuxCDDj`xfIi*u z5J-c+q2q+jereP{e{B?VTBxf=LK9gcM{M<>8?_)G(c zLPJfwkjQlDEw?EJV0JXe$vgDK{N4KrjcfCIX_QmLpnCe0S*wd=X*+@FqwZrp|29SX zl`2?Qkp!UIb+*)}d(3l=IGN4&j>c9uUJG9}Ow`RN42i(|TIR-`>WkjCktU`TabOI) zikp4cv;`GKo2Pf`bH?b+b%KXBi%Li&zYB+>01-+SOX~|Vd#IrLUGxkj@E4oK)uRg3 ziR3FCV-gwUgrEX`B{ysU2I}Nc9e-FDR@bbMdRijM-m=MQ7k***()FX+N>?`VD)n03 z=S8U~YBty2A*Gb;Umf+OOVq)NLI#GI#)Qu5oK=cE$Nc7p#aEQe<|g|{Q+mLK`iU95 zDTbIx*G{I3s)ZwJgkyq*f9j$Kuw1qro$eHvDq=*uQFS)l<|NUWZ|RZ;X;X5keC&kp zfM3f)d(*4BW_~9g@3I04gxE!&-PHgURW$#l&ZUveAuHcDjX46H4H6?X+uVa3{h;sv zRS2GI1*i|bvpp+A-)(7k94|7}+uFqo)&67CU5%+a%cZ5@A%yvML__zPx zg%_#mwKU|*da5mzbbn^{>kiRo?e)81236BzxlV)@$$Adh>MyqiyD1%0$W39;8NPRz z!BAh(&?KKZ>1?g%^FBA}x_5_rbS{ly4f(ZRai#Z`ymM8W6>PDcxp1P1?rV~q^c1%y`2s%Xu zGn-rPnKbp=xzC6@CG zIX&9!9ebjAN$>S@^ZzY!g!J~3fjF)=F5L73{uctvHfH;L^+7u#T5Xa^)D}PTE;mRA zxMlV}oyk5>gVj4O*Xkt8mW4UN=36SPXZlzlSA`s#u5M5dTHz}$5;Hm0+}+ae@w$aE z_gG*1$ZMmF{pI;Tj?O#4=lYN1&)fUlaNKZb+_Fd68Ods(QW9lnrfi9f%p`?|j1Z#C zl7xg`WRpE^Ipek)+@8FC+sm-GX#9&|rYf3Dtgu5I| zvr4#Tr|27(?B6|*Kuv^R$YO%#5}w#C23L$Rd@eovR4E;6j($NTUMf@3X211E@qD?8 z&R3{!NAPJE^(hu zty^57TqMQTYkMV_t<(rHNL6}8V=UBD(bOWmB{6c#NpbDYkcmTe@{VVquC{*zBrEr=1{}-y2dTMt!hNIox?QyjXWRvT<%q3mGN)sT zWV-s~YufuYQ?m};P|eZ2LgXbQgb#5=-!f+9@>T`oZkWz_#!dd`jGynAzWx2jT%-Il zUwq<>(=n#h`nL(XR`N(4G%sa?8n(gQm@j-Tha{Ra>#;x|I+yNnkc+vx{*(S+kGC`0 z)J3+)1_D?qhgTJT1vk{RdHZo;hf8gKdrT$zZK}^zNAR69{M7$C8#H*FD;fvms02uj zuub)0ePosMPifA}4Q^bhpFGn##|5`d6%csIHh@)0T<3z&$F`lxuV#Jv?{(0StlI+j zb#B+kqH^AlK&aI68hkbSPUbV@@SD8qh_+Wb#0i@4C?mti4Xc$l7>++RLleT6W)`c| zJ061nD!5Ez%nA-oWf0l)GOv?qx2#$VO{Is6oYd^+gg=8!i}-OvbWk7N9D^#^Nq(_X zu2q~+Iiqu|cRnG=7;xsYrDJzUqNiy2GI~3kS9gsgcE{qJ<~9v11tu~Uihi=O9 z(5PHbZya@}|8dniL%#7IlsF{ksFiBzMSUvh=#Q zsd}KPs!eBX76QTKo67-x)W^h5f5|Jg8PIZNv zpOnU1|JbF7>-CJufS_4u9Y{H#O$U@J-Utbm)T~1{h+=pCSIN+1=8<=RME#^35 zdy4%GFF4tJrfnG15f5sLAF3EoAGegy7mN{QtmA@Z6+ER`qJC=;`?bKjYQC^`8TN!L zM(R&Z6qRtNzB|_L&~Z(yhjOfo@v5j@(BvttZiFgH{f$aZy~TCPVbM?SjMjM^smx7Z z=rg<)jnBw=jqpi1gKA^6QtGPXVmWISb5tc?yGvhHfJ!BEgs{V!zkQQ!ahwO8@TtV2 z5zw6Kl~{~Z0DU90sbEqN8tLWLSFD^_X$UnrRM+o}7jN^R3!2}xM6>!Tvz<|`hK;Zu zMPrCgfG9f|ylO);-d*m{4DHY(cBtehiHuhLc9Rnw5t&LCrN9)LPT(t%9+d_XVRf=P z_kX1;sB12a+3wh{tXSQ@mHbz?zcNm4+^)_!5;ZSHqMq2R#EaZ|{bHYtKnI^pd|Xw-2!(hjLR)SuVanXk;ug3wq1-R4FC(C$r_1sMZnI4Aa4tTMlL?28L@}k1_>?_JyTaA+5 zxHMkVyOU`9*5Fc@K&FRxr7cP<7UN>Kz}ZA`7Aqi$jCqr;)!w~-UdO3j9Fznsma zQ_@T_$&y^|-m?zQuY`VTeyd@%=zq1Lp~}y(=vGDHBc7BoM^Pi?3@+ex& z_Kpm5K@We+WxiR@W->SGqeSUc;qX`9pW?U5ET!TLB~*&oPfm>EM7`HZ{K6T3J>f-X zeB+I&N;j?py+Na6q0=uT93PbNVxZ+DIx6g2YTNs~rW))f53A!&K9xYq zqm*!0N~CD?spP8-ED2Q5t@kB&lu1o@K{~_L1t-{KW0>;boY4Fpos^oYW>ft9AOtyK zN~~3?KSx@l5mO7R%NVUWi9-FdETyPf9IF)5bCcS|v`piCamMshxf(RdNt*@b6)G>U zk)>}>h(LQOQ8mO?UQ#X}mveg}Z)zfUo1L66<7s^>8E|74BzRknbJu!Mr*NWyRf)6e z;aDkyq{!7JQpzxWh7W8?AVq|xPeOBVkk~1Uos{M#Bd;^ws);WYU)umjbpABLaV56Y zKx-w2>tZV7OPMxXb4josYRoORayDb;Yq|oSZqpX{<7?e(F}7-{6OE6wp{|2Om8L%S z?+OqdT|vhr%iNiCi{4H+cc0aiK9*~_50P5#D1$rPc+PBq&?<(iXD?$le<)K!)CE;=ggq^2CZ;oTOSSljB|B;skFOtO!GAM_iA#M9bi4OG?@jZoZpg4)9i{d?xih zUbxM2O%b&#QAW>0CMfw%k>buK`rYgZ&wBWloL(RQ%3~FZUo{)|Kyx)>xt3|w>mJpe zy?u*q9Wg>wZKba&^h%B)Z{&+*R2P2&-^Jjn)D$u1(khXaMoIU=y(%H`9<|O;&2v|| z9y-Yt=$y{Zt_X6*CsJSrW27TqZ3uN+!=f#@;GY_3RZ1@*AN8%Jb8RKRzhkyEh^6R;hj{sXs4WDxg>u+ zW<6I7@v@BjV|DSJ1J*XgK+P!XpmQmQ1wKtulZ{SK?0pW@6!E$#f5yb~zN$N&@UC!w zI{p>TMRor4SY%YPZ6oMY%Bq34%eY%bj>Nsfl2>xJG=7e7l(s>)b~3wZ-zqVT3kC&Q z+ONyglVNF$2~+#mfi{)JZfH@)y4mbqVVR8IDBH88OMT+HETq-l!p)^jyzLjf3@$}w5`Sesdbe$E-k9$OUd}`}37onn-O?Xxda9YWA zu^6O{TZlP!{WMWmt((Tpl^k`QT@*NVn`f1)o=WNP$0=mf8L>4jb4Lp2SG1cD#aHi? zv@zOYNjy+S>7$dM6^=5bg2TMYx8@pm--2Rz#VkILC322;z} zQTX3d4lm>hrH9A!rAnK1=y`TZah8tAlHb#pL){%a6Mz#b60P zk<_i!wln(Mf4s>s^&QtZ+!^bInUyS1OC>kl2*F#@lt$a!V08oREN4lCePUnf7*Q#h z$#rGSy=6PoU3a*_2`iH6p#0E6>M332htAFz8*Ov@s3thAySp|9moY=|u`(7ESY7?o zJ=T3X$z1M;hnktY!ZDXOm5=NSsq9XKiBpKZ(RTN3tZzE)?oytYaBn5`>FrW9MujOt z_0J^6+vlvwc&ZD!DeTDw!b7PC|3hqHpmhwifeMw+NQz1YWh(zgwT=4sIDW1Q_PXuX zFFfD{S4=B1dZh3&IxE&GXC=jel)+SL|vc^M}pS@Dyi6ctCju6jxN zDbl$)BTG6uX9xhRJECzzysmfQ>H8HNgo-k5SFnq?ubOOB@xcvt*L!q_l^O%4^Q0^8 zR9RBwBBeAs;$);v^Yo2(spP>>TrcB2MGvcwlc-*uT>@8Yu6~E(HH{QT!^y(JRBbx= zLEplQQTPzp9&3uD9rY}Qt#37Ge^;JQ)o*EcsmW^f8(gm)RwAF*zBStzu#KMH-EG3~ zrS>BYP*lm%7^~Al0?|czf0bM-Do(0(7S0LO?&-Z-e8CYL(ycabq-mT|zvcCi;x0xu z7fy?{>DG>jr-@67S}W(Q9Gi7Cxx+i!0^Fi{#*xXU^$IHBV;9J({MZSq?y{9Pq>Y$% zI<>K`ll_NMF=rdT9|>WI*|H45#49~+vIq-B)V^OHTG8t-wg zrC|<<=X&i6ZqeBpE^)&CqAqf>*|%8}Mm8tSslX5V$C_TLb43=45hxDL35aX{DLRNv(;N=>{Uc zRBqGA`hifz+d-AN1M(ZnDA1LAQh?_L#hjbv_BlYZOYkQ9;~39gDCC9#h(h923jTxFg#rGCM7 z11?jxmvWzDkfUi*b*u4~O02F3xlC%hdnM5v#FQIf9O8yP#4=A zaIGHJS29X4Kq)$&rX3A^!fDF4N@O+Fy77DisCdZsnt#exul_>8bB++KqoY&-4WJ2g zvj{j=@M~ZEr-DZ@rkdhDU~4@Icc?yhVxrBf^K$6r3W={qYSSHt2~LQw3pEiHbx~I@ zb#*vYus~{0RUH|Yz`i!YrjcXra=Ut;RNM8Ot6&H18Ujq^6O+-C;RInL%410GtwRdew-6dx&5zYFGs;rpjMTfZ4U^er;g^A zC#KqxbatK-`UOLyiO2QOL9e9TI#s;t3*7>PD(t-;lEw*@w*P%rwPGSG}pi}dbGpOC|mQoY`#o`iwPwhi>iU|nak%zx($PkG;* zA+YM(jwp%5Md|B;ZGtLcQadeJ^Z7;v|4p?-Y3C${XjiEJf~F!eqAL5(1DgN86^dJu zkt!3dlG%08K+{)$B$l&_pL-qQ_>lz5r|w+O)%Ah{M=%fDIE zvh!YcMNt`B74f80Kq*Yu?B#*oL9H@*RI?7r1*QCPH|5W@URzyLGA996f+tSDnvE*5g9zyxjS3UP_G6GqzDQ_?@G1^vyMiZE7gj<$UEzHCl`_asZ_K4Ne;(3p_isNj?kz| zQ;vsz8mDnzO;c)p5`-0%tmcWgE7(_Jl1lE(;Q=WkvTSB&m-|sVYJ+{39A-XVTpPpJY-~xXmT}S4vd1;ZE4)XPTCV)$tc8 z1@#Zojd|!D)F;aS`NvtfN`yl2zw>n6_=XY>a?ob!cKa*x$Mvm7sg2 z7b;~V+P~{BK1Le#?G!!cTqm^5W0oA~)orWwS)9#7#;MA3#i#Y{lf3gX<^!|3p_&-` zhj}WC$@?u8bm&)>JoxfeW_jSpZ8q8aA<7nlx29pCsKvYO@f-UYYL+oYo{lHH>5Uwj z;}-LcCk!5G?(Dh#e6EToRtp-5rj-<*{Ku|XA{Wm>{8h@gci>t+<4>Z_6D|&gs}jd< zv2ih7ezY#8`cK?b9UW)#j<}#dGA9IewB`;$)hqZ)IU^JBqQ1LGcxG|xi#V6dB_a4a z(=ucmWpUD2ew4+?uWcN$Tk_g!7WwzRyUjYDal_DeAPx3~HBb%QrZ?7qBPc~S|RjnU5?gQmhwd4*BX0p6x1KF{Kho#39!Y;}b> ze19KXUH!u{d7~z}>dH`-_e(fa$&j}&Bb(J1BRq}I%M*H=U#9W$ zbxzpH=acBMfxWXi?u_+Lu-r%DcvSO2$3d0>)TTW?bHYc@A;1fV zjy~;%*07)Gf<4(*&F?{7zMX~CI|W<`j%}`TP1Pr zRK_aPWD94O^W1PADrcM~($(-&Tj*toUjRQ}%spg2g^h#pXDMHP$MkHa|1z|aXMeR8 z??h*&d85WBJQpJAY9r+uyZNJYBKv$#SBWGS z@REp3{n@gBAFkp*x|O0K3D*4A@Vq^P#epRad{*3I>+&~aJDaEl$JOJ?IpT6j}4m6b-;Rsut>w4%;QR5)u3ZM*QRmN z97++rbUa5_@UGTy-nOHe>5N`GEmQT`rItgwVj0ScIr|;-DPh$a<|u=H8uz%Mb5D8) z;l+KN6^oNAcrg?|)y9?D7(dYxwMHH>O4z9h_*STmK=erBbOj}=F_~krVs}@XQDV+( z+*xR8=AHC@HOFwJAkDMH`S?ljDB!d)m|h@u4YpSBfMm3V9J8I>w2&ReVb$QckK-fJ z?+26forq^=6?iR;OT%gKmTP!DURO+T6Fig3@2X*C8XFyA(^OvH&HP->AI#2r>p!>p zGgsNT?zq^;6nY~DA1fET7h=jW#J*rSx;)|Rev_@LQge0OrRH|C$^e%ZMGW?C3t+D{o> zB}r-;fo|`aJHDb9lAN$F0?!pQz8;c_xV#QTv)O-&voq?>aPsNd2Va+P>>L|cwqAulil}jItw;Ryx@j$D-85cAwSAVM`O7Cv5~uNZZmnYu z@$V!&?upVsobQJoZdlV2{j|C0ZAyuwy3ljj_85b*Ib}AR5JN9?1)th?Iaxe0VS>6^q4*xjs?P7 zi=pYLAxinX&|+)EL=4c~*B$Q_aQkKI@{L-?eYy}|;V%U|a+WtWAKPgfehoHN2|Er# z9Vya(hF1ZrEit+5?+q~H0UaW-UNW;^`Kt0AwlYXgt3Dhj|D!(hC_WaJZ1+ zAK}MDUh9v4l6hIn(}#53!vT3L9b~=L(N{RZ58piE%vijgOOaFq-$(aw%%6^TeelgL zr0Y(6&AgnCHS^Eszx$c4UEyF(ui}cYDG&ao$&}Z1mU?q9{N5UC9MJ9;+g6YK7)$T- z*lReI#2RnnVk(!$(<_BF7xU3$F7C?3y6Z2nI0F7Rm?XAL6g~^X)%Q^ngb^!H&l|sg zYg^sX^8Lv_t7wc;?w`boQj2ZjN?&BpqKiA4{=~05pxA-|(NV7FjriIJ&TrTr zdB4Jml%IZp7pu7YFTN!%=wh3UC4WE%H~f>p1J$tNCA$TVy@%&TV{3;gsmvLP>sSGt>Skn^xJ5E`zv2jRh)u0w)hwfWT_vH<@GxO-V5yoJziwAf3%_$}) z@x~7J&gHv%`JcMQE%x6zyNe^e@$a+vJOrovAjp zCoW^J9D1+jORB4+A$|kzJh%+TD?1clsrVD&ds;Y&Y zrwwca6U^*A#&ksnT;P--Y!^+%7flA4`8r}15(+u*bJR_&jSzGdpXkzICmQ3 zXFh*?!awGlb9vvt z+~I0)nA-AIzknsAD1rxyx#DxIEm5|tjZ6dFQIy3koB2Z_BmbbtIc__c9*9YwQ9}1~ zr@1K{?0`-Y_<1rytD)_1bAsIJg00G9s*OK$*{p!|^LXYLzL&=X)7iF&Wy?9*0p-Q~ z)dQtU)Fr=-why7l1UwS)U=`*R^75Mynf$pVUKgMIHn%IEaVP)h38&T653I^9Jj5dT z`qsgs7jQEaF=H(YX3G#9s$icsmglwVX(c0lz6MjDEn)A+EF4em9Dg{;!&<#%(8Uom z%Xp-W%|^gqd+K>`QE1~zT*>9(8rUt%XAylwL_BJ4j$=F7-Wwqc`JzA8e$DiD}O~&0VfQ@bE(|zjVu`(Q#q`VXOtpGUi^~cEmXe4zWz8RoPLbq(DU^S z3`0U7J{*cjS0pTe_A>6h@I?Wo!fp46WxrAX<=ws6r-(y-vixzk&HPtsxiUCt>(L$C zrC#2NAZ>4d!L3Y|M%yj?P+5oBZ1pFV;nMqSQ?U1Wo>3l9ZpEs4I9kqQwXv%`{uhFK zOVKODnSpd68TP4SXDTFkTr z($IrAB4VMadWn2)Fpg#LY!2T}W7#^EKcVB>X2qJZfJ$t+dXJuUP4cuj1gFH34MpTQ zNP%!;E3P=AMn?>LOifGbJm%Mn_>+gq_z z;=|$iPgT}%>`$RXG)|_l(_cn^TKfh6DP!#+{LKxc|7R$bNjEIPEwVda3d5Db*zSXu zH<~Lmdm*%I&_t!ZINKL^Sgyv!=095WB^M|`=3PqgcKj0a{88Q*$35}nBiyd!-H&lu z#f(U{582~QC?_acg<=ZJe&xq`-1H7-Nc;B-{i>rfj?V|%az9Rp(KhH2fP^{L369=m z&bvRG;XppS%UE5))d!8M7P+2NT=4cZK5|3um%QqYWe+*X7puFQF|GM*XplJh9qdSB z?0aaK#^D_il*+#oIV_FY)A>gpZ+yiCZm4(4(zO4)NR9n}^TEMDxQ@bQe;oVVI?&Le zm>@@6Bs!Ha`Vs#r=gK42=@);;8?N|$I#UG%SWn%RJAxr8W~(>Ncazy2%k%i%NNmpL zgt=I*wBLHzriIiN{w`V0_l#3bzLiZ~(fa^bD9!USYgNOprWhB9{4w}Wwa_w~c(fgC zx;tHYBZ}!Pjge-*hfU*`zl2L%5i^pbJkazm+XkU-j2Y9KH-@_DeS`395hu*TEWw=y zV_6zMeLxK}gU*`ybo$r4TFmuZInV_QcX5g*VpA-3WN1&U4FG3jfERLB;=U%*QE(JB zx0!W_a}#;JiWy(oSUdAAX1Zbi54=?!Ih7Q-v6+i$9dq8Xo#wr<2+L>Pl}{zsF&L1{ zxvBgsh21x?T@LI1V5-I58+h9Ve|*Oh5A3Xm@3lA>flFGK@5ih9lOC3+va1_|kUW%+{qgO6TPHp5h*AEi9BV|`LE|B#<-RT0{)91o&Cnfiof{I^ z`&(PF?U=|waaT5QofBG3vkl$#AoLe8dkD5_p0E=e+|X~i@z@XahwKThx}dFo--FbA z)oB@X-SK68iu`l78VQ2u!)b)Bc#>27meB@@z9{zHtUMi}+!8>`b5nh)>+(jdN_2!j%iy zGMBABV6iHS_c_-M0a-j!12>u@Isgmz;A%BIm~T_}&9l)_s`~->TengiU5n^Fjtj(* z?rIdEwc9x;0yVa_JDItHT3{Pu_yTBrVCj@=m_P^7`n zW-~ApPM`5{7;dYG4abz1pj7m@pK#tC{1T$CZ+{6f4;Ut|#6#{n$!8vL?T5US$&I~O zQNqu+ajO>&6j7G3qfIO?AaIenkB@)*bcVbh->3pd>}rV z!_mQ*zL=*&@Q<|dl3jg_n}PW2Tb!w4gTeSBiysch;|xxzhRyf+_SfurpE2)ooO;Q3 z%xRc%%zldvCAJ6~I~YSf5b`OCtJra$ed7(k#K%wALW6=#KH6tY0RJVd?uqRSc{>QL z`!hh&xO-e5jfO8n6Sm+f_*ImcPfXeJU_8`8Y+Z#5ie-lyVVudMxeP1@-`z*KCjEjDCW^f%znu996k~2Y%l$!c38mmo*3Dis--i( z;1MMb)H2KSn4ajOe96_2aBJOYR4bs%Gz2|nkO*&@FCDW~=q4%^&k@z?B>#XYYwUURcu)WAP>EgjUa z}U(KeC_-ICg|@l=8qZYRA3u9d-`H!#H+~M(HC8 zz!=mMgM+bp4Aj+5-;J?i`wYhQ9BbBoOlR_8c6-FTsGm@QlDRe={rspZOpt z0$C%ls0Mm}X7Bi$E8(nKs~ui=WLrg<0#+<%dNv;{=WNlBr*fS}D2qA74STf^uZH8} z&`BKo&+$zO6F)#dCGIVPIC!4&G^MfUZ|s@L=3{xZjGd?QWHtQu6T1i~eU*>HjpJ7t zj+^80hd<8lG-7t+ndn)_-ijs7Wa0@n*9>zFH^~G&*GOe^-(k8c%u}qC@v}9m`NCJ+ zV`ZJpwDg%H3(#Iv^HC6f=jVLZkkIib>$`s+LoL$smr?@7>H8_E{M>AtaEAASPPzWy z*gaVOvw3MRw??E_bGxmihuYnmT3<%#bB*3 zeul|;ytE8+M6l20WW}}Uy--&*m(qrv-N;NY3}3`CzPOOckWfr`6Z68ba+(pdYpzFM zF*JH2`w1JivRRpY@!g;B?iMaqg4=5bh$x=K?!Fk8&l$n!sc23w9DTz)<10V1#G+b9 z;Fr(r78sq#vUo$3xNqZNk?|I?K=by`Y&JFEd$#h&VyTF0A+I~S2H>pv5nr^P2-$x& zJP+}`CkNvFBGy07fMQ;oM(ue=_2e8eM*d@@gwqL>Tz^tS_10M|;i7Ex1w5F`2j4vPz)oUQ%@wb3SxwZgi-k4u=3+zz;?4^5jC;KXccC6e zBBqS{GwA%76V|ezm@|8rWM$x5s$3ax#N_4QKH)#ru)hyxd1B3MbA@DXLHj2>-3c?3 z?JnrAO8XZ^rE~gLqd85R#TOm$@*+<0##z~9La?O^+^ggI=eX#F&F@*g(QzpnR`9n5 z82Ff1&QL66P4naynL3&qJkf6>zpjCbzj#JO_ef+0r~<@sDK_`QOA54!C`#qS#X%nihu6fKH5#3rq*qWGvb#|6N<` zJY!w{Tg+G96+Z@VEaRR)jM-%J^13&JSd<)h5E7n0Xipkw8z2)$+IaIrS~Xq2iERk8$Wav{6u~0>(Ez&2}qMqWEt93vf~Q46*JQvt26oeuRQyN z)0gpyG+-;(+ZWxY@l`*3a+)JTAOg|aV1#~v&H9JuBSbv9p=h1WFJ8dO40JTT9?xtH%-Au0{FHf0B1#|aOtooDon#69>{0lFS_>&y%hgDK0dtrAyjP^pUiO5!^_dQ0*U%wEep0G&NoebX0;obrkuHyDG z_FT!$o>=$+ztN`YAm@h}zj3h6r4a@jKRz87HRm5-qMhjzaX^dm42mkBw~;Q7`0xYk zMh~y%S=|>$x!4Ph(%8%s-TUK&7lutXm$%bSJYUARm*Abr9lGdq*zXkU7xAU-JTL#| zRQ46iZXC-3AQQpFdhipAuLc@^fLFY5V+6h`XU3PfSj^9wW3pu47Z{tyd22cAvDF@; z^t`pn=*K@Cu#TpAZA|h(R&Pk>v-%fA0-2vV5q8n2J66C6y@`*fNJ-jiTPx{wsLc z8ErQ*+8tk=w^2uF&BP5)YGTVc^b5eoF_2H{-sdPO;qM(yu3Sy4!wh4pugvBzORNX?TW|B{ zM_+QFn-%cNC|UCc4ylS;X=;a8$Kr}Ar6D+w%|rF^Vm1f@vf7e!>T~Enqq%@|CKmV#c!U$G);Kq<;h{tKm17?#V;uC*_wHja;fiHMtCB# zi2bVJ%?{AUDrgS$U+ysu1)Ba1M(1p1cRf+GYv8|O){$*njNQ^ljY8dGme#{_xr}+jHy?BIM*f%2)8Fxuly!6Xv3~2lM$FzD zgzx-Rzd_UZ1LGjeu)3iDvFj&7)0G5A14_Qdi^Z>_?c1e6Cd<#gcUAm zJP1dnz!`^Cg&aHrg*kjha+EZS0*_Jov5pnmC+_4TSCsDIbZ<Na>n0*6U zZ48`&ZUuJ#-bv>~y^?9HdD!U3=YHWa5jOU7jAlezxXa7bh7GjoXpdw5=rtbEo;dgx z{?rAIo=a>1#wDe&?Cklq4xMUey4ukxYazMCn}MR1Mn~c?t$ps6~iU<9E~pG)b&8P z+;;8ZTuN#49Ey1Myt#4fe8xQKH#TyyCW?2sKLCH_Fj2>*=w$Aw(;v$uZkz!@PyTHJ z*{1&T!s$%*O5#ho{OdOss(RdNdgh;hGx&fO>&n-dR12!ynh!<1W`nQcPkk~kS%8$B z{w_MB(oLW6Xeal`sqvlZ^fFKLBR_(?M@Q85z|;{4P`CIBPCn)|vDvdY z%MFtJtvyZ8JbrVCPI4^%Xl$Rw=Xu8)Bi)eiX9VMQJ_zW8BjN`1K#m=b&s1d={LKPyoKLW2<=nc^WHr8^F0R(@R0w!8SAE3^(?Akj-GR{P*u547<>zpe_Dop!OGe8;ExhY!wP<0T(75+A@9XP?D#H@q?gJ2e~c1np`kOX~B8uaxj|jzAbnT4`PH6YCanhQ0#oy(8Q!;7sY;PDbZ1qo+ z?(WaSJm!VvCwblnhr}_H3Md#pKA8Us;$3VfC_P5Mh8UG&?vlCEO=WU^Hg}z%%sW5r zp;V&-k2Bc?g}D?u^0KhWUg+K(%BogH9iw!B*C3oihCI0?ysbyHkca+foV5S`j8lP^CFZERN=Jo|d+{%dYZzKIiPRgvS#HImQ|8Sv)8- zrIYc!7qx<)DxdD=8t5??uQ}k$NNCPbr-Yi~esqmBq%zfH&k;@jP?r_Rx2Y2j> zFn;5&>S0|lsSg%PL)Zo7^7nb;LLuuv=4J&B9w#b!{si^U>^{H(cjSnDDiN|jI#k1) zb`a+7ovx0*Apt8YNt`S(P^EaI!c(qdykttqp~%vTQRc*s)N?*7B)lw2n3oFT0&~NdCWZ@Yc;0no*j!OtC`n_~quNyYJ z_&My?WWy7)#i@Q`^}PQTn;w5~${eiq4>D9L1zqKSrW<$k#s3r&tk3OPL_1&ji%4zMJKqtjKAd`l@nHT zW_R3DFPv&Js?(P#`RZ_2$&8nG=04NZDEVTtD}HW>Rg&g*GY3|ahUO$47mUR+2uq?| z%GwErT2-KVmPj@KP%}@30=%NW6_}|hXf)<}p;ZGkcgD?UA=NR&ev!yom$i`fiByo5 z<0&pHp?{n?YDIgK%kslQ(^0ps1>L91TUoG6)Ara|$z$P&F5#R2^8x*!Jw*wZiAXP2 z*m3hu9lOq2o){t-n=khJ;G8$kMWciIkw*5pZElCPRW_)6Milxd3@_lqYy7m3Nf(T! zsL70G=H*G$=IaelT$2MU(sY>fIvX8MyzA0ZlOM#EFhwSv5~ijyUcR&QihZT?Y2)ZB z7_O5Kh89zas|xgdp!IQmOm)MxcIIsTBF0Pv`e0pz&dcPaLbf|^cfpTWDQ)>AX@^KD zzx3pk&mdh>wfg8< z#-XuzTtcl&B)yJ%$OT0VzQihJV_h|M%8ExkBvNrU`@3Oy4GdQtOan-(lh)pFSo0&y z$s6ltVQ0OxqAF+e>y+z1;F=lso1LeND$FNbFZZ-B@}#$HhZ&5}f3Tj~tihraJEZ~9uMf_jKC+{uTRR$3r}{5VTRL9AdvGNlcE z^dGaTctdpAr*@CqZur*SXrKP!cwG*rDC2&tX#rIx&(uV*s%S~q3i)9wo0jl1orHQ? zlGsDP*InDGb=OJmj^QDYvNW-k_51IOukL_Z{zw)JLO~%_tffFPd8V#&S0%Uo!!=SQ zrm%_jIAx04#spu;5py*XnkSEMY$S?ljm|;twe8D2Q5Sa4L1Ubdlfw@crJ-@Zc*N zD{_8g9CX2vCg|;8jMK^LAFG@H=mibH%b9lDNb=?Xm^VQ#+WzjSnag~4+9uHhSn~HTzr?SRh?d@YM2@q*b$f~U#*P89?yu6UBdmL7Qb5g)sv&>!!*VQ_O?RzKU` z+%&&OK)XNPydt{$X?a(~HCL$>(zuJZQCfY8-5k+ZVJmK^@BdT-+0@RZS=IWRTkRE#x~!5gLh?YIY&`hlI2nqq&k@cl&B&fusfW*-8ZEK-3OxXbs z4L^J6BHK9N>v(Qa8b|_l%j=O)ebv1ds*7g-48D@WwxMyw{|JNvg7^F4ty128UZT;BGh?UvgfGB>@DW-9o8Z@ z#&fAWRyR4$6;8>vzkaQnDFpUJ8dH5;Qv^96trq%f^IIF5zqKgmks`j3NTp4QUbRW} z{3X*mCS{p->HA8X;y#KnW^!kFNXb)eaIlhZ%i~Z=r%L)um7$Ee3Lc6#2axYg2DxCd zG9%pWf!ynZU4a-UyHJ$Dgp+F*1UzxPJxB7;|?Xd;?IoiIww5xb-CEQh;5Q=U_v z_|XO9z2FfHS2rwah9UYbqH(>FAJs#IwtmiVm4j8iXDL%I8ow+0vbn|vrx>Y5@u2q| zj0u0)5yu)K7|3md-?UBBc(9oA?mbt+q*8W~f+ddf&iA^)8sfrT;rniI$>*(21dM$3^#|}^*mlF=D9ewD(8#;QRcPH349{mMKa%#@4JfX+P+kx ziukn6AZ>7AbJOvTt_dx|0`%)BLnnjN72K&nZ8>+&n@euJJntSjrZJ8eUJJxlH}t4) z=jyS>_*E|cCfHob#j?N2gOEY*GTyjpe4APqje?kc+n@n*>NR#oy1y~WV`GdfUfjm` zY?|Xd~u1#U2v|DN{!LzTW{H$wczPy%w*|R_zV)s zqFSaP(g%K_gkPufY&kzTPsb{Dx=B4(kM6LY6V4VIIYe@=0lK+bK=r^k&7k?}S0UE1 z{!$%pma^j`dqeeI%NsTD0*C3_Q3Sj@+Nc!K-|K}icSJVEVcGl}+0A>XF}le5_tY^x zO*>x+t8N<;Y3&U=A5v}@J8PA6TtbfcpsVi9dPacPWaEG8u6pCOQf91U4qSYd8! z(0I{tO7*(RU?)R@==s#r|1BlLM4?=vPW4TGR#_J+MO#^=Ux{}HVG3!OZf|x7V5XZlx-F7CGy4to~)$XE$)=I_A+}p zVSNh!a5b#=3jJ?QG|sy&0R5Csl|fa8 zck_5!Dy-@j_7oh8=bbU(8N3br5pGH(H*fQ1J`#mh&rbrER&mEokT6a;FAZCWUs8x%;QN7!R;%mK{)alQ=?Tt^bUp__ZX^E~sC{7&qf?sd|yV z@h4zStexcEqEE{u=MK%8Pv=?2eUp2vENA=M#z*aw$dlrR=^hdJ-xF@S%OnnQMpaY0 z5k8N!lAyB&%N2Y{P^_ofE6v0#y=lyo=GP4FG$5C!l#3xtyR*4b^u9Yro2PGvx;f=$ zhM;?iJswUkeew#0y<+OVi2PVYJHqhx8;l6B6g7- zOsT;SIIxV%6F5)Yo;$|B+VGGnSDH(#uA4L*ZN;f;0=f1?kQgJXfFzogl$lgchg}bB zI+1k4`k;Zg__=EDJPwsA54a{&ODH}TUp)${T}6+~te`SYwrCzzZ1HoBvc;9MLn)CI1!ETQSJ=*RMyH$!bIWRm#ufxLv+!5yOD{ zSyY|S*28q9-NSI*2`8Hvqr1K2xY|dDVu9*90i)ziOXK_M+O9EFv+i3)r%`J8M@q0N z;2vib`k{|Ajz*!i1J3IH)l(6I4@>Cg4oy@hKH>6GPE6ovF{$n_NZjw+EYm5ZbQ>4^ z>I2avuEaq8>x1H?X@DR$k@y?(UyDy($a)o=ewXLv%)Q2rV!7Yv22FUYIw#f&SMt2!$la0c$=LTDf)cSyZoQ$COp$k%~qsj?c z(Z-<2i^5ORu*I5#b*WzY3O3BPGkivzksFm#k)zz4BqK<4(Xz%3|9a!DRE`mr8W7(E zWXt-f zTgB8+L{(6d+8;#x$S{Us%bWaLWXlB0aOr!G50$K;fwntdP@uNvB(e5x7RH+YsBukW zPrc)XFV(ST^VOZOCJI--YyWu36y8|xjB9%T@Z6;fAmHM{7;E6Nt{Ni#*^k}WrETBKS||u@rWbn@Y71!AU&)a3h7jCVPcgqY8J> zlqQ9u1Nn(gBlg-&Hj(}z{y5tU2hqIkHm0LR6!ZfXK|w} zx(V{$;dBDGikcN~%q|sF>*Rk7uoTXex<eUo6G<+pDc zQTBKy|8>K4H@x5u$s2ShM(GK0zKY-efiX{33QLWjM!* z_8&`Z_q`&}Uh}Sz5OGKzy`y4HgcuP>gBQ1Qp2#M zZ0L!7ng`Z3(%LI^jLZF@q>_~^Q)Fbh(fp)r48G3}71qJECbQG{og-e$HqPqnYJc>6 zhG4pbCBRmSl@x{QLS*|wUG{Drtzs%>v80mkJz$pe@W`RrTdmYtTsK&kR1O;@WsVlrAWNc{&G*xlFg7LL{AWu!fORMAFmC?iy)C zdi^}AZ)KBjE^lC_@c{eA8`*pM1FlrZBs-G2Yz5zo2jzzuj;M&nc)9PR%zN^ttcAkG zdm4GHq`>lDf*$Z^bAva2&ONQG>nU?Y>kkp{oiIKe;uLMEgK6T*DoDND!oj6zRX(!2IHhTPpyuKZkBH0t zkljQZ%QraHP9Zn-rc0cuZZr~mfp??LZT*6mF(_UX2&lrIF5SY964@149dA#XXNGaN zW@~img5Rp6gCky!#K+1G2uBZj(1K0v9HT;_lr^#$BI`i{)!&sRa*`wd&as^I!Uy#m9xncUZQVB z7_It3ZF|zLswUUJRo!ar&kLETp5h_Zzh8`}`r>72HruK3h)GTsAJfML+d|RM8GY+n z2iG(b>nhC#-nE>rvPV_WDT9+M`RjeFFqhpic7zO`sz_s<>_l21QnxbE!ZDM~3%66H zOC7|jnpHTRru_=574RY1GLl|MHd^&->AWGXP7x0{TkLq4c&D{+M9lDp5Iv()h;6cb z+;Fqp22)d&n~*})eu(2OX#?(961Sv8nk>EUYN~+pnl_KBU&r|KlcUkLg1uF5molJ? zUzA!J<3kZR8xH=%BOi& zVP+na$fy*1oB>Tpogbj#wu0zvvmkSW#hN)0-;n7)&ZR8)FY4?9?yo-A}UY zgDH{|%Pk@Zw&Fzn@r!h@p%4wMbCmt=O57_Jfl#HaI8M1newN2=t6ZE9%z)hbK7WzR zuiQH5fxf8ijK<-%gR6)@w!W8O%aK{(g90TS6>(8HC1^<$P3sP|)QC^9TYE!+k>ytC zIC8BGQPRN9%z~l2V#yDMx6~RAwh?VmX`MGm?W_lGP8Acg8zI2jdD2uL6t{ zzO9~drzOO7u43()HbJfBik)Q~BWk_q0ZJu%Iv1*Rz!uTqwMi^so&t|Naa((ea2!{P zP!v{*1YX0SuT$MIQgc)_l2VvI;9Yrg@0zAU|J&0EH47{$_<%%Kn&W9UqCe5eAeH#j z+taTmPU>ftl0;tGR1U4+M+y8CsFA{#HEEF?*TssWnPQCwfjVL8K-F(t6e zj}Lfgc|-f|a-q0SDYh&3RgR1%sNVR;6(>V1@!?D~cB`HVu}&($8>dQbG$e<(1R;96 zud9m@FI0(as_SHybj5r}tH~t@IqirkwT&@;x32MRwgw?b8x|q5MV8Bmff@Q z0VBo4OSSHAhl)>;D+2Jn3(nTX=MI=y#}dFFsro82tLDKncFnc7`}uqPSMO<@z1?Fo zj1Vr#Xai9M-R(K~Uo9l*&ubfSH9=n2aw>{$RVn8eS$^4`6#iXBi5#SslzsS3RaRMq zE3Rst=Z1?lF)oW zYb7|rClv21g&_*bm24l3G2*HTJ5$PM^DPx6SMox+*zP~2`Xn36NQ@Wh%|aCuF92Q0 z|4BOQI4jGwZJ)EIV@(G$bc>WA-3Tg(g(!-#ZMR+9j*ZyeU5L6B0~7>BkW@+pk?v-? zyRY}S*89)*+uwe-&dgfRbKh5-=W+VIe@}bQAbXXH1wJ;>$q*n}hKoZ0-KIJ zmptnhp{uLy1fC|!j|df{JB4^-7pKF0-bK@zjddi=$QYLOO$Ne(+jIJH z^nhtIijhd2n&Y+cvCg`q)o#ns)11IDYRmUU`wzJ*-@+iy9kZR%)5k28&I`_VlsBF{ z?SVGFz(u}gObpt&NOE7AzhAdCcxAf+V*=g5QJYALcfPG)+F5L0MYtaFHPV5RCW3Nm z@7(6v4o+TTo>oi?#t4EG&I<1$>vBCC<*P%UX(B|Dkq#|nuhEhqzI_;9zlQ0jb{-=C z66R30Igx6px3Vfb+-N`KT5r-2j@o@;nvri$af((Ny(fKraip-)tfAiHuMV+&P%mIu z<5ZzNIftyqLVE^w)?up!Vspf~AD7k|69(eFa$pjsYAG|=_I{>a2hR|1Y3Q$JK)tKM zNklg`Ti+x8;JtLx8RJLuozcqJY6X1MB+cX7hy4q8u7k$37?x*hhP-(7MF(5$o2>ZI zB=grp#ti3Qh0Z_xjv;)c@`%)o&^_52N^(S74JZEvE;^v0STE$}Hrf*I@d9Hidp-4e zh#xfuHbp2?ER9h=DIw|J5o~Fvfw&1Wodb`tg1@~zRQ5)@y4ZF$*{~2JnswGdcOk?=Y(Ac(*}V@ zq&s#gY&vHNMbQ2nJwTRarh4;s`y)5jC=~}r9TSopK$FX#1>*Ctvb(Y zx>MS2Yp<)NC*TP;dt~!O#uQ-gnygQmb!f4eTtCwik9mA>Yl-t4w}b4)n1-#Jr!+?$ zr9Frnn{7m-)-@kUsUGs@GUidT;CI~0g>v^M(h?RbkuH6 zRQwmAdxY&_HSG*}d1QOF+MW~kKR%h`KC7S26f{cDa|uT12($e#{Xy-~YFFfFJL-Z| z?Q5_epy7yd#0tUhgZq`)SAo4jK47tJiO?dXGJNC78ct?kj-JO=*Vb#)3y7g_@>S!S zCVRNb6_~C0wvf8|h)>J0bB^ZTY_%QI9RaBrrw zC~{KS;aqz@M9rEHSFOq(Kv@x`HY68tdK3HG&U1fm%y3TWE9CH!s7RD5j63RZx_+p7 zWRRWk-~5{z&zfgZyNKOof|y;9(SRFb77R(`)1i84d#f|m`DQngHk)UM$#yI-=Gz|^ zTbBs!MJxfsKS3{st2xWF_9kU}U?s+U4(cVb+D>I#XZ+jR7us|xy%U~%w5ixGU{Jt8 z9pN&kgXjdP>%u%KvSX|D&sGwnSLW~}Gcqn{vXT=2>EvV1mipxTPw!V|*Rl!XaEQ=g z>?GHOpaOjvA846#_>XfF!Q8_%O>GlWL_E&b5TS`l`YK$BnPRhf z6VpqkVM#iL_ZnOqw=lx3X8U)(xA1)moaK9Qp)(j+)+}Qe8T5QZM6j&J_T=bHaud?L z584vvS)xm7t%fZy?*-hp!^XbseXP3?ie(WH=`n>fNQ6vrwrZDlUR~$5by~&3Sl2dG zf@`LnD|MFdJNZ7toC17_+X~VJR9MznJWinw^mvZGWLT8pz08nQ&sF)9Z5mg9l??(c zgRq}JGuLk76Rz+WCL|x5Ao<5S^I<}UqQlgtof64aL`}p_BuZzIB2C0FY~Dhz>UlV> z;9bbGx7e-J`-1X86z24CA@=fpZ8XlDdpxqmvXV4}J$0iq@V6A%4Xt+Fai6MvS!g_^ z1IsOn^aRFG5ke}~fxkN2!Myind&k;0QP<(M4EJ5blNGk3$$C-% zItn*Oyq+d~J4=^`*p+9(;>dC$!PW>}OZrc>F~u!`Xlm90B@a8wBEQ8t)Hq%Iw-}wp zJ}zD7hUv3*dYykE)(m0=Vx4dvj|dR1Y>6@Ey%NE;+18VGAEUN9n@x2`+yk~9Sc?Il z*)qAMX5jpRE`B9RuWX#DAId!^@dSJcG92^m)NpkyaTw-&%I^pzChAkRV>#NyRyRld zBk{?e%|RhIny0kN=m(>C6Lq##%>M`g z0vcap@6pAdG|uWhC#_4kwquQF8cBd1Pi(6Cu$^z?8Z(}jM@UUg6=!%`K+R1yvcmI* z=sIiII_23H1X~oi<@`-njokhTdV#%0mPS&$hGt4TQl0;QH>-a(MAa5-vK2-4Cs?iH z{*Js*V$)EPH28_iC~`e1x9OhQisXAG2W*bMXa5-Gai0YsqoIc4UNLJI`m1}Nbj?pr zg?$34Bf{Hu#HbgCc~-@;R?E-O=S|MhT?k`AdYJubrEOrd6Y4%-;u227C>KD;yDoo?C9Ci5FQln_6?R1@3i&I zzWZ}LqhW5mr&O#q&p$m-=4y%;3%qMiIN{&*4W)K8QnQiVpn*huOBa@@$M?@o>Emd|3xVb!gjem2V`;6W#^%T+e4fcMxkoQNC_RB7YuO8wh>Yy|+9(f1T zJ#SWn&0r*mfe{MgNlOH%nCr8|_mS?ixMAeWx0a%70(ulqCMQE1CtmL6oCFuc7>!S_ z+>Y`Mp0qMDG74NDPF2rTC!^JWI)dS`WW~1id;h1lo*b8wEY5HS!V&CQ$eLkOQ{bBd zRQtFvFbG@|sYamdky;n6M>!cX#B}b(Hl9j3EK66B+5<jKNS!jhONG5jQ-D@{Ym4{hr?GRHH8?&X(czq>DEz(~FZn;qu=xpp)9nUk*Zj;ydd zqcn|iN|c&{u2Q}hJD${&nxVy-Q#^}}cJk6jTUhB7msI*qc($Pv6?5Wg;mU3FJ~Wf* zbU?f5c1h04_H8dX1r!ubao)Wifx`o%rq~s;)yG}=a|ZeKR2nrtK03%kFjAdUJ#;B0 zM~}hb&(Raj25FYwtuMs}bAuA2-CKUrHpAQG_GdR;=6yFWHGlY`WS^EVZmX-A)n|K( z+`rQF44Dy(MjKt-$K<5+q+QMw<)mi?-cHJ4ly0v#6fBR#sUbpbGIe{HE@fsnCUEG@Ks)pJ&`o6_J&hSsHU!q4T zT}CU!d01>ubJm@-PnbX!`%dbUN;^H$M=QhFWTlE3er&eBB11DL8g}S^6(&i6q%ITbC@?mIpXAVCe(PYBp?Q zR4h;D8I!lX61y)#yX$OFq>jevCpMvJnir<)+j}ZXTADaX4iE~^>{8oxjP#3rK6Bei zAF?H!w4(v#AoiwWVOkh)a`KH#jk3hK$J}@URBaHN+&(SNE;xbiH$;U+%K>bY=kr-? z5fCIeE~xEM4{|DI>x?i(X1hLoR)VOJD8r6488XzNP>8W|v|7V4>p~V)sr^LF(`3it z29s<{*PNk9=*QXq(sgU2K1>XdG{fV?FoLkE(8|bMC~zeVNn2Myl&$n$>>0`xe*aWY z#_ZJARq+=W%@&+q=ez*GN57kYv~5iwG@He$t-GbzIzMv-lNZk$1_ zg(fZ5XXYateBa9UDz(+-9{0ODtjIb1@7LN9W@`vmnI$GE6(vlL_LF3aR)L?6+>vP* zf;ztUa*L!h$+PdcO>*rHSQBM-7YaRzNoG!>_#9^B_~IbCjSjOTPE|~^@etB#U0QA5 zkQkL~yJ$sEdZO9LQlCTKU299ohK37GazTa`Fz;%s=Sj;=bFMh&()LE9siEO|pxCDk zzvS5pDK+1&C84RtLu%Twc%=_b(*@yPnd3@ZmEkn_>y!0IlgEy;DabDM{8>`TBBV15 zoj*LS%r0kpOVLZ;%85#kfRR%=FWU*glalK1dJgP}Qr|mY$*hNbC{{D!f?9tgNfViZ{zSqBUH=EM zK4L`zYRtGogzy|T9n45WKmBl-ZDIDvlg3k;Hs+sv%v$_jgml!PWvw*l{Yj~8_3X1(`CG&@t$tUv0T+;3+Nz7Zgo}<8NSfi)4iN$D3gCJwSD~DYEQ-_jOKBk zx>WUrgrB2XzeFbmj3LH%Y75Ks=&BmN9P-#(!14O)%RyXKp&c@IpnF4 zBSr~?G6Bs@C^c_C`n@K5w$k&+rWgA6Mc>N*av6Cj;T|wYwK*zY&3s?!x`n{0ECo=C zCi-)71TjF9yUs>a_UJ&*DK=^|K$@-LDi8P611RWh_xfdvE^l1AzD73^?|UU|ed|z_ zll;!plFv8qsitoP96m;d+9m?o$?W6X3HKBAUFnJ}ZAHnM+M4F3P~LztrBY+tnYNKC0#nh?}A%mz3=`9KswnLAWcY%hQGvItJs*AZYZ}h zZX$5{^pA-?H~uO;w&i7cBOy+cAZn&b+D03b`g23y@6~ zt49K=OZI59Dz;L~d#t)@dmVf3qqC_I20eA>ATiVGM zJG0zA<>@VU55n7pwjjWu)`_NkfK$TVIJur$D@7wix921xmJ&U~u7FsLQ01_=#q#la zk>y?LSqWcP_-5*Oi=B-N20K7N>8VbOIze@cWGKN?U`Wmyz&p<8LsegHlbFpGIzzs5 zxhG^j1j?I9V6>lUx6<{8DU7Wg;?h(tWLk@vk(`SLdkCdjxzAWo<<*jaQ|3B1HV6Zl z&(J>eeFa@_d~v2uCk2w3NwfV1r@hIR^D&aWTV;QtkOVVAD0!i^#e7j~heLb7^Wk3N zT!1&2rbA{UGSrS_!=N7J=dvK?-mmoto*Rqp8J?wLXLKZ&`;1~ylY7K&k5v+Lk~Ggc zY)n@n`BsTyI&m1hIn%rbpElfzebo$SQ!Xb!sn;7D>+KTK`ohHoU^`D2srMOP?F`89 zC;S_XicOYFxk5fjx!p(lVX-|2D;qrqJ${8xmTn2?8aikkWC1-$J}*;_R2RK(O3(&6 z5ejuS4wYWITv%vZr8f$_!bD4XCxy59v)VC1J^0c=j*~l|>8iG^LBDzL#db%9Tx~pi zeA=7 z=&wtC$BhD~ASC`_mdI(0W`I3C&q**-{hKWq>p^LcwpcB*`U>B(e^_J#P`DL(y)g;Q z4ryGhrz0EMnSG`jQq)P4=HYD$rXvXXfxRtmwSfkVwR!Ira;qkKBW1s{Q5MX-~ODUQ$m9y z>hq*JudClLu_m&X3auk^+AvIh_VdS03~N?=|8g)TAZvtJgn-!<8>TEDwvOKm0>R)OnqNb$Rv zte-}oTE7*lSPrIW850BEFW#ecCAHe-XmRJ>$E(d}juD>ZS>gt;85J8(^#fI|bGsczdE;%v_;u|8!=XtWb(fk2tH*ju5ydmcc8fSW6sQA{9Ug8DpBOkDz*T8Q6)v_6vS zV?{MSp1~t)U$p3)?Eq0Zs|!7?fV5~Dc2@S(Efw)TY}o{xg*!COd%)uWKlRZoE5pGCh^ zXx$ML7FibcWWD{#JxB|TYA#uP4kzG>OBbd2WNbbbId<#K_67A)x$8)m7I@{bpv1Uw zxieISLi0%;=Ar*`T2iDD%A%yDBcJ&EYWLlxxdL-!C znj@mTYUtGf*OcZjBqqEf@|yw z-Y?E`v}#30!<$y+lRcV_IMgg;&7id==mC9Bx_BP4LU^3UaNlEI&N__0z0Bv&f0g(> zSXp8X?8NImg{P7gB;JZd|MXr;b8TS`>?yNo`VqRh7Hi>ztg;K(>=fD9Rs%cC)(o2- zXLuOc3J4F(mjYUss_Re|Wr(-^+eITaC7sIl>3y47PXor z?r2x$FG_L!Ex9?J!n7sXDFkn0b>x5F;NKV8V?Mwg+<+*2%AK6dT$kR?Cd_^!8X90lI?<2k$RIXB9eEccK5U@vNxx!<5PlIjD9FOj0#0p!d>L zZ1zo}C-|I8ObKM=`845?woT&5` z4+7>$Y=;1~$!3BpL2F%LAGTTx)n=HB!8i}oA~gm*PiS7TEX{Khwx)ZqOWRo2Y`3Gi zz#maz#n7>etsCcUi5LAxBvgiSf4DNB4)E-yh&y^$&_StOj%@Tmnm2MPvfpX8*`aR@U@3hQqO8XPs*2Rbd`XF#&n7y} zCS>JP1Xg6LY4ie#l`^?LYq`-2P8C&JZbMC>SF;KFVrj>fk#1!PxNmb zFKsk55A;P89yNZ40`Qr|e5}lV27d}MJk5*IgLNhYNa3S z=TMVV2UdCQR99!*Sz!#E1CA}~74S2es^HQ@|J=@x(QFzDbB_{+#DBAWsBk6f(K2Iy zR8Z><8g{Samg*taXcc}+)K%DvsNJgVFsvTh z797^mLIeCEpbzq@h?Za`kRYDMzJxB(3pDwYb|&5;luP9n!`!>lCsaEdyz=P=4wo|_ z)^#|i2R%i-h#YN_J)_+Cb~giO6v(yqB~N&zCwzTZ=^NM|YW-xR^cuu|Ax5LQu#((! zbZt=UaH+()pRp)Rm$cY3^>za}*A;eltC5WGH=4{^>&HFP?0n1pk>3Aao1j=a*(5P@ zBTei#F2``sVV~IK+<;Y;b_k>upelp`xH8d;Vz@>XgPemD@1URL;mLZO&N`^?po7Nh z4LAlo>=c;`|m^GVB1mEOa?UgxAoRs+4!NPxA8(4|4GCGCh9pfGL2zl>lK$T_61 zIv*+)Rr7uGw?9y( zVfR2u5~a(~|KMw&1|ugIy*F|!YC>*y5_lVIKHXWhaUAnPYCnXI;2~R?tnCCFw{2Im=tCkj9U*m80iYiYsomyu-$?rg+%E=Js8klY|x*=Ta zBHD*)%{*Tt#Z!I>b+UKb2s_a=*(+xi3qHN>o!cfm-iA~T$)LQYP z8vHuOl_4B`8=LK5=&!!F#`9m+*BajlXrs}b=dAWfvO}`P6cmU(+BpDgWSk*2p^C=D z&EJk;iuVOGK0Bi-(VVe;)7pgvxlgIrK(a)8e3^ujocG zJ&_7mZI9p(0f|G#YlAWG-9boSK*u9Pn~per#v}otYnXCrc6d|3Bcsu3u%B9OW{ppt ze`9NJ_C}*|Ulg?X7MN8FliT=MZIbc<0-#}_f?{q-`OWK(prYASZ&Ysib>0)rr~~q( z7Z^&1iLuEdR9NgQ(K5xl3h1>cZN;O_I32}Ki?v5;UT+6TbE`4LEjXAqqc(4}d{p0N zoPq3@pF~{~<0|oUvj{ci-n2h+4g?110Jb)6Iqgi1yI}Pa4aNW#= zoJ@@<|79jcyHD*Oz7FOdtP~-j*4t%h)HzR}YGWTZ2O3bx|Hz+Ay%!QeHN<)X?D`lj zL-ZSx5X_b5RB{pU{~lV^Z8;*bNT2+(!hVcX z0;wS(x)Lc5nig>yLdMDzuT*Sy-nC_ z!zkEDg+@QH*&Z2ZHO2kH!(qyi&v=<`WsQjNyVBMjFy?#7 z>x{c{=oYJr(@iU^gx6}fQDhG1G)T~6ebkc_`{B+d`zuk4VRClTxGHN;*HdoeOPv?J zWwEE=ef5w18lgL)^+mjJ0o2Fnj%-bcQTM)D09)WppJwmKc7+B`nZpg1{FAKlRCQYnsh2E6Sl^)s;nT>sWPwSD80_QMr&4`rS9~EtoycF zzi_d&VbnhilV*YoA@-1-kf3HpRRc6GPVWu&Pp>n&JH~p2UaWF!Z*H-5Cw&_`VuP!o zXRNUs0j<~B*#YhBE9&QSGc~2k%G)`6c2EyBL&)qQ4${wS>>94Hea1QzE>kzsm<(|1bmTZq#|O zVXHy?ZK2jm)rEb<7=s953h*sOYxKQRDy&^KHcWq+vK%&RmrT|r#g=4tEj0C`HnP~}EwTR~ciiRO!OhF;L)L2W zE8wV~A{hSBlk`-a-k#ypr<`uS1%}J?af`jX-Tp-7vcyqn~CKIcVa^e7mE-nUo#_H!rJ9yAgvVF7Cwn0hSYc#wsAG7C(eLrf`S&JZq3+n66IvTAGll&HYlI?k= z%_oq(%ox91Rbj`fZ5)=>4VGVNUoQ6qgro|)9s^E>`*)H%X=9j%b#-$}%P=3(7WH#) z1tCyls9U!A2qJH*TbNT%`by&Sqeemg)L~=5wHmt|F*Jj9PK-8ob+6OhSaq$oH{%sR za0}S5!qy#jNp1EP8(wV#c37av&aSo}rDx-uxrG1mbl8J^bQp!z7 z@)wTS+tfB>*D_S!Wd|tfS6OqSUOwWO3v}jhq^b=*)+qgWiY|oFl;N329XsjDD*LF; z&M&p6$pJ67D^}TCklz2c68IYwJaq0gUJ-qj?Fz1{L3&y0dZ{R|*RBH`}m z_PGW+Ix^Q)*2J5&%&;9%X^(HCvyyd7vI1j-9Q2glu0hH@O%-sT+UROb*<^?p+x*qW zIe-4Q&L}uhW(DlZkJ%|G^w8I#h#sa_rT3@%Ot153@ioQ82^L((D4)ts-D1=>Fheea za5&ehVQn2VWotjNgPEE=P+e2>`bhrA(8)IWrr?{e>?RJA z4K^{;twUt@JdvT~R7IbmWAPeun%A6V(_A^mbHH8$vH+l?xl#6xP! z?XC_z)J3O}i!odgq;lWxc)ha*Xp_?*e) zGlqsOKaSE3eC8uXCfyh3JBJ}LM|D;9*>MjnKDgCp*V)8*j==hi3>3T!YmF(#W0C5H z2)UPxdgBVuE`M;i+S6ip^kvYv4oZU?eAFtd?ci_jKl=1bdpt(Zmb-ZnKg+YJT8&OG zMe~PiN`hJ^_`S1Zj7G8(M0HbbGxvJJX zqcvI(P*D-jg52I)3DuT@XQa|8EBoqg#V^*5dTFnfF%~PcOOicC{K90-z|Y9})>lXr zrcKrtXl2{!*+x5$nI3J?Ry)>c*TM&*T7!9IHe#0HQN!8e(E*Rs*_U%Jm zKkz=F)F#U*c7hPQ?RkCh%PfADX6j%_TaAu<|zc#`o`@Qbf+E-y`#=CNWQO_HUQW-4{(%K}I4bivJTGiDj(zGE| zxgSMpb*oJ#3XM7EU)HJB+Ag(3+)sO4gc(89RFvw{9m_DUizZ?;9^l!azf9Ksh#ZJU zEwX+4Y#EtB+q@;eVxLz{m+!G4eDWidG`d+fNYoKAWP)qE(M`oT){;o@_Rz zQYhIT+-2XQ<634n(Bm(%JmeYt8jLuI1jU2g(PzkA`YXM`o|&rUFjR4G6&Zg%=>v1} zTo85bAI^X(-0F(fD5?k$5b?@S)qR~6P5kIZ-kRMyR-?j{l;wJ!53+o+`tct77mTuH z_9>L4_k7pCy~sulkxpJ9ZK(en(}NWUdbzP;}vkM(oyLGdx$483{1=W8^6W+g~U z{|n*lf%*oC3tdE@WSqCan)RW{km)%H_B z&uy|cu{zOCYamAU)y!CJnX1A_Z8%k^?k>#y#Ru|VEHFqoxPcxd zTlu7|pib_oj;yrEb)wt4(p@k;dO5nSFZiHh%OLf>%{TB+Q@r;q4BX$$LUWt7bssE6!wbvGw+qvtF9HmEpaQhpc(7jRmg`~3> z9qB84-=B=rE4-(J)dW#WQGVcn|ivVZKNYFn_%%ej;0vjhc*w@&1hD~=o#h)W8B-g z6cJjjEoOs>Vq}k9$M$)rBjegZk&0KF9rjO>^Vtvy@YC}+jfeW==L`ZX8?0i02GrZr z#F&=bt}QmX(r#I97uNeHU&8jJ%!zzIRN0N>(f1enfe#1i1bR837!drrYb`v|dVfNQ zM2Vt*|J(PlM>kk1IW<39_n_JaH7~t3XoeZ7E5xy-BUdFSf9TDR|YVF?RZsot8mx>|B9 ztj-9%_&@6vr|J@WCPVK4QBT#u?*7wD&(w?2+C5NA)qWc2iHDtvY-xoNN{*3#>s-&U zgEq#@WYQk%87|z=f3P*^q4}s>CVB_`s$rhex4ui z3tTA1KFM_bf(KZE*>mq5e*mXD$r!7>Z(ec)?JLnHW9*3Ww_n;F&z8d=M~9|Z4r4) zs7+F2b>4w4=%cR~n-6e0(5A_{o=0eqRw3G{umC)H_)GOxzuZ~PXG8l$9QCX1L1|fU zi=#EJyL0=0n;@pO^G518^!5DNgPf=%4K_PT zdtv!#vp@4xI_6tk=COKEec_Ybhi zWys&p7Z4}Ta24y)UOHZ7Jh%K@^vPE-kUZfO+=wN1Aqv~gh7I#kl!~lD(WE8l^JJ}I z7(2u%#IKCgT$UhR-OqB2p&29J0>g}ux!bNmVzt@f2U}_gEkFfs=8>40Pt? z@@~QgQs2)rP;M9^a!a$B{#97X1{+dsoi-U~&hxvS@j|Q&1I$c9r4tmbJ2%kGH9dEB3D==$T}B)YmOGb+=vBYN>0jJ)g-k+Y|5=IRm|?JE)d@bUSTowmtoI zTBBV*OgGiq+ud|+rA6i$sHnMzye7~7)28CK+he1n^y(3(j567M_w=wD=&b|~{H5>y{4~A-ZyjD-wyOFxHjVD~A z(&5IWbXh{`_j-GWoX|z?y82+LAuOSRqE0Id>W?VhOfqsncMet$D!NI6_t+^%7nRs= z`)wO*wLk32YHL{PQ_A-H+|{_^PpgYkT6=wvr1c{mcNTxC=f~YJS}fpqwRJ^PcB1p* zc5iYu)LF}&u2%c5_w1KI@JJN1)(hgrY$uPE%>ZRV1(~Xc>aA|7r>`XN$uc{GFQVMK2Hk#mBaGHl^ad)OiC$xWcC}UzqmZF*nR0{)!9ep~M>k)$+}?tI zw#=>sF8aH5j#WamuPR>Xp^^k0x>6Y2N=Ev(mOfD2(fFZ0fp@*%3ZXcDYqvt=`@%2u zlZT8ux8D(G<6MB!K3da;yJjGGnadZU+ZlpenCc}?@kQ=KM@?(<^Lkg2W&Y%S(9naP z6nrIxeX?F1qm@D3e5P&+5muS&J~mDt!IWsQC+cnJAFjH;H5GvD)5dYxa{n(P>z1?IRmfGidZ(*E5>0fW9@ygg{7bU2$of>#% z&eo(zg-vl{^DRB~KkBP)8eeXSm~cz&w3W8K+%8?nG8F2B8jnHwX z89Xica4+%=LeUVlX|xND+b+0~o9t|)Xv;lfe&-){cf3k|wwicd!x|9*^XcL}y<@EV z?09047nb+YdPwrbs+QUFR0zd3W}T<+oiW#qdo#CL+b9>+XT|F8G`~0h<~c_eb&;Pc zw~#T7UXEatDtk27$&F+FvbZqazT8^K1O47P_Q!YFv1Gyj8K0t8`?*>EoAY(ZtY<&1 ztG31c#7eGfgJ-zR*kNQ39hqlu)Y`R+>>hc16&ce6+$@1$*;6N@6`bI1!Mtm=mF>nU zdZN~)^+k0ygubuY2K?rl^SgernE;x8bTP)PCL_mWS*CghHSKJj&b>ceiA@$eTnm|H z_0(Qcy$)J=vE8uRt(`4%y$c?5#8z_1?{|MGnhQJ;sE|dYVNuj%jjO z_E}hk{db|SbWg6ZzMNWHeEN+~1gzz6J@i40#*b0waMfHbb|P_o^am+%o&Q z!TN49bO_&k?~3a&i+rv)n-N*MFG@Pm1^3k#oXkUgoBZ}v_k`9mcd9T-+%wB8W}97E zX%iP3X3XC<+ON`O8{IWl++ZuCm2rxj9OwPZl|=_8DUDRGuIhpas*O&qw_i^<-*xH| zyPA#U7dAM?Q)}-|)%qes>^C!AF-bZ(Ld7x4In$rg$H(c1I$M^gdF8gK*s%snm)l6{ zpLN!a*}@vjWJ|Tp21cqaBN`TDX9#q|nn}(Ler15R*V;WpbZ4CpnJ)y>vC-Zq4}G35 zkEX4#2`nxT*(E7jMbKojs!r8dmgb|KZ*ce`F;$2-&F4#rd~_A|@OG<0D6-bx#&N&O z#dyn>xOS$0C#|nQJ=iWbi6`FkCU4{5FBAbZ<~wI%-X_ zR`%74So1E|%RDpvg-~!%s#xOmB|@&=KHp|NNOWCn-0xGrv4u>u*b>IOus<}TgL4%u&2RHW=8W3;@p8-dw~1({l3CZ>*W z_S0aR0!E!>HU+OYDfWLGN5QbwzFdA~vE3G>KXHU%smsE|ciAa5Uk+@q&o}*pJ{4&+8 z%+8C^IhB@m(4|~Yt@KQmJ`3yw^2^=!dc3yNbO$x0i>I${JX4=X>CKU@$>}rPh2_P3 zAoaHMfX%J9*A~00C31~>NFUl^U&d<0E`PgQlf3hNX@b6|BRJpbrb~MZ5eu9;vPCG{ zE@RrT(X$U;{MDb9hnLuWk*YpnpRf=~($WOIHdtYZibp9apuOY8wt5`2aB^_qwAAFs70QUChoPar>mAj<_uT${O^2c5tpLkuJP>kQ6!shwPCDI zR#`5{sXy#|kU`t+g*Y9{@Dr)cK#j%_bdFD@-Z{-vqHvu*O?4Nk{e-k`w}NW>t@~>~mjS?C#Fn zw;LNkRP?(cC?Y$I(T1U(4AOhF&XKOZKq#-y$aEa&M>U?xacrkAvVZ=|72_q#?0-@E zV3k$I>3*8fB)!vJ2}~PCdooC$0ZzraZ;WqAqBtbkGVFE})3mkrT!W2TXs?=mx6eQG z=TQEW8%X4Rj6NTrA>ryZU0ra9a7xtMmk}bN>ET)%hkE}XW0uR^jf?i-@BOfyRcmZ} zC(;O$WOs_w-U6ev$Si8KavJQjUT(UXj0dOO=)59p?a%qProl=#+tF}MKHvjhmKwd7 zS@m?9Q^W;guf2Ghn%H$_cn8`$S){XoZT%DVG@O}4ZHBu` z-q2VjuwfZ2zEdVeztkBEi1bQp2aB9OvHr zUwUdlosm~Et=_(dY}R1k{bRML!WQ{1HEgRP`nr~&QqU+d`YBGMdg&;$Plx17 z;{FXMo}FHl*(An%O(tdlT z-nVj>wpiKkwgcbIMnm?7|7c5$rgYVZsP3te;1ZnXBCDj~zJvZWSv;TT9keWjnp>^W zZ00&!gaP4zT^FxAVf@A`44)b&TQB9nSsmvpvR?i5A)pCtkCa<=&UgKNw+*6B{?p#% z-@n3sMI}y1YqSV|pBAYXJG*P_e}g=Sw058;xt!C}3Fk8*gVorgBkr>}xz4_lX7BRE zz;=fX`Ov~@I}gZYXU&Y$q#-V$PaNgsu4yU$oIe!Qw~&*$#n{elHntvV(%Q+SDVO@8LDyh?$h5^ZLGi7)>`Ivcb~oRk3~SdJZSqNtM7OG zPD`@#6Z9(2IqxY+E)a)$c<+gW44K(2$iNz`c#qxMXs2&=mE)&tY)U|B1vVv=`0x-M zNLZKTL-kZOq~Boz0db&@3*-3($PK(Z-?lf{%w2|iV(=at9I5I5*naXQ8?0@D{?1VX z#<5d8GULeUx>dTlonj$C;{T$~DYcGJI1YHSz+cv({J z03Co-IYK+?UHq^YxpITW*V!2-ER(l;n?;dPw#{xKHSdU3GBJR>7pFsQ)H_;V4ORup zTG<2CBTD)T)M?;t?+|(6y0^6y$ZzwRUX{4c24kkua}dQ=J!_yVwDV4)%eN} zVQNaD-O*rI9&#^I;2-CdzI4EtnofySZk*o9bg1v-Q*=*+PU$SvZfFLWt1w%;1hQPd zjlr?L$54&F0D(EgieVN0QI+T598S_h5t`ar_d%*ZO)uc%$ky+*HcftaGtA^2zu~CO zKy|;D*{#jmY%udlTusrt7$JMQd@i@6xNBB+R(`8ZiBlp>U|eRQl#RCm^zCypKsG#zNaxjD zj7I=%Bz*NQu2Q&>eK9VrcH$%<=CT8HaWLt1_V!Uf&)+@Z>d?=2+ILWwD~wKmCZR?2 z-R;FMxvq=;;=k|a&b0&7Avo-6Z7>w6lm2dgzuPv^H5_&lf+wmV0VOCWQWs>o+W$_r z_BarGsss67_#EpjHo=n%b|R*#vk}MLXYu4tCr-!0#YG}W2oW7OVPw&|wzFVhPVMGQ zq!)X+E9a{yz0aM6AG6thKVm2+A(LMhrpphxGonp_eZr0a;aQ}fBYQ|{-$z;KD%)ue zw?K*>#D)_Rna|Djjrq6xUE})19(x~eTAh``w=tvlJej4lqI7>3Jexa*VnBp^3SZCKKYGIuX z#A#J)8;;oWM$;a<9+z&RQI*XkxCEYUoHPAacJY71A6-0yU_y62WDa(ojyMxd3+>V| zcL2>i?D-UF2aRq1fP81Hbw(~2r8~%63+OyLWf&cZJ^0sV`>v!@i-)PrgmwjO`;hm5 zvkus4IPoiupT(Mc0R>eXC(_U9;0fSC^kZm=vs6UxSd%ScwgxlP?4hGR6kTw{!cl-6 zwnj3A;Za9>+&uOCn>o4_Wp7(u1ZO+T6_SqCO-3e z_vw$LtPNLAwk{9XpIw~UlHOS;S7|yH;zlw?APKAaFua5bYp~d)lZbpv)`> zu0o2thuLpXT^fBTH;ERqz}l0Ib<{qAK3(SCieD-WMJ`f#mWeNAy2l~Dqq-v9Z|f5e zj#-{q34tkEoYU11b`tI=-Zn}v9m=UIoGfsfFE!kIB! zQeg-TxF1hvyV1r!?QfxNn{`WfvRu0qXOGavViR5s4G)jWF}sa@-%0P;iwUfZ)_={u z=QGFQ9;sKd^Z~xJc4{jr$?10M>#aYrDW!Hc))E*{Xgmsxy8ZeB_l6w8trl|F?FrM- zY|kv{k?q`%38^}x*?LpzQ@2#QgNa!XrO~=$?k@bg%w|VA{G8`JD@Mbjv;&bO9?u*N zfC|z^Q`nj@>SroaYg9h?3U9>y#2JleA=f4|$*eJ|!976*p)kSDMR(Co>_I*yTADdz zqR-4(`cJ_bQRJ%cUyj=y)TPkfp@cP9O^D&YEyAO;Io}p!>2&l+ZN-F{0_^S<{|EnS zw%04|#uht#(h%5_&iM)&fI_Q636C@m=w*^~s;QT7CmqeuTrBDBBGlUNy*w29QrEPq+%@m+^$Oq!lzT8QBtpIyBrk~&df5vYX= z?MLc?Tz9)OI_QUYmDUdnaGHDTuFupHBvYj7SzILP?itVKAZoJjirwG$?>y@VVWz~o zqF%1Cn<5k)q9C4==Df{4>2C3eX{WWg6=Kw%)EN3ZSaTKb{H-apk<2#p>~WlEg?2Mj znh58@Er@q><>L^6(4uE(A}~5?0H7S44~`GQiVvKqKoNw?<$wpkIu90_*vjcK)9-~w{Uzn*w;|nf@&f+CrjJ$@V51% zW(YzNP6OJ1o@7>ItQQF7WM6{bf~60k3z~hl-o(?9p~1K^kri>W@!H~& zpn|1>%Cirl`W&@WaFUjLn^Oh24~0OiQy?x*S0ztEJE3Wh%JQTJ_Dvgrd(?SuIF87V zxJe<}g*X_TZ0yRzt)k}S%ZtUW|Gwf2e`#v%(Dy8a2DI0 zQ1>x;VIE0xs@_d)H7THkHdus7l9Ew=20dzNRS{Nd&*_gTR^HSWNj z&i4X$rHweRc4jNQ)m~3`4$@NM3rSG{QwD{p&~ln>1{)2t+w*M~Ct;1zWj`9H^GJ+h z+lXH;Tii;;>CU|zndS}JZw>B0h3#3|YVYRSw_$pabZI<{6&?+6Hpq9p!>Mklc{$6& zbWYgBOC!+Dvf{JsAA)8nsmZxnWd$!RfB+y$E^g<3;NPD-NmN?bCSf&G_hZ6AH?+a0YV=?Ca(c6x3Dd+JRkM!F)M$8U>~!GbGL&esl@)dv zZ9u-QCG)G;#xol!wKtib)jB4zTcYRw?1eTKB0%zXAabAsiip#RCVxl%3q`rWb02=l z^DH)QK)$=D$YKDp9i@Tvs!$-Pb263AmMzDnl0`(Lw%DRN=d|RMdv*Xd4}JQTMK+6! z*J3Y7SZ%)$<+*$O=i8wrbHJwRCp?Mp$LL|fm@qjcnXA#xF0%94!=7*tFKWX<*^eoss8cyr=4{Lx@GstUiLJ zbGWX7XNy`gS-t z>I7oz5h1q}xa9A4&SYfXq>Yf^6Rkc`Leg2qGmxnYl4sNP2@=R;&*$3Q;QG(*#IG~o zKzW60Dc^XKcUKq_m44{K@F6DaDf|v?^b_R&cIpPjBGG^D_JCIoB&y_+*$USbBn65! z6ud?5>j#=Wh#a&ir?MYRb#mr2Bx;4}pA4M^Ie}GdixG#7f0@K>?xVL+!;*DUV!Tbm zct_-R<7>loSGrHCXSY*XtA%H|?`8$V|307Uy4J3Og)0p~ zL5-0I=2#oW!4e^r1BZT!V?E|m-gCZHS=Sa@o#*uiBnPHzSC{(RihF$}-!D9wFdae@ z&ZE^vbKqAXLZ(;66WVOx&2Qrk<<{nR;JcQS5(R*U+Aqt}9zy^f-;I ziU_r*mqEl!twknFtNoO3IApukxYqhAuIUJs#`vkYKFzr$F+4MLm#JEgj}_b*HEO+c z#z>978(%*(KOB6x#W~OG-J$}!00!6dti$+kU{66az|BgJ%mSKuAcaShAy(&esDPHq zRbJ+m-XnMh!49-KiQ-M1vnX;i)FWK2nHr7{7jHg$XBd8P;TWPKX)7>JDu_V$lhjt? z&rDltS3KI-ZJ-mR=>h4ZRPSG&Oi^jG$Fd_KV7!wBjla~j*87Ux$b4g!C%=y+*bZ-9 zw4Q}oo$5Cc6o>@Af)ovcnLu<%2%~gYs|_x5bsU75KiDSc+kg4Ll2QT5hbXszu0!k% zmo?Sr)nD_}p;$|Crop0C>&4kv<;vBkOZ=~%!fD16Qt2-5(apxQ9YrpD$l?ATY^XNk zj+m3^RmTj5qWq-|_Bl^oi32dl<$K@StH?Qu-4GOovS7lv1*j8mWStXZe- zXmW!a%4ZUmvs>)uBD)=lb-8EgFdEH+#K^xNK3=lFETfrD@K+$)ChsZQ>(#`7-s0&- znurgyz_WW6=NnTm9QPOFf#vH9=o>^VsO70krSH;QdlL_F8U#d$t-6Ktob5T@d~~p- zHV9vTxvOFr?Vd@`2;Y)BB~4rS9J2H>oTYfZNxd4SkICPxa|%KKVmlqmS-!nU?*>~S zpo2iM$l^rnf}$YBPqs(W^d0pKOr%!3m7o(?3bmezyc+{cvvciuQ|`v&PbN{dziSC0 z`V-3h1<;V%h>mPWn%FS1q|3!aN^>gL=u&B)M?H_CE9^y>WH?_27Q@8c!Llfz%VHH2#CX-7yyDT~J7Y*O>Gv^Y!? z5JbU>VcpDGsttyEM%S>+@qnpo1>Nc3sM+LUK8AVwSk0;WlKrqKlX{B#>hoA$^6yOs!1;` zv~`flVe!Fgs5Jhml6uc_?H+QTmcnG?L`~OoaDbUY^EQFmrxjuW3jC_fs~wi+ui>~a zF%S(;*ST{!ij@{Hhh(L}%Fb{i!M{_qfE$X*37k3#a_SRS{CtuHK54&&w+t^lXDp0X z1m`H96TH(ePj$iG+)Vw$dlB!|^^IZfDHsiZAjF16joiSz7n)O<{caA4-3YY|VFNzp zRENY4NcZ&B+fszWXB0tceB*RmWPq00WQZL9|5ukeo9;-B@!K9GvL~P>eot!kO#jAD zgBRUuZ$!DrjaLJ^NBeT?4D+qX>tt?%F?`o`R)WJF^fB0wppHpvGxaO^R%u#cZc+T2 zpNmk6E(Reh`4L4no!MrweZ+5DVLQV-m@gurKUlKRo20sIdnl|?p7VIGa+gwypm3oC8!N#Wo3VIIL-Sw%myUZ(X?y#>IP$IxkHldFtT! znK6&eU@DH(2Qzq;-A=+qiCqTGpvbNX#e>er{NBteM!TcME&N)FuHrnzxoMV^tZ!gq z2b97y2@oFpYK1|SsV_60ioy~bAExE?_5)7XD5n@POMC}fW{NJvUm4V;sGH)v_pEKS ztz4j${%jv8a+TdnoKe!iI{TeX1XmKo@&utki%!*i7EBrbE%SYC=QQSj+-euqd1G>< z+@~DTCDzFBrouTb96OB^&(V4lvlJx7MM*@SrKmEqHmV&+EC#j}CaiVva0=19FO3~kcS1UNF;px6l0>O@Tm^L<=< zXLyKuj%nHAQK$13a&R01TxK!FfPW#JHCqA!{+yl2kSGdnSon{vD1B*iH2| zhjl=iKQlj<7&8zIcig}08=R$psNhpbDT)38Mx^OXGuLM_-j8--$-rh;kF10A*lJ%F z8;=M1x%9|iH@VV`iQEJHT%>w@5cF@H zpUdwyHs~hnk1GLoHw-A`{Y*#_-2^%+#fcd;>3%ZI zi1n0>gM86AA_*~v&BDZ$9Y=|KoSU(uhUufwJAz#K6()*_zK5Ec>~F_9-V~}MhBgog z;s4{~BdLe8t|YWysB( z6Qd&MXOmB`!(%-KhRxU4oOt9t!6@eTgyBODF9ZoNp{;hf!sn2Csi@&vl>064C<6o> zi{LJ)c9;>tFG|!Gq3#dHNHo1Cy<4NvxnmhVfMTKChSFnJcs|JFY6l~8X3`h@hy{aB z9sw{L3QT9MMkn5vx`kyTDo1jqp$nEfm3>yRpJ`7rp9zRpesn-5queI8Dd_G+s(MZy zs6L!Xe62JT(+L!#JE*XM?1@VKWJAWxdES5w3@0fBr+}Ve=gXUtrZt?^iArsCf$kQj zwPX`OqpkMA;Qb}OH2)c$163#*vw%9#xWQqLQ!e|06ovErgeYg9QZqmy4&8t()b41t z^Gf|IIJ?ZZBu6TZH@ZJE9&VFZ??=|Ah`W`OZ5XFYye6T2!rVdES?AlgePvDxZ7gwB ze@cz5;x1^g!=UV8?*!B<$>*-!Qv76lAyFG(yAf5!%mIcQ(OnhZg*{h>WZt@zSs4FI z6oZ68M0ge$(un)X*GSR1QZPw;J0xGQ>Y|;gWdey|3Qc~f;#yVi-`C}}))_80Z9>3# zwmfgQCFvmZ8M29aT4KeWy9J3mPg=c`d}7Nzxq!@wS?qMmjdyAcg0XNNigbGL=tQ+} zJ0y!b_Ov9OPp8R~hBKXHc8<9!S7KaIY89MIRsQ$>2#p!H5i%#JdO`7Ekb2KeG&Ioz zn64y4jbDt5i5oD)`+5$I0rfdGHlD$He~>DGx`c={JoCA@Z}5gE`2=_tU6#) zf1P*hf0Y|I%O#b@Z+LFCXC2;0R7yaXLym$~o}`%2)Z!xE;y7*Nua5FP;A7}msCg@PeaCApe2diO36w#dlxA<`UEm)K<2Xg3~`?l_^vB$ zIDJQzpIwtma z*Tg#pnN3FrltuCW&t4VdT-=TXO_DlSi}u*<55EEk4-ind!(JwbhOMVbChbl%Y#SddG%>aXM4(Xbr_<4iT5nJSa-yI}PLSYuw*m1UIm>kLR1V6|Q5jq@!iAnD5`V`AmtE2YV(VyAk z9QdpnU%DNwaQfmKcr+ja$SLQE;xwmdVf=pVfGC6idh1QJ?ZP-$j|nF&K8;$eodQ|mCLkIKFOnpkej>9YuRrHkb2Lfswh z{o}Jt-T7A~>MCx)1Sen7NwF(g*5GrzGb%mb3d0vw$B)%k%-P#u+eQFx%y+P<8B5*|%5J40Ll_COC z1(b3vh$tW+3Ze+MORox2#exM9q=?u7kro0Wl{6BPoWA!yC+mCW`uu|*mz$ik_xr9j z%a~)15%}{9efdaeEor#s^dx`wrkIHwox*|WfE;KZrNsS#Kb{`>0`*c)^v(3G)VIq! zBPt@gPZw9IFOKA0DY5ue#FSEcKT4;+OA}8Xbit|l!5qwd#RnQtFy|aYMschgr9qWT zG)ZrT^p*3%&G~>7RKsuB616_e`LyU-4V6;~y zb~j&Z4_3of7MFZBE=xIb!NFVdo@8xQeS=Ze)A2h|uhgIAyK{%4w*R#7NHwcH-D25! zRMe>N=uW=uw}=ioU8G`ZlGg&65f#O>O+BIBOy3)Ne52OPXE0)LXS<>!AgAyS*jHyj zly4dLSSkiN8d-z;)-Gr^K>zfQ#CzAlt5pj6NLJf z+tDjNz9TCC_!gL-_Rt{GM%KPdb3#dL-~m|pt>oZQ{+TE3<^TF7tl@oZ0D z(#PRu=*2qH*=ogVRH_Z(NM|va7pn7nvR;FX#;rI)pizE3Y-My_ZBe3MLZ?*b9i7C1 zS#9apFm|uN=(ZMU$$h=~kJyYDYp{a;Ebn}XO-W0_A^)g;itG|^af=mZ{gz32Mwmx$ z$>ZyxLp!D~AI=hSb9f$29t^cf^1^Ru3b-EVVY>qT>*hDHC7;rr?xbIYE;tg+$W*a$ zAkfIK^kvUyyWXhrg80n=R!C_{E8EgL#N_U@3mrvE-zKn>Bvq=GSTQ<^CR^&wFEZm0 z8?`ca8zDSfVmgCR3Z#0tJIySWQj>b#UIwj8ZHo~*D(H~v^^P6L%&0^grn$jV*K1CIBxH{jC zbXtn}PEx^+3eD%QR1>UN=)1vZpkqegyEoi(R$(t=$#QHriv*W7r_-zH?@j6N-BDfJ z-hIvu?T8x+mUO~9ks*r$;Rao8Z)A{b2J&%DA+Ya!!ln*yN2@E@U?!`9!^h# z3`f*yMh0x=`tu{@te)U(^o6GqIhI61HqzL{!g9j(S+ol*q}z{1@23gblmC;_ z8$*G?o+89Gr%Nm0aj*Z?ogHqGQ*`Q#Im=T5k=#KS8hxTKa^H2md7K`qFJCV4A%*I- zD^Vk<>wZ~Inm<3LGrskoQENu>T*r)KI~SWRnY+Z(W>S5zmG-MfXlMD~q4eFxWGYgi+?I^$_AMd3H|9ZinQk;CNcd_y_1a{OST&}Wa`#ZqH`0ETT-w9L z&|vqYeml!u@f@F%j#E!|hEW9fg&0_UmHSXosK|CE_ihsgBNU=D9i>`pPXFNFXS8H0 zHxP!EzRIb_VU_8msORe zr#PrP?)Aqj;egF`)#25&zio5{>TqZb-aL};> zx=5r-IuAIyYNzhxOm=le-h9INTXMIIZ`R)*HR`{6v*2$pLu9*&!JO(2*_&>Ed0)Ez z6*(WH3zKszQFSOUzi-Mz3?b*qJyF?S+Z#IeSD+j;hjOw;?4xZlW%zzqx=6KF_%kzU zhSOtDX#{7u;1z1xFTrogo#}T}%eW8{WpnyLHI#eTbp@tJEO9^X1MTTp`MM==11`hz zuUK>~?paTGz;&W7JKN;(^`0#OAe+>yQQN!IN{X+Yfg(=Qr$}K6gZQ3PEj3uj780)O4knWQxx8b4l6CmD*CuQ^XGDANnsOC?RrY-%Y7!DEfV4 z+=qq4wsa#IRr|tC>C91FGLr16a?mQa>Lu%WmZ?F1{)4)uH|DmaW@MtB@cQs4Nk{5L zERPT)?MiO(o|vnu9TZ279+%aX;8r-hq?#48#vCtukdgoNC3T_4MQ8WAz#$MLk-UHYn>EJ1$Wjdup`Aj;B99Yya{n3wnaxiN0 z-}q4!#jDYQUcs;2Z|_VZz`tq3;y)OcL1yZq?b$t$RlHU4(4*j`lA$hI`hL zq}tz*P*AbR<7K>f6&0i5{3;&fK>jR00+o`~gBh5TzEYWYKTu28+#agY4|XRx;--$6 zusm8xi`B+}XlVIRK7qzpZ)|5fEk^*->*4*^0|Jr_3#w)%@D46B-AP?^FiwTpv~kZ! zR+T9*_)Rzip85E4M~)yAk8lndt3(HSGjzhc>r9_fo>}41BbQq13yr{79`4Vg%|LJd zwYp_6da+M80`ou8QNhq4`Ue~AN^OOex=A_FbJoYI42x>|IR zLgELT(>JSuWFBO_PL$|~dUZWgZq7d;|EQB13S`ML`@s+%k~n47+~r*pIhQ?E;PPol zC`~ovBxt_cqVIOncGU9@N8`V|aQrI$`3SGLKL!xTNyq9X2JZMs#5qOg&UkBgb)-df z%Pl6TFh}yoz==coKUh6|v4@CJ{|mJxzb&fK5(>PY(f@0Zz%a4}I1 zL(m`nuho7vxiF$)`8U^2`NoEeliv{?&AmEbQz4CmgKCY`8H3=VhFUFD z5e-J&vacBj;f2=70z?}`>8-kwD$#a>Vn(D@IW!7R(r;2PpY{i{f|Sq$IUiA`Y5O;Y z*TCiccfhT@eo}OEqSjf}bg@d$7VAtTXk}K-O)UAheloH;41M6>q=yagM8QcYF9XOsU4C#*5Rr|CQ4d zrO^I}OIr;DS9zqKukaHbB-7za+Ry#!2xi3|U1@8$f$S;UdJ$@Mwz+s#gP}sd??4E5 zThlwsX&eb?nsn_p*!32EL8k5LRQb0piQ9*_D*GPpyS#Q!n{n ze;)j+f@+JcG?DNos4{sRu^P$KnIYisTh{_ldoPtv--rkmzFa*&nG$g9-CB}rxUX*U zeVqgX$lBqy^n$-@#KS+74moxuAFiC|4CH(DhEQ~WMo9)@ZB43Hi$DKxue+j7Hg)}h z+Jl;6&K;I$IK>YJ?{{{8evF^sK)%vs83;qnP4kPN&=v^l?>oX#MMits?2Id=^Cp<9 zEmhZM_x*8N&V)*tLN@Xl=3j=GdaJg$n?L9b3RaV9$Iov`M~aOWZz(hqrZZa{$k&^u z^#_mCg4-zE3<=9YC6}QxrwC04raevcb4p|o^z?2E8{zaP7ER>CLI^^gT)y?LC^{)j zNcZr>+9Ln%V#zB7+d+4K3!a#qRjO+ppJ%?+V7?thFc_?9f!d${G{g_SVb%%fZ;KtP zEA*ZikFDuwY*=M&$-hpHTXw#FzZPi8C=!&=k|Nu#N_-gS!AgEgQ`(aZ`%8vdIhcXs zDk_T;{$mbOLi{#$d@El5OKxeq|sO^dAD;`7&HHncYrB~w&# z?t1oW+>!TC)kx_-JPN7V=-|xym3+<#RNqe^@sVTUw+RYLSXlI9eNucG}cI(?HHwBc^2CTN}~<$kDFx))H!*N znqU9Y8qsXu_Rt;r4sEuGzE+;i6l9yBZ489Z09C5-qA%9fv_0_5` z_=c^qYWlF~Vd1Wvw5d72Hk8j54+kQKWBxc-ubR-&JwtWyeJzrtG>2rCv)2(bYZ9ZL zl90H&NM6sbQrlp3+}D+w!5e=&GECk|=U$2Dc2!4kw9Hxl&+j4)q_!yoN+R~}bf|Ej z#Q*+$LQ1#P0#mwnBrlbT6pEgh${ecJ5%+ppdy=ir1fjsL#UOavP|Q8A84T`|EWA)< z!fZ+D8p!G4t)gm|zMQ(kp_!-U?!%I(@DqG6=SV zCg|HOX=&j$cwhe=45nPQKaPD17O|;uBg=Zvp*rIPwxzG(|8&O8_mfn$MKHb|TOx$Q z4XU-^QXW1KaTo&ifcI6;2f#W?Fn`W6qrH#P4V~8!TpIP#`(;3hST9h|@30d+5PZU2 zxlD>Y7V|NGis!;j79yZC`ZHGbjCWZiUqYm_cxSh+a%&B>c$P^OH*1XpK|w;ADd#{y}0lg z-!1!egd50Zr1@k4FU6CuhvMIHgK=MM2~CzYYI!H*^YRA>Evk*xDxSlMox$CbO)i70 zls|yR`KrcYFi%&a_s3cN(LnG>mz7Sc8%f@0CiCldbcUGi`^)Fr2(e*HUNRJD z-aubq*L&)T{7I`Brf(`({R;s%XisNw0^i#eHQu~xSZ@ppBiKjvU~Ros7(_?Vlr{$} ztU`a_KHLUzTPM;HZb3inh&&?i!vl4ssK5X_35h!En0lu?~+DbMY0@zDF~3?GyKUtxNcKL7WI12%8w zqNQ@{kGawtqb250gBDok)9BLIKt>7SP<%EtbyyHYC%+ZRo5@84rb5x zcTTU_3e%aAJ>9tbIwLCTEEI<$w0E`yeD`0IsgeP0CFlkG_9O6cND8DN!nqw6wT1?) zy3{E>sl>E#Ji;J*%@I-eDJ(nFH<9Yg1G&FcFMhlFR!1|2bU!|f^=cq&ypxtSC^_@O{gn@ z>ZqL#fMClzLvhQL>(EQ<;r=;BT$0^7qcuCCEw<$Lw}ZbWL>m9RCERDkRqy&9B#^;S zI61x4JTDbU@EzrxKTpy}?xx~mlEUK&o5k3=ob%r~bpsI_uNcUu`^+?Iyf0SEP3gL7 z-0{Q6=#|(g$p6evT+VkY1?~RSqcXp_KR7&|`N94!mA0nv+da#0wl$Oz2^q;Il(a=f zeVJMXeQsO359|hTa#Uy@Al6xl57&Cys0&T$m&Ny|4caflbfzz1aTDi}Vu%}L0miRKRWFtNv`l_=7JYbn3iXZ{C7Uw*)Mr*ssMaj?n*t96Yimk> z)LNF#X2FPZ{XMI6?TBb5=2{w!BdeW8Igr&r;_! z2VQ|-e+YbUFmGR60;@P_L-{RrivwWp$%YKtpifs}!ZUM*(i~v&%ji=`wB~Hn&E!tH z9EiL5r1&5rks~m-1*?it)xgA@me+m4_P~Dsj`yx}G&Zu&QV+`au@D25s zAgeLR%zhLMB9xgCvRAkQM^-}~m|Ch=+tMdIPcm6PTU*Ql0rHT~_ecuR*IJ-M<}6~DPEZH& z1uHpCVL5j<##c2R=FE!;>Wa4D2j1SA`b^1znGn=k^!qAvG5MQA(eEStyC8y?r~M-w z1NCZq+N{6p2<|Q(%Y7cWSVt^QD zi%V+JQ*7i_)4#B4+ml-H?zX5fzto%{X$K)bnUZ_X=G|mr^q7OOfBHl{I?!_jb5Wui z=*rsCKiZCWqX#ST+b`6v8r{Mcutdt=-F+}V^OAbLRU{E$OGhJq?b;@3=S}TNjy|*+ zdH>l`qgvJ;1bCV|=fG<2((mYQpal3HHguY!pX#c{mhNd--!cf%QBSx$^9sOWONO{U zkn{G$x^)vJ3p_~@$G3L8F**69yNtx=?S8hU4(&j}#P$Q;;wsd$+Ha4c*fTnEC=UN0 z8u6~@Q5?nJt|aw-XEsqJ&@JoZKN7U@SW! z*l{dHkb)`^6I|XNKLPs}?orEYSVas)T*9^*z%nYgduKu@{tJ@=EVXL9>)#Ys11^@H zV%vMShU)i~kL7@a^(9d&u65xCHm z6j_ypL(z{whx`Oz2hOk=0a%>Dlh-CxBMbbr5+~fz6|PZmtI(l(pd6oOpx|5kX=VFT zJiHhkRAjCMqvIfbs2^yOi$kJIa=Tqv-KZ5#NP8`MZ3vrZ^%<)QrtSV;A}U)7|DBkR zKArzs&JVy|^p75t*Ug$)KCNdFkAlP+vjFR6r5kGV+pR1WjL0*;vKlwblB2(Cxbc}8gh8G)tv(46dvg=3$Iunx znf7Z+&n~17LI7H0Vk>j`*6?aEaNp7m2?8({NUu9Gj%ZTp2-HP$hVxNab`G=p5N*uC zF#i<^tI@aqmVp%1*N9eJ8i@^v-8|<~=Xgh|og3Xs7hm=?2XWq7gQNbtN=!~I*MG&- z+tL}Ho{K4$K{g~TGUEyf{j6Of6cG#}p|a#|XLW8Z(Ao_hffpUr5jTZ){Z~a}VqACv zt2058&%6~p^IxvXCtw4zpyH}PQVBs~Qu=3W`q})Lp3m4e9Vix!kD&cO_52ZS#m4-J z;e7Xt;VymhE5Vwc@}GQF*~{Cnnm#%?-Ne&;Yb4F|2egkZk4y18yNZ=`!KiSqzjS7BhQBsDeNOtoz8eUmvBPU&r45Wd zq9>nY3wTj@us&85@2mJ55eAxF606R*#j~Pmbgdt`83IoMnOI zUA#7H!H#-61c9-ETQLn>KreT4?CgAfez+=A``iZkrz6~cVOCpfCr9#mbW2v{eSN#t z`Cf2JEenP}2e(#&FlJHxZo*SE;-OV@S)0XU(#TLg$>tZ8s&Q#8l`0$?6QaI|u9Md0^EnN@6?C}OHl7SU zS$1}$<7=@9fB2Y$K)ch_gxYcX^k5NuTFrwq&I4eCmkWZAM2J`=H3}u*`SH@jyUz7K#CY6}-?69}9 z@d|^Ik9DW6X<_TR_10-_TX3Srw#0#NX$ea0eq11zM%^M~wVHdr6nSB^Vw))@J}9#G-2A( ze;0&l)E?V~*W?DmYi_#bef7rTsE{vO5(CzW&qlg=bxodMiKUev;yjEEN6YWbO}^bL z^Fy!r*V`v!k>}jX-n@`Nx3IQ2|8zJ%vNW^@A6k+Z3Mm8m9Ax$BL6i9N?9^6Gm%J-n zwtL6Nw)GTzdky0H{B==lQ8UJMsGZqZXa5-rF5D`70SMFBG_7g?>Mr_|Hfnv4h|XD^KRBFkc{@MuDXCuc zHQm8~zOFkoHjkc~i~`S}nRc*zH#L~a*Kf+l!PQsCV(Guv=YO}Qku@36!9nSZl(7dh z&8tTyr!L=Oa?(aMCZv69d7h6)p28lG@^~QMI*@75Rcd+Z(mZoGq|WskpNFGT#%oD) zWWx!uKlPuPf#G~>yXaY#R>QNHI?dN*ip_b4N_x-odT~%3PO0?qV1t{p4)k$TlBV?Xg7i=I$qu1WwrHoc zjyqLb`eZ3u)T@mp!T9Q5l2=sIt1pBL=Lc=Gc8A-b)_du=FfSXobxgpIwB6sx$IOcX z+y^+o_UAvY&bJKZdlu&RJ86r9Ho10vbYvtz4Y>}PnI7|;w@wR3@)LU|zGM4Lh+)tN z%$pjSq1$C@^(DcWI_Sl`ds}KO$_u*EQ|;-}Q6a|u2Wpw~V=3manNg!2HaES-F9vUu zCEuES{80Yr@{DBu-k0;;BYDpaF%A6D`n;3)JUTYr{xUNv%c2a;VvSTIDdD-!VJ=W+?x*Bb}uc8k1!4tdYKCr<)bUNw`4cdHd)7xg@}A)EqU zTAsl>OV{P|)L?JM6ytZR1G9QJgNE|w0TH{*Ns3ZCYrDvX6Vk8i`RZyqzL7sng5K8f z(tNxqw=(}9?LGp|9cih{$RbCLp*SBE`n|`@PILzLo*Wha(~Z2kk+&;b-ejMb|9mS3M!PK!e*QKa z;P}ZzfSUJzSL(Yo>`yq{BS)Bh?eqR4a)107ME!IuYpp?s+= z2=fYtng)tH80&}WAy>ZLwy80c|A7?-2;H3bspU7y#&m5-?Ec)pE`z?`vo=@T)2~Ja zm2~ptq(r=Khje^v+Ie~~&_33ZK1h}a6#?3|A{?Uo7seFz_-6w^>l6eksjVegKocj% z-s=T(lHk8;>!fK_xyeW07|2U_CsCGbBanzbOg1S)QQG)A5jgXtzsB~p}5;=EA;MpeD#;=|g#O6(tV(x%X z?n(Aj4m9_9BdY` z0Yn`AV&0x2*}4oc|5{H55Sb%z(UYx2$2 zG-rK&+Bu;}Y!Pou49W+lM%}Xiob*q9&4e^h!`_yR7#<@pGn6mv&B%c>R>jJ|{mTN^ z{gF*x+W~g95c6ol6ct1`?Nu|Q#yetW%v^SA3(c03*5>0)YSxF!)_tq;1(kFI17aSv z8$uXFR5VU~XX!`YoSwSCDKxlhd6r_8pE$`H%Fks9|&E2QP-t;sR^O4L!`%g{j2dkpK+GS0?*0+8$11q*{j6vQJ&;)q3Dd~XjrG&6c>{E=) z4ZW;?Qv$5#i)+CGdhfE>TKb!6hZELIxmEJD#NCysR6X97E~TzXp=j%LJxFk343czn zXV>zd8TA_3ugjP5O89U71%C~dK-q149h+BE-;@Ssq#oRfZPP2B$&RsByl*viAUW87 zw|V_mO!uZN&OO-9FNezIL!x45Fly$Z{&vM$%FJy;AC>Ob*pWPAR{DG+HWMwgys$Rk zUdvxun2#II8{fz$!8)j&;JR{!8w2c_wsZ|Vq%&RHl8y$B%}A%o%hb^Uyz`QbdG=P}4{|N#BJ_qgGpJ<-I6plz*7j$NNq35k*zApb_r_pB)2+G_ zR`W`zG{3huZ|(?>2~fXn&{5U&>4^!%bj|Fj**-Qa_A)*>9J;3GZp=Sw@(i7b-J#RmyM4eoJk4-l|ob!zv8W^HuJ*Rqa3v+--~u- z)2%&CHgv0|X`_=N_>r@s%Ke4k3uEj~|L}h6`6k*~8-mI;b#?wXfauMj z(#vsUCdM>&Dp%r>{Q89Sd@Ub7DIHx8&9Mzse;WZrM>NI(TM@bA%njk4H)b&3>7OQ# zfWe7LSGbBoDfN|^(R81Jv2U%TC%@FlzuuUyH#u4zZ276ngRyqJe>&k<87j0%pG(C7d+aS9e*uEj0x&FB>w$!~T+I@|;^SRaZ3hQSJ1@zFs@6&>^`0Sk6 z6QLXS0lOrM#zri;?`w07a-%wVap3>gy_(GnbY$H$3`eEYo6|q3M~>w0&k9v=z5hBx zF(F^TENz87<_OoAWQLX?R0EL!D0x2z0M!Z%yay6hn}kr^Z%@>E1Q~Rl;;; zJ+H@B$Q`fd0|-$*6G~+d^yaT3L*g0o$DEwrm-FzfgqHTZ?E@sgdVWX+cA5?KW$Nn} z8^|t->a@BlU!$;Gk24 zteTy=Hr6(mb%$&1KGTz2w)<|uU>`L-SqAyqxOByEUSjEkCM~Bznu^cmPFpAc&6@oW zVt`kZ`H?;MwOhprX`UaRhAVeU!{kFJr@aPZU9`>xwKpHap?h)cF?FxVpGOvYBiAZv z!eIUcglAl|HecT+-BC&F=O_5^EmPADI+V$Qm@V2II=bHSrjdNr^Wla$c|~rnq(|0g z+4Cc~0nO=&nZcS{vweCurTXoYv9E^Zn7)vc1@cs=b=C9r%Y%nJc2WMQ;_%IUhb~5Q z(U~ronnt&!cg;>YFlA zpeH&)G;Cx}Y-(T5-x?@>+PU6YfRXLE*z zaW{|w_u4*oexKMTd_ewZOn5Zk(ixtf-F=9SU{-py@{!cXen9{&#Wozh8STp9?Tw8F}!G<@rOEv|DGiBsv#38z0s{DyInbeNw8 z?Bi?~Y_6BK4duyO`-4z)J8Nk)wk4rM`}*rKl%KmO?~Lf)lOJkJ2Te(v;CnNp&ivzc z3FiC|%``|Ry&Ar&ec8V+Uy%=%%3liV<9+<9TGA2PURK>e6;j^Q)1IyAkZt1dcx#L8 zvvem^XH!VDC0eZ8z!ewfk2j|qU(24-hQ;|~d zX?}#rs+wpr83tz8(jxKmWRtb{NQ~w5AJ22i;=A{N%pO-Z7ax?lL#Tz63Yu zXdiD5S%8i`{@Akogo^sb7~cJuF@GxV#)bswXNi zPF)_P>uX*KPS-crWEua(_1T>Qp%1sFc3#X7+TDZ6JZWB>nQrFh_24ZWJCeV(Hq^Ry zeI?uDsJ@W@R1GBa$j(qyxV0@cZ53*tkIoG?*TV(tIA>bW5*|g8XB@U6{{y*VQ6S#m zRzIW!Sv`*yYfpxWy|Pt$7CC3z^rV^cHtD31eCWh9r;)!qJ{>!p&#dK-5C>bF)ds(R zF@K^pOFfq7!uV%6f=j{j44s#qQ=N3u901hs*M|Bp(L? zTibCk4^-1N6M|UVwsj1=&fhkPfz!su68-Gq{J~n@h3-cqpZ{83Ld9An#|YxvNP)rZ zUOz%*+Pj{g)Fj)@Ne2DnW+cSU_s}j=C$Gy-)bj8fp>K2F>(Q$0wISZ}eQ+ugIt*BS zlDX+H9xHQW^6tr>?6!=)Kf~iaEa?n@2b3GFAjmwfC!ei7la5^FL`nHm5%Mw=MRMUYHWR);H&*x%LXT zO{fB&Wq`=%g5F3ie}8@1YFQM@0N+?05%qaaofZUIQ*%f9`lKlGkJ>K5Va}ct!`3@+ z;OlupNBXmY=Gwfho)3II&$Af&-`I{ncXPg>5)?Hx(mO_lD$(P!gD1awUIOfFnVWuJ z3;GID$Ju@1cW~H}=zL#zGuHRu*w34=yq(QfcAys09-cLqwx)q?g0t}{8h6whC!}qM z^DkKN)bqF%`BPB4h4~rGlQ+VZ@@kvctsupCRx76jv*^Uxp@1}J$5>-OYg{sJ&>B3B zUbrEPa{pYB_p|A?EYwX;S(@qDf>st-?w$}{MSq@?Ry3z|Gt+{c@0y&NRCk;TLHBRx zpPH;K4Sn_1F9auNPkQpL>4bqi+sN3+pgDbIYV2iPyJI?VB)=d^@DyuJyUGQ2b?bTi zHQ`ust=hvh;kBTzyvJrMFsCCt)SiJ!wuZBYr`H4H8p%gY3@!S#Te286ZdKN;y!~>_ zDL$|~*UbJlh7ak(y?IYEP-D}amb7j``j;s=M^wLST)Jm4tU}-7>Nt>{9Faj+wfOa@ z){b13&##1;;~?9DvFX8T8a*dHrgoYYYfTr;h}!8p6OzokU}Ij2D!Di&lG`rM$c>M! z4$p-ju8sTh#g=q^Yii6&v-Gp-C7J-U0$ZCmIvr(PE(F2(IS%Lwmj!!a^=lb{`KY(@ z5$GB1A@g|M*t8AB?3po5yK+uC0Ec;QLdSJ~PgAFvH4NunR>ZM?U`c#aJ~E={`63=N zusM{m^(=5*+HpbJc{u-=`X%8D!D=Y@r7~}0MIMJl^jhBE^!$xHmWIEgv@P}0h9&Sd zDV>TuFfTB=3+JV|cnjmx+l_ocUwHLSUmq?TPrVj9iI=R*&$p)MRZvL3W$E=VlhdUw zse4{5O{||8F|B8GNQzx#0?sk&t^CGNe)5gTfX}@ai-+&p7|uIhtGKi;z8Ufo1h^&b zI6IVSSIvtPtAKpDp7*0PV#{x3-V>&`A|Kk4_FbC4?pv?UFz4B0)0Ne9($v`R_|e?- zaX)`j`jsAPYC5?V-UlxZ8u-OZ&Q(idQ}2Iu!Bla2b9*)L^XYKo$w3Wz$IQ@~nX^M0 zu(~@rdjA({F`IaS;-ao$d2lih)(=9Z7Ujn(vE7dXefQ|}l7+je!K9iuJDoC|x1XLa ztmVJZZyC%#dpl2qqOZ>1w7m0jJ_i@^t@z{+n9Z%xKsh--nU=0FVN@&H*<6ss?K@d~ zmR0eGNAmo2!SLK`ad_NMd?k;sBuZ9`MMdMtYI=5TI;T0^Ge21Cr%p>>BJV#f9l(#w zg&K^B?SX2x*Yk~j&hnsPe12m-pC1P+VPOr1Mm2g)5aI4xkk0Vdwo0c_qwPrN+W7WE zoVw-t7L=03`715y^P*}c4b`%{c^TH696B|K69-RE@PU>&X%zXU@#&VKyr4JFCOWYy zykFaw{2^JLP!W2u-9>w4^qMx0K5E zKKd1l!XxR6>+>ltueJ--5C@pg@08dHGOc(Yr7vAZJtM0;GZ-pN#~C88%G-Il%R*&z z_tp7%xu7ra+?tLVohG-Yp6M~s+cZDY;%yVdYV%t5ti!P&|2@RQRk6qPKZ^pzy?lAT zAHHF0o4+Ih8H9mx=|lSWIVP6*LvzwnWVtb^33w$P)bfOlu{J(-X?7_NU6mij8C#RP z=tWc3vPLv99cSTgZfw{cIwSPh7mW)e!taevmsl;?l)sA)up~GtTP+Q2?He2NoVK*p zVE$$`c6i;j#*C<2{9WvVGp48UoDHc-=pP63VfDPj+I*Xz@Miu4M1`t6y3t$VKJc&Z zc-N;+PG%^7nwCC`&oDXNHJneHoczZBKr_@TYeJug7QH(5!4-KsWc8k`4|=keA;9fp zW6l4=Qv#TsKPUZIo*kcdt><+HOtt(6I|91eHTj;Ae9D{oL_6{;;=JJ_U0t&O*?d(d zra!9FriVt^<&)B(wB1I9&+@x^^OeKl(D5nr++|tZrue;+XWkcC*nAfLzs;oJx9mPM zFq2oOr_T?^P<|6U4X>e*SG=9CCX2K(n1_2U4|Sa}oATu?vHOV%_0iGMLmexh?`Y!eh^z=bgbg?NZN;1z>JhlQkXU4 zrBuRi@m*f`a2{G6Y8XwcgZq8)%KRAIUZ~*(fEr;NcS3Uh|94WFPoHgC=*fS5TzZbb zhVqzQ#m2y#IWEmXlHZX311DLZf5h*V@=rDGv{h2~-aIKS#W9!?NbKJ_@bGdUMU>$% z0sq%<{?oc(vhBMpaD%Hh#?!lt2yjuL`ZAX3_-HXcFfF3W$EGFS`iFtZwLE7q1ZJPx z5H-WNHTec)xwoR?vUlK_|CYHV^3~Y%44s4-=`5#lT<9TOX1~V!vUgS#j7jjp^tQ8e zx;j(@Ut%NJ5}ITEGzz*y0b}#z(6ssdwDcBPtx;(@Vxa|@MyOc8kau5~{qLje^J$dK z*XJR4%W(AXM|3A0R%KFJgGo3cm^>7;kk%?)$@kSnA3hvvx_80i zqMnzst)3MvUn=@4y`_`VLA2cYB{8;R;xn8d*+12y~AZj%o zY2t}-F)7%C7mkU%zw795Wc!A7Z+P{_*tNeI@{b1pR{kP=Y?Hp05ImIe2fEYmVVmPa z9rS`J2?n@RXZkCtrQv*AJ>Tn-bIN!-HUxXt7bOI0rD6hYbK$f*iE?+35B3z_pNDL2 zb|?4oXuyKO8IxbmI9y`6l)M?s?+vFEw}%QhG_N_ZYp%Z6+TEBEw4dMcb`b;pz*?SU z8I$(`-71rv^+EZ#a(yV+pN;Axo=69kM8j6?_k)Q^yxllDT%t}Kl{Vl+A=S{X+?1C| z(`)l=oRAHnE%W#Oz`!+k15IfT{R&*TiNWLTF~#8wHl@+aeBhxZ5L5?ta$RR7`NjQ!f%-w=C1Prj9>Hl=HEY3Ntjb97(2LQvwD z<6_SKmC50N_82Skdj761Hze?J z!6Z?g-i+hgp8WYg(12B!_5|1Q=5?`#_RmfEH|9Y-!MLa!e8@_p;(nbrA?;>CbzHg< z3fG=?M&@iu&+rfJ32~v%za7eWyR6R_+h?~A#eojNp4(u1db%Z@IX;~Rav2w!mHNkD z6^*siiuU8Z)Mej}j$rBAnNlTJxNZ5Vu?i*q!8lb1JtowU=#>7iNl;f1Uv&h_^{|SE zdS1UNt9v(Yu+y1O-5BmU2ikG)dz;gGqlz(Uhn95v*t8O#t++&S6I(&$hOH;Dm)_tZ z(?{LQ?#!nAM@n=wT1~_p*Ov6#&e*1TgHabh}XsxE1G(ShDmge-81{^b}buE7tkG&`M<0ftjs_l&% zV~IdVu(Bl`Ld%GD3NvxN;n+aV7uf%5ga`MLrW;a;x}`r<2K{P z_|ci6^Q>DRlW^ugFfLt10RWJ)CCDfD(Ow?PuVCqKid_<_(TZMvf48*64$&g(sMXK` z{=w+To)>keRwhb3vl_7jWiECZz@d>B_2h5Coi^oP*lDeW@+9zW0XZW!)>IHjg(Cfz z$EJgd^7b$#z@~J9Vg|rsL$j#6h!4{!F5+a0WRTG1Ol1FYeLh+l^mfApbw4-M(ofSQSS50r_0@^qmLx~7` z-ybmE9TW*Mkek!LN5#JR{oR4^_jab8EP7)g$X~TgNaui_yeF-mzWklCYwtT^M*Y%; zV0&nt-84GHtG_!Y9c`Q5JQpicN#m9Y6s9wqgTwsL#!v|xOHHvgT?Wk80U@(t*>#0} z$crr0O)y8L+eh+U2KtRWV06pAr$0YOny)9cM(h1CA$sRv?zJ`6l9u9ucZcuWZ%3!g z9lY+)PCUJ6oZe5&gc8(1o=)qvCpe0tI(z_F_d5U%8b3koGMV}=#xG8e36aJSn-sOX zNFgDF+=Bhv8+qixo}j_gsQI@&nvwX{`*a3t>(irx@qXTz*dd+R5%}RZD>0)v#^bX0 z)Ej$qM zGDbH`)^92N&^CN2BZKC&9oHck%jnp%Ue%fYC~vj}nO}P@gRf$kYqzB*_CRw_(D-jc zHsG$SBr8?oCsj@6ZWdU&l4yTeiAZl0ahnH<3eyjGGx-Vq`ES<5=$#f_0&@quB%f|d zw|9lJ)e<_!;N13LUw^YbsS|!$&v&pE>I*Fe`fN_xD>O9lQ5(TaY*Q$TPF-mobVZ$j z@WA`V?iGPwcbN&MMFv<3T&OS4gzfaj9xlS_Q?@zOpH0bB8YAKb*3w0{N{)VxG7o;2 zvJ)|kN+i6uKY0Eol8>0?*TT2%WxE4R7%L&jKnv)-Sl`?eQrfR0eVZ<*wQB=J2B!9x zZEDNKJuySv-siWaZvEVjLs4Nm!e$(tU$utK)fF5+0G-I+GLn}=t?^-;y`Ip$|I0w! zZV~>BmN<})pa}u=xX^ZoyN7wu2H%=K(nyf3{%gg!mjC3Z^#+%I#pXN@mmcqz#t0Dr zXh|i_!5i<6KygNQ)LOUG%*15^9hm9Y^2^RlfACT+qgQ5nGLWAT^M~S$?Nv$dgN%t_ zbeOwi6ABMX+xYpS1`oJcYqHiK>a3sciA=O*Q>+hY#t$%zW&|LQ+q6pQi*%4|AW}Fd zZ`c_#-($N)|*z92bi#4AjwS9I+U}A@k4xEIBl@n#Va0cN=lp$}bKQ;vav?{88D>5Yypp1+PtA1lPa7vvx!8yef;eBW@M$Y&hZ-508Kwx`9< z?+cjjbzAZocJ53Q>Aabh$bcQO&vL1Ok(qTpyP(|{j{D@LNNvI zimy-g@pkMW?%VRL1knh3m}$=NP(H+|!}V3C^#;yA&Wye}9o(Ak#|CT*H<@Rt;*{Hs z?XT+72_9{^*hb7i=-Ye>lhzjb=E$2zqe>clH1eOzNfW7h>Do~`)dk2w9SKH#IRP2p z9r?=2SYLjK5;Bc$yS;s(F20Y3i(3#%aC3UNJruV@v(KlsEH>qfKUR?Bh zr^rDAVJq;~V2pGz<^1~vMG602=INN3-GMm+-j_A<5&!}&2W?C#qc`s9b497_=L6yJ zC>@L{jPbLZ(~RzPssGXyXv5~NG|NQ3CA56U)xuxNZvO7YA>wh0RsDf=eAzZJmVRq; zPmR1!QG?lIRZ-BuABh&JWjIo{S;-j#QPsb{KT749z1T_VXIg2~3iLv*4b`Be`t9JF zGsKpnjE7=(s7%52pcb1`i%kPv#NKf2*;pKljsPsTMhi9th}sd^(+&L#=ajZ|yEX0B z=-d|!=T>G?1F?v7S;6Jb9SBuejnbLW2J2>4v^s(x{@YP;zt)xL^eGU8GbW(PfYhPw zb@s&O(bs!p!ROV1JgPP9Zw_MhTTtsOyOR7eyE8-rC+JXy!}MfBBm8%KhS_~t+B&l@ zkKv(K4}|9nsNEDt?KcO;G82eJ!R9DRvY}y{+C9nk^iKFqkK4$$I-+0OsK&-lU650V`c~TwmM^9 z`#{={@Ve1a-EEI7Iuf$O5UpeDQEKvJbo-q1vk1%BBX&Ww&*q7gl=x{Hxa0SBN5ygk zWjsP*Xij8aDB-{y=m0wdJy9pu2V-h%(DQi zGG5v4f7FTuxyzBj22bw@UG(R=Loe%l>J|%G)o^iD%-lq#7!XGlp*Qvuug8ATHx0y$ z<9sE0YdVVbv3YT4NWIN4&{U6khP+_3X@CNb@WXjzAouCT`ttEPpG?iqUbcQ;DE7Y8 zmL2JN(Gqe5T6nZ8Q3>a`FLs7n z>9JkuPj+2pPc;T9U63^k)G~8_T&(EJyV_$Nh?;GCJXNvL5RlSZM{2^5r<3Mex20Kj zRqV*~)J6{EmoX4}6s93Mn&&7YB8mvw;u4UmCH)`mrlPjJ3nh)tbgVN2#4fGScdWaj z<=gk^i|u14_A#ncL&1g}gW$pI0nN|WTUWYA1Yu?TF6dPdoR{tXp~A|3E|M#H}TKsXotsXyNeQSZ+W)Ad5B z5^^cwm;L&yZEMOVmW4Zmr#H7$9a5Ot&}W9iLv0?K0Y1q9Vq{H zgt{F?!3mNJf-4^r%aUqryIRGah=4 zyG@sLX^Bg#heO5U0t%4*`8^~xQ2omrKg=0xj#}$-HKAD78Cv>7?Xf$2pX5UMU+M0j zHfQa$Qp*Hl_r*5rY}$sL8DX=2FW_Bw(;4$D+O~6?E6OcccEm}B^EQoq0UiE+x_7bOv!(Hd^4*Gz!Owq zE-)>@D^MeSkH1?{l7HHCz&jFZl;uXo!};BCaBpEC6fpih6cR1~H$ChpX{r+xrD3S{ z>k38vv)bY7(R3_UmnzR}C=7%)>`YE@_I-K-h;~OHX$viKq7M~i026stH#Y3SI)e59 zjHUA6SfzTsO{z(A28r(jfQ9jFAu0o zXQ%@HwCL$A2J~THvryMP4d!cohQ91z%j2u%Zfr^F=;W1XT~a60KF}1hxvcVWJ1m{? z%Oev6cQrKU#xfWxSpKfKWUMKI%`O5pjn;HsbJ7>Mt+ZBtTXgLnv*lr5juivG-9lyCxP5s4q}}#Oof4cfJ2e-h)nf zB|$&F(2?|3cGLF2rAK4}aad&~&58yr-B7Kx1w4?iExH74Me@#9ds%RURM1~ClkEt_ zl6Q8b8#UBmF11!8pR8^i%(5z_6sh46YQ|0J*JcNzL345-AAC5}?*Qs;6ow1IHD&E>&_T`H(68ZPzy$A)o7a!OHmnpq$6F2 zGu)Q8h^UN%$ql8bA5NkmK}ndRj$)^iJwbEkzr_H$6m| zjd52LFW-w&qF)}2Vd%lo1GWO}pP+CoyqnY7YH;L`{UpSHPzfj<=qIr37*pc9TTSqvrzR$o1G2qh{e*`C+BqG|qTTV9$bjaph2f&o2<45#`8)8W8wef0ZEGO} z`cI3K#iMGy_^iUAW84-v>R+p&`{BbhZ3$E6N9h>TYA)Z}mt9m3m6)?EKw$~;-+{5T zMKAN6&QR5R5Y?Y9iV!V?*>AOXQwGOKUw}uM)Efs)w{x~ao-B5~O1`zIQ}EAf@Z5GS zO1uX$Q8jO+d<&%?4E2uXd_>qqG$JWJo688ooJ6bZAE)%(;=C>?kyXQiQyx` zyxnFf@2i#=%pXbV*jj9n+)0LoTo^K;i9f9~{YYE5*w~@C&L$M|DgEzomcE}UE^_w| z2HxHT)K#c}_$YKW$1~T*DR@TTK zX+=etqDkvrcbH6-pYVxl(r;@0^t?v_&(#|=H{98xGxNn-sDJpeoz6K@rF>nw{!ey< zw!j~YXTjf^L(g_AZ954^xjX0EaI{p3x@N*Mg#KvoS|k+H+M;rxodsigZ+kdCxOC4^ zm>CHj{_BfQi&!e+{RAo0`>F?5ycb~%FRq%Np=f6pUbWvArxaLs1NStdA_^W)xG2ng ziiQ%JAC0Nus94sMg~aV|O}7_ao$2LUyMt)GpLEBIwdIWAT*p^08f%ohX4!lk3Z)lK z$2;j)w5Gjf%(hU9o+z78SF47P%+swAoo*9Wosf5`VXp7(8-pV34>J zZ{T;jNdO-%w0zu{ic&zL9MW!7o7IhbVtvr#qQuw;1t%JYKci%;p4TRD8N&lC>&8bx zfpnA<;Gy)^&D1d*rjO73orcz8owdLkvO-_Ft(xtw^j3KH3e-zbrXq2Law%$?*- z=7#RIA6RNF=kua!_=bN7(H&p0J$CtjW&L?ejB-w*b!IV~dc$D;nA?ZG# zz0*$^j&ow2;r(if!I+#{s$I;-Z8)6!9Qb41lxpytUu+9$Bi*0(Wew_4+oM>ZqM`*B zIZ^-X_E{DXor-?cXjY`^^VXOp^%XVUvF$utf_(0twv$9QH$)=UOAh5f=+Or9F+4=- zaXz)BVkvm%OSn>4k)~T7 zH8k;P(|z3Canmv3D`_vtfTENKP*FerB?Tm>s1`NDZjDeIyALwPZ3?-OeiS+lW?)ZH4>ve~^Pt)qTGc~)s*7-n`_3`SbhzjcUQfc2xJ@O&81F*g)zMT#VS64Y zP*16-FwLvShSU^QhyDnaNIPuJA9q*yDvHy-wVL6IqPVpNd_izP4X>D_c{LpFImgIE zMQ61x!efZ377S}F0$Y~RN$Ec5>9wWL*(a(*59^H|k4adpz%MTTJ0B_4+9?(!aa=W| zDK7Q3kpB{mU**2%3|Kub`iJ6)PEyY8#XiyzbIQvx@9=jmZ7nL|Y%7|4DO{^sQ;NHtf;ED$lhAnDB&irC^jvBQZzF_ay+4)_HH2VdO;39r* zD9)-pA}apNOv>+K%tx-1tPPza3S&I7+oI$asdn;D;#7R1OGJGM!k`8l66N!+OqxVC z%k11O5m8DkTILLxInql<9xDbGC)nMvx`Izw4>y*%9E}G163`5}wpZ zq4Jl%dy%Nv5>M>+ARS!3qPO{ei|R#Bcma*aQk|-`Wbl5xEc(N`5=yz%`20U_4HJqh zl$V|%6qM#b@s6Ks!H~i*uLtK!F$OBM?5xJFrag<_!QXO*`8boea9>3_GKAIm(~P1l zSG2O#s~42}^&0m9P#C2zPIK}&(i#l58uRX}lwQ&xOE2-CqFOnnzvDB7t)xh!`>iT- zG3D#laBLr{#++I|_(g}``VW-=<+|vE&cTAD787>ZyYF>5nvIfPQRFy@V4`a^^2nE{ z7Wh=HX|Y)+xm3x0B$U_*DBVjx4FOo2P|I^^Q-k>2N6IsnPvL>G1E(rIR3&<`@zkht z0PKBG=%RwSr6|1p5Yf?78p%3Mf91zTEBjF=tr9*m=W;Jqp93f5T$LWQc{nncnEVJL zL@g$;`!>SE-68*j$)J8s45zABl;p?yFRj5eQ2u?`XC4kFrF1Ho z6t5D3TAumO;QZ7G)h(^Tm%dXI>RXT+b3W^VGdL}3trbKD#AH-zo&`m8`u3K1M|)7J z)Z>_L_>ETfbhsqhO==21K_vV+Hk6f2&lcFFTrm(aZHG4K?1&QhBGu#${>La&E|A51 zcc8t~MMy!6t>rz`8h@Ghc*X(Ze;AtRHIh;5HAvut&)7x|I?m`s02E6i}mQg?jOp&*Vl@Y{46U25Iir- zyDKVZpR`%*)*~UyutpE41qc%25}7Y|t*|5BQ&+(Sk!ifj+)_h{#@^jJlt}fwS&0*_ zZuDN7Of^GfX1UmI5kvK;%2FYh)8gX%j~-cZE7!w_%Ddi4-BO9qAU!?o_iBFSQ+u!e zZY}@SS#3nsF`?M9w={>2dWS48GPgxN@5F9akBK(wf4_)jLp|yZNXsg_+r=m1Ep%uu z1BL>=V@pIK)XG81^N5{tO>2NoC;5UoJQk~Pa%8y_ixo!~J-t)acv~V9eHM@iqp$*5 zDqC6Rs@71qLm2RPEf3Ts zdKUfET96aUS0u>9Lv%Ih2co-rrYI;{ggITEfJ&r8w|cCqtYx*tyK6BkpHUBW^qY&T zh;QeX$ps?(g=B>=rHiH?VDlOAp$Pi@qMVRthhp>eo%PTo`n9E0Z`pAw@7mw}c{Tk+ zmaK-`5zP5SQQb$)!Cdk!^yxczwzcpZ(oI~2S&`CZ?nO(QBa)Qdq0aHXUTlpUD$@G9 zP&+s7<`N0#SszyOtE=&T3u$n?!7*H-61v=XxG7?$Ghe(Nee1Icp>QFwx}tl6nR`C! zE_%_RCwGS|p5Kn1p$@4==Itp@&*&nH82 z&=pcTyd|{UU#9F@bWM*d%8dt8L*>EbFz^-Pa6z}(wi;bi5rCM3ilvM{Y8K(eBJRxM zOv!*>wg6QIAHK^$3UW+)BnQ#2oRf!ZaYKRdPTk2iu&7j7WAv1YNDDmez(V3Wn^s22 zfP?%Ie4}#0k&mAI#mgS=#Fa9q)B^#6UIic1|> z&!6+A6wI_55Jm4I);AG zLI)JR+z|-L&ox0Kk)+x*t@?}B*l7C{92Jg1Qy22q8ZIz@Bs2<5qRrx`)$+CwGu~~o zGnXZCOepaAThj-`|59%pTh1yDjQH@0Vi~r8oph;@#KT7lF7$`8j=G~C>zt5o(7#k7 z$Xr!TKXkh*5mo+1aL;o#6e~12k;fhJgqIhj;)UT*A-)@O1vfx7r@G9Qm|}xX7JsYU z5v7-S*n(hRKDkrjyxpg|AV1~A7ITny(Pu8VLscmnpP^1Dv^_uMEMCmR;nhyGa38gF zEq}}OvmRYPINE9dtAV@eA(5666`c^{Z}02R&B+_r9skBneUi9)`KoMW=kAJ zP>IhY0(;|oSEEXiN8GGE3&zq(x?lBDk9{;K&BseNaY`Y&p8Pj8-FgssDg37z2~m~n ziCL-mR(Tyac!S0({`vp=zT#%GYjL*HKc4Td8PJQarAF#(OaA-@YyK)~MTqXh90Xch z&vm^O)o8FMwiQ&$c&m8Lc-OmEBE*@2`i@_#ggS~bnUnKbgLNmz`i351C|^?Y@6)mv z1&`wD<@bJ#&xQWIS%_e#`{+}=Wgf`Zc~Y<9_%;i#9|SYbG}k9 zUow$z;m$(w z@|1MW(YDoQei0qA;ozO#K=Ig>q*5+M4M#5USme$5EwLf%l=^I6DUqSKHL8xKf=Tfs zs}v=3F;E|Lj|vbA%m2$Fj9RNy_40Llsn*29wJ0KL_?Eb*1{^17z4?AB`AR&$y+mW< zD(6jb`yblB_$1z7HJCrgn!~EH?8-TBUSFxq`*iNL5O{p8md}HKHbTeP0k~5yfT5&- z*Z-E&BBxx_8q>h5n*v)KZ8&14Q;*6@Og@xq-*DvaIaYh*{|46LRp|w8<%WqvSn{u{_cdf}zC}L2hKA0Y=qkpEKVGF+)9YfYb1KH zsU@Dj+7iG0kxHcF7dbVEX1w6>Y<*xoW2x3|D9`*^OV;+3hvT+-6kqn_lNeiN@#(BGOj~GkR0S?(l(F#Oilz&oU=@S@7mYYgM)dRYB-^OIXH^#N|8?C5|Y~_x4 z<~w-%EvZ%QQR)`Y+*87f@UH)i{^2;Gje*xJh^cLC(5Oyf^=c1E#F;5;1vATM7E7HI zmB4+)0q5uX2&>jAeE5>Ov_#|W%!^e%EUHCFW4O{gd-zU^$S-^P);obs)NDS6^KWA4 z99kz7KO1q&jOUJp$rR&*pSDE(e`O_5oxAklB`)1lYE&@|MZp(Tm&(7Wp{J_thvUrj z7@fdmTB6p1w0O4erdRwhoK2;n(J%=`EYYnuqTZ{Tl@xc3BXvWi^X)Hv+y$*sYng)i z%wKJd2=lDbgDOj11bZBgdPAk^Y<$4l5l%Hn68KxGmu|-pb{$H0kfAQRgOY>3FQ|H7 z!_m=PRQiY=sRb4#d4KmV<%%JJaC|bS^>*2B|db8hKIvN?%pL~n#iFjgp8PZa!`ct;#C8dj$aollr^`FUi zt85Jppd6((cDtV_oxVGD2eWAh%&MTukfOMNT>kt*0@v7g0M2xPY;VvQXDaylfM>p1=;d8Rb@dN zMo>yh-f^2At;cEk&v3+t5yBLn8(vm<*KXETr8ASZs_0kULpwOgx{4H<)QzbI&rEanIB7l%VaJI|ueX^YMv%w|~ zIiDsJ`hxzgvDe_Y4@X=w@%b!UF4Uc(-tWm@R|#&wyw*4;MmE0nlc@N*GH<#?W zwA5_x&|$&uN*DGsWKMWdCH7VpR3dlmiArrSszh|NWEZ!!M6(5rs4ZpBYxP8qN4Z~a z?mt@teLdEST$zQbH~vx3JhhhZ^?5r@B`$eiqY0PE@-B|4pP5%dX`Hl^EHjEf5F4Ml zP;^9N-$i6_kIdFDz!Sr=kbOpi8~Fs22;Z6!xhGIbzlX`U#=Q9bh$7Ayv=Dxakg3Y5 zhyJ{u;9Y-dBzmZ6T0g7=-U?K0NrJ-+ix&wDd=DrG5FSEelR{mGtS$l*JJs_xLY2j* z^6$qq2Y!2eYoKsDmS_GYlU#|f65Y4 z8^3_(;AS|_)zqqyM|&&Cv-(y9>nn4Lb46g1ZpBBtr76H+za2sSb2w&|cy}Kwa`fWc z^|BbDZ#XdGlRBgx6HmC@XEZpCsF%KB6%-&qbXxUJfUZ>69`Zhw2pSJzpy}qc11@+$ z)nw1_IFt4Gjqe)?pI-;{pQ`$n=-hXyM3uXLCD8uww#4uC;hp*8O7=Xn9PI>$p~Nvo`oiqZqNB0QIVWRXu;O~=W+h0S<1*@^RX1H+t8Xj(N+iVC6yxzYt=%gD>v5wd^54fZef(s>0)IiWmrZ+R<|g;H0m zxIC_{@K_{Yo+f~KQaoRmM|7CWWaa_oYM4lxUDymyeHUA=CrurgH z>8BCQ(kC-3lAO8+QWsk>zAXa-cd9^KRR zR<^w(J&v>Ihsa_r={Yr3xn+}d2Rc5~R++^CE_1+J@zyvC%OLfLNS?X(?uM$aSCAma zs4w`EcrG@6xg{#4B?VRc1#+W*uZO0}uJj7LUlO=tGOIGZe^bMUJ{V=L zR2@#*rNznR;WFKLCz&-_sX1z`AE24ZXqdyWl!9cMX5L+H_ib1TUYWYfQl9WwiEyzs z%)dN0l#Hnk13m`QwYOIPe>B|(lw@_aF7VT#I!^a=_Y4^%OHx6+BtddUi2{fH!F|ukJD23lqwNR^Ss!$F5m(a4-*Yw+gLRkxs1Jo!z(o190BieBalE%mVbfYDmKYYeBT0F)^_jdJvr!TVk$p zYbEMaOOl{>E46#$O{EuW$_fjNVm-e?xGC5C5oQiy!^csN*oVxW-Lf-0Ew7~7h#IQ% z!cVJ}6Qgr(DZudQ_y$Qr`-Q3>(zQ6bC2rZU?j$=%5-00Q_lk-$2izTLS*Dqo^M*$7 z@Wlq2*k5KmZ>YU^JhLG&+c_fM=BSeR^3MsGlB)(IvWCvNi}Q9ydVaz#AFMr1g`zdG zgConT0CTP$O-IBB8uX)cf|IIEUnud)z9yP z{l$>R1|=iHt-+=A7Rz8uX2p4DzMB9}=5bcLPVQ1{tfdhGAV^RVBa+D@kiW>P5NozJ zF+KiBq0la?$g}4(j&KW!ZzNWK7U=RcHm|1`P3=cxMIGHd!k(kzL z!JqkvHPFPAIUxJ&|BGSXAYyDCv}waL$&~Q|jq+ta7uY2p$hlz8XXNt}!XsiQ1ndxq zDmQVe81SoG*&5G`ReNM2z*c@+ji|X(?BP)pQCmTu{?O$z#*sJCZYI{%0IOt~Z{4R6 zCaW`hd6!1a2o|@zSjJ42>2eb?FTB5&Js1o2h1y*D0(hopwr(du;johAg9+4JmFHZ( ze z&^sUtAYWfb+w$e&7Mx)irZ4yFS-q7N z$rS81_ETfHyEYlV;C~2SC2qU}%sJ79r?VnRPvDHe!iu(fm77THbm)bd|qR-$XEgd0tS5I8{ACg&SEtG8=yV29HU zt&G`~e&=?XuZS`8{ZobXK)>{W&t;VBy&lU9ZuRsaS0`WVC{ho+YI97C{VeGWHxctK zVPW5PvSEuDVp_}H)5&0=Olj)p@ewR>BoH!rMxu6-GA&E&SaZaC_S#AN33re&&zn%S zJRv%a&Jt1Uu5vE~MZPWTJ0FOLWfn$IiC&C1%{3QSUzCHA#plap-nx4-+mB=eC4Jnh z@|MB$CMvqWdIW5uL0WP*MDDDgdY<<(6O=ErEvo0<^bZK4KIPbFK4nd;*nDS3yD_$E zdfp+>D7H`LV)y81cCLuy!KhVjRB9L;Sy<+YpKS{c{6zCwyc%zo=+1E zvx&WlEZvF-Ek;swP$j_`lczTVXLiC*5=)yD;7vU?98X-twv&goN2E(Ke`FI}y1R51 zBub-4)fxM&=6Yr@amX2(cSNlmu_KrGt@vaEK8SR`Rc(n}_{@xZp`CxjkRK`mJ|+eK zFSM4ZF9nI_;>*-vM1xI5Ur!Wkw(Jj|xJ6%d$Nx=Lt`SnhhiL2_32D=vO%IW?EQko1 zBG9QgnfxX-zy44&=vpk|v?Uh{k(>wmLQ7%y?r7D#Zm9IKba>ts+0G07*!6U5`K^)i zWAib!QM*=Mv4)TL#XOFmP7g9$AImIRhW`_8YLwi4V?6kcAIy)8Y;HA%+v-68%GKM7 z%N=s6K_(iQ*~dL~NA2TNjZUbQ!{wz|`hyX*8j|&_zt6hE^XZpmWIwXPy`hfMKM*6~ zzD>48630{I95nx{^R}kIa;Ply>W-AnEy14psHHern-Yl3;3`eHr29dU-C-x+=!ik9 zj6B1rJ52A-ZFsx{rmDfgo;4Djf4!(%!a8!Mr|AYf$ORqYERHnWLKtie1eA%iBmB_% z%Y27pK9~8_Z?qPxXnd%-5TFh!O(rA0?sfZ=hA^>}Mda;p0YIE4PU+LRb6*c%k3&vo%70u*@p;rBrnRilY zwnaBtd#E3r(ifsD59%=oGW3TY&b8BuRiwZ)lz==#W#q{7V}8w|YRpY|c{D(|ciM{M z6k(?q%P18M6rX7++WU$Pa=z_xxP^pPL~(PYL*Yw!h1?{p?sVwTyOZAE`?(9cE|FIrlPuC8Ddi+CB& z-{5SEm*zw+`8vHyojY^Oy_=(?vNN*=+Y}j^hcZmNCB<9j){MYo*)~3@Xxcb`kLMd$%tZ9-swNONVcTx1aJ)I-%HX16|$i_WX zfzx|Kw{Y|Rki~l==eX&*nwA|{hv|_Sk6?St!SYNUi}S*G?lxY20eA}m&=X-mp;)f1 zP}1GFCp7f-90+(p13Q4lIhj*0@s1QF@2{7K>rWX99s4IoVls>P_QsZCZg<$XC%Ox% z&_qYbBYd_i^ftA<@2sP_UMi{VIlugw_K-y-cZqGNY$tXF5j@)n1W$Ls^Hb$Hrrk|O zpY=8M2*uB-hc6zP2tur~-=fj-L31ufW3uY3EH)mi)=$lxD?okfoX zA8O@mTC_$&_3^Vqfj;5R{X4sj9%X*@dMizKM?rbACuqsH^~JmIrt_Eo9W7sCy$zwG zz%Uqlz0{&|W=rwvNcn(CC^-S=)~?W$`=5bOHP>r!Zav1meY+k*wG<-dw{F~agVA(w z&R{T+j%|(KI#9f-KZ4?=uwkG$OTadx_!GReJt9DTDDKg@1#bW^>ACQMMMA2xC9%2sON=I^vY>K0`!PzS|S6wdeH& zKyjdGpVV2%aaU{>ROPX4?o z8t#Fc+7{wJHN`|Dds>R=(_)tTrQLxc|EDL25NtJAX_#&UI_1LhxkkC+STNWR87-F= z@>R=Qc$Us!XwbofkbKZrfWOvnD?S!PwZ{ay-4WLT!aAOS3=2vj@mUm0Duvact||Lo zhX$1RM8MV_eP^HchN6+_-e;13f%Xd>1jlGCE}sf-_?6x`?Z@`Sbn=_07t668$o4c(3;{$D z9}Rnc;-ct6`eZN)(~l09Z$m0N3OAjGk0AES-ePUqCcU9Mu}^=o7!4>aO_qx86A^%s zNL!8>49#Ks0GzB?bjt2`6^b?A?TNwY*Ul_pLDp?m*As6I)zBy|#qI^=hEyr$1z+W~ zIf4IkUN-}U?63By-r{McHWQxzUz;rm?oI#irxs zO?0_iV#dUW&UvanK10K*KVs~k0_}Bt8w?~*^rp(e^P=+d=%-PleW(^vma8T@M}gs0 z{f~{S@s$%_HR|Xk*>QA(c=8@+Di$2#vURrvZFh&STc9AT;Akf#X=(n3m zJr){e@?wRax#)#(gYM{S+h9hqrgVUQV-~YMqcuQ_v;cKHCN*q19AZ%BY*yi)BG2Yx zfS@0}k!hjT*_V?&59kWiixS0+_`1Za!6w6jk{yyU&p(D^j?6RT@#MHmmI*STBFaFaoLQFwiX-Dp!K84@K+W9^A2C?7glLGLGJWN2ZLH zHjygJkq4n;DW_7}Hv)TMU`HPhmwY`8nIC7j7wWII=fj@;$){yQCvneCLh@ z{;-;fZ%xH6i{f`#wlB35n~z4+hfyp(IjyH?(vs6#yk!z%Z_rU-M^j3W_}CNW14h`; zd>$;_!L1esj&YhPx*{u#RzP$mR4<0;xQbvYPqOEIfq~pF)s?}ShbW#ODz(zjA1u#s zC5MADbjfh2>Auw!_VBUpViu%jdSr7?Q;0PIt)pBH#h!z4eb${HO<}S{ z@GlKRr|y@%(ecEu@4*Hdq-cyOEeOJ@L#2H7(%~Swn94mTJFn%8aEaBMg4_85PWV97 zn=YOi->}+@NX+*kKmmwWpj7KzR7yGbm>0Cqg>ysc`l@QF>88y%v(~~0UEf1D_*gk| zS4@?9oU$u&NIl98jYXvs_?NjcwdajR0W2)igGRxi5h&$F)1&X{m>E%x-FLBKZGb+DdF=M=`2i(;qB_duB#ryG2j&wMHnU_0lgwDu#g%2MT)P zV8Fa9&I=88Y^ck?8Y#a&n>vb{-W@*WrQYKA&+QGq+&4{Dlhd zXaL3F2*AtwCf~u11_~beo`F#5{Ik9o8itrQCI}>FV9BBK3r)r5b3;jQ+tCv3xlK8s zwK$Fl1{K84m@a+x^nj-6V=rggf~q=tn~n?$mLuhtO>m=Brr%^Fn(yu$i_Fx4-$waw zdej>J(jN*!yHdfOER87vh(R-6(>q*?@Z*wskw@%JL09udHRe*SMG#nFs5!E~M>9gc zyEhmY-}-wfL7hFDs`))Ka|C8|~Zsv+ArzSMeoB zv{s(nD495M$X|z|({N%zU|&4ZelRY5a8CHN;s$wfS8;)vYj6+BqSGQTd%df;yiu-> z;ec2;Tplk|PHokGx+U6drO7C%hLp7w-Muj%`Ci)QP^^KX6-Fm%g0ZMSQZ^Wgn|1yC zVCP=WE8)6~M)qx|me!u!UBH4SXB48N1AC%y7O5{ZHi!F*E=^T4qvX0oZ?X48`8KUfWQkPibixOki?!)v8>3*_wSQJO8F9yjd=JRlgmyZn7nUE&Ugrl6JpGfX37$DxMx0>07uCd$P`cqi=lF(Od0}Nj>IPk38 z`@o8pIPE`CPZTH5Pfdf5%FA4hxuLCnHdCoVuen%339mQeqJ3tD*4D&g#Zr?cKeB@f zwv(X^PKTV$Ic{D|w4Af3e7L2UToh!;UL_CBWz$0W>QXaMngVtCEL@^9sEqH3d(BZD zjv3sWe;Typr#>lr{2OtjLMmemOElcmW6TH~@0P`johTm;#5C<>6@JfNEG(;X zIx{s04ljtI>bFvLAb4wVo=9(cSi(SlHGIb}}NVe9kC ztIgFND@B+L*Y12rS6I$NXBOLwsFx_dX+qGnq9>ycW#9E`p$31m-__kYH$15cZW?e- zO_-=-S_BW@nO=NAc+gBVe2ktb^4`!Ezy8W-$kf1gZcvGvQ!0lYlivT^)kvCdo(cwC zD?C=rvqaDdwiqZLuE(gCJ!N570dn54`1J$lMs{-}oo&jfBQX_ami|-odZvZ$<|t44z4{Kc_yFX$-6MH%$WVkhd90};5s zIxS+Jc3p=PAqsak%}J`}jj|770Ck@q(as&1dmgW&_;pKBgK?o34ul%g2?G&t+}9gb z`-?_GTlJB_h^TAxVlu_=7es;JvV}oZ9#h$LKKn!E?8E*5=gru<#Njkr&T6Z9O_c3p z<^~FM-;)=94NM+e6m!e zDsYks-cUiDC3j%dA_vR0-GWbJe)`9AN;E+b zB|xbr3&WZijwW)`8SIS}xNlVKq#_8VjQ1;H5<& zmMgegPF_aYTTVR?JveZ1JSSyqB^+R3X=|>S7mBQ_EhyQ*UqNZiPr)8^AfbNXke`_iZ?2Ue_75by(bSG3< zZZbVU+67q+xqC2j0V{SRlyza`-J|0%lhhYJkk*vA?ibu_A#Nyupc5Dt(_O zjl|?M`1LK++3S4p9k(hI_ z4OJqZmm)d~*%$4K7nl?Yy@BLY2c=D9o(Mf3ai$ehv6>qv_J&0zTXYv87pi)CD|7?( z6_A1D`in-RoIW$6(?LP3b5bqELL$r&42^}Qw>xi9h*bP|xU4E%>a-%l(-UXr;{K>L z+|pAVN9~G!NG2?o5}_K3Sn=igf#cmir<}t6rR=KW08;bT9l<7273x%&Uc5k%IIFjS zyD9|Cr1q)f*z}o2xDw@73!)&^voLnqMR=k$^d28Vev?herv@U^ry(ioxIhmYO*vfL zM$E;KYaB7B{61u9s9f7*E~6(|HqyQjjA=m@y?L=PHO~(e7f!}7!QYT6Lr80s&?E%l zq4`k@{$xQ+tAB8~)U5qS2{nn-?jZin>kl18IMtMX?Q;GO6t2KAy94TfddU_p68Q!!w21aE?4fZEYk zd{L1aPRL2pv&tSbrdhRrmB38_QgJ z0xP?BG6cuyeD0~LXsr^d$Yi3{LAk7EnW2bS{)P76Qp{F*Mwyr% z(dkcnB5ySneMl8s6gL?WGUEn${O*NRzBfIzzjp7BTJRw~ zkrkc=-gMe6yWWA3$P2`lhthFf5GQS`T1XI-3>1nTr7vu#p6HRk1_rLY(p~&Qbwk&) zS;M0xba(Zkay>SGQ7~#Krm80jV%Ja=?JhoUDrWWtgZ;Sv*a+HRD~XmJJT-BBPx-sT zr}yVYF7y52ASC=ilnB>q4z27jcNf=U#F>1GiqjJS4shV}nw5A!Q#NQmBl8RfmEH7# z&1Almg!${{qR~_w)f>uV@2kM#1x+iys_Ib*{babGZ!>jt9Xc}$$|ahLKl3SR8BoQ9 z1pGd|lwZ+B>?>q9R!nU3LRSdge?kF3`_g#WrQ3$eCcHbfawl!1+<|^zJy3Lc!L*!G zv8-y2X{sbvx?%!?s+_v{c9d^TY|p6&-%}BTWDW-%u{Kd|lytjQn~S@X+mT>LJvoF7Q35`%q~5-8LK@xI-i11-KF??UAlPP_Y`$)Pd3y^VE7fi`~(s zT0*;W2bBf9CY2GlU}3qVoPv6o4>qEuU7kClIVEcx<~4ZP^SVPOQBwKpWQ&5QC_b$O7p6zSh_2`_V1vedZuVci_&G6DE<1Q?bJy5I`-qDpb>67 zT2|HEr3@{h{A|nA_hkyIw-xwyPgKXSxK-u$1(#{2pw0>NmqQb67nUED?1rW^J4(#ATPdi>*z< z?T&opn69u4jxl?vm&bkY8Ve72$VgNiK3Ehx{%Jj;W&VA>^VuFOrf+q}xl+kJK<;MB zMx*4C-fEQ3RHIko*wJzn>WERp-%+8Yy-eZUGqe|*Hy3Yp7Qe+z>xr|vjQcKjQe-z- z8$({ve6&>QbIVpommQ70TPYR!IO6r3JsFw)#A#tk7N;$b^rdHm5;4mJa{J9>_++sROZ&Y&SZstCfUPL%4=^sd*?d^Z-gl7q}a$iW0SJlD~_ z5bdWT{o7qpz8}^JB`@%uuwsk?dZD9XRgQ4Bv029Bc3&na5(7B2D)JqnMD)|H2*8%@ z4gohs{5`0<8#&~T*P=D`p|PlOTs0mRm7Wc`Pal#}>}e2OzO5%=qN+#+j`?CT4iZN6Yyh%H$EorDAyA;qreh+f9aW~?oi zncC!daKQeF2Z%jYDfW>~w+F@oy`Kh`u7oD-Li`7a7X5RL7^6`=TzVu<_>JQ+i)|xg zo08CY6daX$ygt6}NT}cG3g@yZ9gId0fHxC;8M&*5I~t7ZmhqSmvO_hR5Fy**`Fq=n z-%>U2D)@+3ItulLgIPIhnW<_!F z`ITyDkNkTqIAb$v3Oe96fwxOL2!e5$=AYq>1`)&i<3F zr~_^x-k2&k#lKQc!WF3$*EYq3Xti?R@NRZ!t|bhcEMI2LQr`yWZD;-6)yM?5sg>Jg zJKKRWFSDXp*HkZ@Y!C9`(=CzF2)yAQ6{9ZrP^ul95pb%BI5v8*Q={gO)!;I1SPiURSIhD683h+{S4-Tl z<4uv1@3#dA04MBCu_H0&%DnT$Q%KxzY)3N?!w1BNh(m?2@Ou#xxN06+Jq@8`g7ZYtN%(W%-l%kkt&pFP zIzhZvctvmz#0{ZB8-}stGg$MssIBN3_giY^OSvP)9P>f-UF{ zaT&3c);Ohg(X7+n7S*wh4RRGrPlZ6uVn8hsbxt2R)oC$N6hD(?)3TJj(?fL+RbsW* zXe-VtqZIC53u*eY1I4eOk5L1|PxWBQ;3*hfz8S74jfLo<4Z|dnnbm(QQCqoQ2q6d2 zI3|xa-7i%Tf1u`)I-eh<62-XDPFRSjhN8aIA*3ZG=JD%3cnlhhZx9!8(5VP?3!jpG zC2aUFrXq6C@pOhSp;5-dw?)IncUy{GTw5;%g#*pWC~MJH!~R3M`(lN*`xu59OWii^K?y0I`m(QRdA|ufDHB~+_ z84>|c*CUrPmE&Ul-4o?Lm}4Ly^2HPkpyjfA-_{xlzh8+`vVa-txJ*(bR;Nqm{9tZl`xv2H1ljd#=>aeG z>-tY&1v(;A(TLRoYg_Z%@`l=LD?lIn*_^xT-zZT2wFqf1KMklk2%)q+?icMTdxUtv z`!kPhf>SDHph+tipyQ;Dpw!juUN<6D*&JOw%X#)-Wvp z>#MNP^{&!JNDZaUD8s|ObrZuvH3($SG7rKU+v_)SwgoWu)Ts!=PITS?u+aNjYO2>m z`}Yh}X;Nas+5VTL2BRje6SQ`3R=Ta2U6#vduN>v}Z_BDuulV*_)YPWeLrVc}BXXEb zkr^6F-%NE@_Hz@bUFM~bEnsjCnKe+JPF{sA_q`Use(hw$EnZfHEXe$qdkPz8rrIK^ z*PHosRT9aXexmFRoHS%vj06?r>FV)P=O~~w74N`jvcuh)e=|Gul|*kbFQ7RBgRf*0 z5W2qSp$_*vPE>Y$|I7Y~niv^OFy(T6KssWS&Qpcyi>ckgn17RPG%W(AT8qq)P4-(~ zRVu=12X?W^`B@c>Cn2A76_`OtmkJBt4WlsfUPh1;A1GffLl$Eq&L<-v(?7qtc#yRSkF1CnaG$LTNN zH>;(L0Oy%86s-F7=TixPJk@lk+CsbOK)tAb7lDCcNnXb@W_l_pq-yj-6H(cjsi8@a zkAn)kp!>wTwMArm5lV3OZSAGkjP-EN>4X4k%rtSc+;MW~ljY@_WOdkyHUa+ZVOvBj z*k7M>k5o)jq~$PpyY3`09vxi~Cgly<8k2#<9+dU{FSi!fe{;+Ze%y^zUrrGjm35D&MB#DccO1do3=d0Z$=$+bW`$ZbqH~Aq+gJ;oBs>x zWvHF-j5;vlW{W@74Yhezf?e>20wu-~ccx212dhex?hoF-9<02pQ^)CvTyr*X73VD# zKRmy6o@PF&%u&iVRj!7{EM{i^-O76G_&rqhngYD~xlRf#75YJF<9HX|6Rl27B!xQP zFhm1HrpLj&p0p40g_S2lL&L+s-%g+|f~V11Or|>AWmG$GhDFrP(bvD8%}S-0-@0z~ zU~M_Ka`ra*|I%Kspmh{|NJkzG3aCZuDIF%sB8jO*6lOb7cBZDm`4(BU>E9^T3C4s> zo)*VmZkRf0@=$TnUt1z8;d-8#DtCnsr}yEGsee2nBghFd>pFzoTx;=TKU9e||CG*5 z_HO@}{YRFzca4(y8WbBt0BX@&dS>c}>@P{dgEIV1WSCm$e*H)*gM2;}iY0g9@B^20 zQK1rv#8R{CaQY#F8DlG6GA6z7GhS}A@zJuT9TZGnHj zLx*rGE|mCo3D6=|B-Lmv%6ceQ-b@l3LbaBj!>vQ(Hk&A$Xq`v*w=v>oz% z?pBFR8R=2rEiC!F4j@!v8K@{Lr(Fz|xs_0mr8exVMlFkZCWEc_o*1M4Avo$0kp@a zn23&mz37cNy4r%MXE=D?*VV>_)|Ur0!c!6_w?@_YdrA+u88lV+4x^=cF$l1_u>0a) zHcD~V3kJEu(_m9_&(?@Ku1MXgUpI$kkbn<^Vkjx$sq(q0oM~l*z6XhKU16+yIag7f z+ZtA7lS<%Pqp68^1w)~`1OYfTDBj|TC&H>MK>X%Gm@kjl6nrP^c7`?AHidodNtp4p z4AzZxRW4sv#K`+(v@9~Y9`xayfS6zuEy0a{E;VY9fv1GTP0=MM)^acQ%s6v5{mz-m zvff82xLB(U&M{G+cHgOf*?st7suP@p;VhhU>Zn#um9rJ`YoZb`e`CdPeB#nX}UJ##i@|vwD_082{&|ephwykJI*5rfnJAl&^kk8 z;)c+}$X`%GNad~*p(T@{(f6nl28`X0f>`Ofu{N#2Fuz!vA(EZqb?SaNnL8Mw4egl- zq~%LEb#_*9EmP*^jP(8ia+F8#CaqB*V;NYX))c(G%%q#tNI8JbnTT3{OR9EUllqMZ z(YKW3^4+qPN*sWFQrC{b*-xCAaEI9JQu{s;lNvhz8y_0clBdz_IJzaUn%NmO>1Lgi znmiW+#HPaYHYQ_4$a)je2D>TxmdHoZoI&%!R5&?2b=~Wt5d<)Jju=Lak>wBZ({B8J z-fj$JxA+jyXsV1#I{uug9E9Po@;j|tsT}eK%+Mv(_ovxMY)ns843=-bbt<-fNg-9A z9YUb&FZFDH+Zx@b@3zDlyBg^tkqR*zf5)eM*NjN(nfI%Q(kx%&2Kg)%2z&#SIp3C= zLdT?1=wcR`-`3zy*TM;LWwe~9!u%?3gk;~2C)yzMT8<-b4ZF0L3bn{2OI@&(z4?)x zin5wkH7aK)#A(T2bV4|Kzt~bdsk)cDCJ%BdTG7SxcH1>;?p|MzXZ4v>1QJVFXAgWS z&!twFPwsg9N9~UVYY7;nkGz$WspL&5hz1epelm3Yz9SQtRnb7RWzB)@G}ClVmDIJ; z^STk&C6IE3TWlXQ`(}}zQ#nnIJ0@4O^RTu}w^cI*T_&|-9MrUm`|1MD)3Rjhq*?#V zczm8BHEB`c9?#poK)t)A_?tg7i#$(jypwDKgh?VhiVa}Kq5*>CE zf+Z*u|d`(5fat$E^Lc$c*N9K)eX+k^EYDf-{}*j=eaahH(yC@)fK_B z_6uDqrw(mJtIbR_RhXPr^pSuH>lo0@3ely>(=sd`Nc~|2Wp_%>QZkz@fjn=UKJ=;7 zs96<}I|D8D5T0`rOe}SPPsj|emdMgG3AFMWr#dNP#dtShk(0twAH*E$Sdj;CCcGcpOynRH%sx@4YlDX!hZ$(Y|@sGG~yV0@lV)88gzMDe8W|SQ|sl) zIZd98J$aYr06Uhdn_4_}WI0m_@EqkE(RKQu4^y+NFO|Ngngc@F7)?c&kip7^K1>F# zwOnd_Z;{`yy{#8NAV~A4_&=(jsYck5dM`_%V9`YvHZVSuF=ffk>V$J8fgUQ2Lk*&)_Q8Wzm}R^ zKT81WVy%&>y6KL{CFgrzHWu2fO@lry27-=PiqpKOZWOj%i)hBT_d(K>yz`{pkSb?u ziNR9&YMhb8qez>!aijFi;&~_Gqg04xQHMlF=qS)vWYqL!yJS}NFbkG8Z8F7(wuJPu z5?T@jNIAVo?c)kjS?1kHP`?7Fv_|lAR%_JgVVk?B-(NvRPg#ohq+6I#?gpZivYYS3cvDZzhKPk_Ow!hz~DqM9jOj(Qu!&N=>Lho(IVJ|40iSmUTB1Gz=l3s{OE1O^@ke*$+=M+c zX1HuJG^-^?*4at^lh~sx*69IX zwZHyP7~oXQPW^hLJc6}>>NvWM&`CQh75P|z3nY4IAs!=V0R1_YHb3Cv;6|2ylFxSIAVk_Zgx#l1sn%LG5VGOG$pMMt(I3j8; zMvR0&D=XCZml~4IR*%q!I#g45e?SL|w1MVLCmg*{r(r7EfkFOgLxFhCn1-EI_s(Ac0gxPo#v=GH)+PnbsJ@c zveLa6XdTFt^_}-8BR(HWb@yebV&c;2(kSShL8$mhsi62W>Ye>f<>VPgmgMj5%9=Ie zc8j|lfQ@qYjq4-g`m`zRd6C)BE-k^+X35|7zCuAKHZxH7%kQdt{@wjbt+tt73M-Pd zdsA^S8*$pQl zbXl&{C*K%0&X4E)Iu0!pww84LKPpf2Uzy51l{3+NwvuxYSjDUIj6* zNL#UPLIK~lv)T&Wj?{iUpe3~0tp6|sPpT!`bzIp)wP`liTnv6N6{OiZ55}SF#uqmi z`#Cu|zhD`Raj8lY*f^APvL5F;;8FPO5vdXPo(PJ4P@WS-Kw~mHI0x#YJ(H;cIwc_B zmWeL*v~C!i^a(Tad5m_k48gdcT zdVF@GQro08wkDqW_4M7%Ig{n3tPmZa#GX6S=zR;Qm|DKGY@AvW$A^@QTZPaIRhx=+ zg9rXJH5mVz@hX1-B;3yFV*J_0#8G$puZjyfBe)+zsr^+_aW5KSBAnIdtzjdS^Vy0s zQ%?4G>I+SALsus(qi`Tr5yk-UquC`aiMRF*kQSE8pX}1=`PPiaCi0h@#xjz+(`>RW z){ydmij3Qru(MCi%y|>5=bVnC8*zOu!*#?1~sv==~Eq8lgt}z)?hZ?EoXL+xb zQ6dUvt)JgtQ;%C#J^V{wcq1!>0%}_|5JAgRf%(crDn3e}^7P#6dxIXKNR&h{pOB&-aN4E>~Hc=jiRFEM$8QQQ!KH>7(_om|9 zwEu4U`ic3!kV=d{;~EsLQkQi{wIR`I`u*NUOjY|1xd6R;z;+oB`6<_&6qRg|l@RCT zulxR^@lsMB^{ssC-{IQ~cLA^R2}I&hy{%*N;;S%KdfD zFEQVcb=3fy(J3qBLfxmtDYBwqImze8Bs-^Zf_=sm5?YIC74p*WSCi#+ZE|2v^y}#X z7i7G#C?j(|$cjOnj?RH!yblZZqx|e)BHorbGuz`6dSy_6-%a`xM9AjepQlu8{54*H z&9PHjGQ|%$TVzN4ldK1cvJ{&Rbi31Q{}jI+!(lRLz`WR(q(~`h#1Z^&T84|$s<5$k ztq#OXYdo40ZxIXKH8sZ%Gg@1jRY{~`$z&!RK%h*qz9kqbe*F>Lj8vmNiGfWzif7ob z5xaJm=2PQB8s(wSw1+FE-(Q)J z$e$JfyCdw|@tOVcgM9S!O@SELZpH^Ld2;HqdaCv1lKMCx;Z8`NbGFy=uADRM>@`!y z2RY&IOEY~O)lD9xfhE`cb6je&txoV%a*(Frax~{T;Z4}Ip^VeSQM=_G`9`Yo{9H{o z?co}Y$bY|{k%OY=Ecc~3VsbHqw|*mMxVR&1kxT7Ux5&_8-IGCXxp6XtzgT0g+4c9k zb2Su*&s&1~bUKttjxSz??Q>XMr1C`iASd?e$uM-glJv<)>1O8yHVjzn>~|E{oF+e`8^+`8W$E4~23gTW0=vB%hn}3faRq z#Tzo{B->voXUzNc77-`XF{zx)7dTO}u!nL(d0zBFx5hdD$QjPrLr$6#K8R!EY-D8q z*R3^8YJZZ0ZK@oN4BYUN7c}~MuT8S^HMDUENmRa06HK*>fY13I1;&_>l{KA{M z)GTBwC;uU*x{M~O)}30o7Ws1L1nQVKbTwbq5-awOxf^_?LNF_#T_~T~8mcGye?MwO z59aO45Jk*hXUWCJ+d>{%g68o_&S{#hw^{_AA@0SwaLV+^0q%J0J{`soW^P=8D`Pqe?Uv#so#e#A|Abuy~$$7C~X zCF3-Y?W|sr(@3Y02Fj^4kHRoBGIvlc_YN z&stXWIk{Vos#z|*n6vtXO73Lo9<7%7$le0T%=#})%l3_|dsan9nS}|!MAz%4{PNx~ z8Q)2c75Yzlejojl-0?v;iB_7N4Dx+70>K!A zpNRcpn)}j9*cXxcg?23yy|s88b|zbfKge?~g7VpaON8xm<|7~En)}l^heO(EYp8JS<6p6DVudn1rB-rtU}sCN7~3+Tiq4C~2v%N65IVBn8oiRWptWh}X~;W1Wf zL{077Ms)5RqAo($a=AV=J8&!KCVL}2m_~(%yl?bE+Eh8M-J64oq4yv`Bih_K*5Wg zRX)O-zMp>N%G8rzJZJB^>E61|N@s28#Q&cS>Bv}yZ>3(95z7V1&^uQmhVq@6zT3A$ zVg`DaNTsKxxt{ijA8@>^WO`z$HpAcB6}y6Jg}A{_@;ZTe2YgvT2$!)X~x^ z&Q4D0`${%ApJ}+Z6)>bdcSm;1HD}vB;bA%U>kY4*8O=KC9r@WQxF~y=^dA3)O;~;^ zq9$3)0%$`{{1F@bl>hT!(>#H4c|KX`9WhuEE_0Cj^T}Dio5Xl3uz610K@khfW=Hl* z6m$pmwVbrLdak)F!&znMZcG2`ewC>S|5U1!t8;CRO)4Q%Dq-yEX?ln0virCf&{_tGF>>A!GYCVzAI@ayRLGXXSH7xCnd9{M~7M zS`(06&P429NRH}fPH}}ZjHn_Z!tvn3dkdaY)lS-fGq{E~%D46ae8+IU{ysWEG7p+B zZ%UPNj>%q6C99uBeaCC&DdoS`7VQ8SSwhV$IDtr^f-6WYzeDOIS!&O-Eduq-+TMTw zmxCHBxWzh=Sn$$i@cIZy3hS-br5RG2$Q$bOxe z*^u0s)A=9L6Lz*KeCRcdPnP)Y^4~MQy{QrXBioP&b~4cnX%nuYlra-)EaoPr8b;xp>SbDeVsJMW{Y_1lg3E!NEY z!XKQLHcOOsC4YZ^bBiLbHDmgIZKrZh#1+Jp^4G15o8nWpV^cV!7RW?0c?WOp zw7@^dL!cIw9{ zsfjtT|*Ey+SPvHUk zkWUWFYRY%_yQ~U(A{e+aLqyi;z0AA6mACu3oI$Wlp3`bvN?C^82&U(mOtUf7iLuXVD6gx z3x{+*T{^$J3hGhX1SkB?{D!ZPLr;w5nOt)#`Frw`ZZ1ANhBkH_rc=(uv@d5{^2BGJ z%G<#%Ioo2qALSjnC{g0;5?z^{J3d3!28+(jH|!f~rEkmA@@!h9NAtA2o|=I_MCnWf z)u~utk6UJ8KS=n+qR9b0&$rVujOVP1j#RB*gv@N;6W6+%po*T;v@I{C-*;NpWgqf} z9+@|56yIA`o|rRSi|=q6|Ctupk)EDw{?(j>uZjUr+jjzt$L% zsP%VKW!iu9xxQH*>db676^8l*(6_`nXPSr>J}P4Zsgt>Cb7YQe&O>rfP*@_>Yp`88 z-%uuXJp-OG-Wy5GSf@E+@>g=*+*77<$NcrS^g-U#IbVe!x!uoLi%(>W&QqjL%TQA( z)+3fvMQ-JHl7TaoY2XsM=55Jcd1f;3-jS?+jypm;cvJX-#}lQIx4xUA^pA*+l!|yj ztzIJD#KgqLR*z*~F5Bh&sahwBP$OV@Hpv>#>sgwy(eG56#pf*VEEbx<=?=LIz7we*3K{@or z1EVs!R;%*XclpAM8NAms+NEs}NmyHn*pmhG*v^Wn?{w@}we&hUb?Ezcyr9=dikTO zO8)NmK?JHi8CQRfNNL_O*82ux*BR#VCLd6O6^G>QmO*>kHo`usZtc(06B*kkv7Jo` z0YN^^$d<@}pXXIUib=i6mApO0;(mW~}vfvPFp%I5Q77qDF99qShZ}0B~8x z8}c|e@o56riMM;}Z8DjH7`O#kud7?+v}`9$#Uz?D0f`WgWNS~RUFEx333tc-azl@b zxF}jA+igx(1D8o(;CJAALPTP|taBQ?WwwD#c^}NNfCH=Il<( z*;%J>s9wxnv)4|tj7F?=MgDqA{_gU*<9id}O@He9@6V`XUY^pIGrs+1es*qVp#Rd7 zo=M!c`Q(26qTKOK(zbMJP86$nR55^BXJ(*pCfehfVZ!@mLh&tlb>8lSvX*yz;=s4# zI=Wd25S^1T&v&zqb}VrK=z7LWUdrl0?D=J}iraVh|M%TL_gkPU7-o7RmESv*WAe1G zlw$pM3kso0@)-j2}H4)M-g z)|#x{FJ^S@!LH7n!U{C$2T@ojR+e^>7nqxOWMi^>o=e|`5NmakN7i|TF&RXIh!2C{GNXVfh4&ZJ}b_24qqZ7lvFNJiZ!?C-jv;t(fD1b z^nduH1kF78&WSIrk58M=?6z7Z&+`n(&K3hSQqO$25p}@bGpicW2&{r8$aFr5Hb}=3 zk#pL4x-T_Fl)IxZ%-tD+-)19>*w`s$8JzIzES-hTHJ3*?fzRYl`ETa#=R!xmLz*v6 z&YL2#oz17@8nU#0{cgaZI6v14`fZ)B#S=^4?N6bX)xHgDi|N)frZYM#fSZA`*^|s4 z`A}Z$dM%l`*Vavh>9h9QzW;VAFp6z8kb)+rq9Qhl1}48ws@pF4wD1ah^r7~^Qg|0U z0bAgft?t+e;B${YkFuWmzTaXzoyl3!z}#QiA3UlJ`kl|eeTMyCLHNlhXLD2s*d@7@ z$cMS}@f&e?bY5ti%DRTXabNu69g3le7Wy`bfpjqY5=HgA=sn_K7D2`S`3lBVs#6j}1yw`PU z4Xl{V@i%#|Z)0FZm*#o zAAm6YJ@X*XFfZ49DZxOlxlh5L#RhJccibgcn|T+lU6MEn%yXSQ?atfVd46m+Z*`nh zLimyUbyl8L_>41rUm(FzakI-RhHuK0u{+$RJf+X%X?a4xsi!aLtX9nN_&8(oJ^iIv zC4YT(lCM@v+`!G^#}Ce5x8S_|ib*2!%+}#ag3cSHkG|sSnGFtT8Ot|P&L#VTdNU@ z=<;)Oz0L!9g)QR+MqTKgH2p_h+7W!|vmHEr;+%V8xkuW4)iEJ~4tEj4M0pt%noL6P zXeyS}ewg_AvRO6QrxHxs4O=47J|8npFkcG@^pMHu&3~A}i4%)`?dB$`{08Q*s&Qvv zs;{&ZfAGI1t~f1`qBN|hrZW>6X6+^R1g$i2j8o~lxN()jo9b;5 zwCGk=4qHjRLn#)^k!vun3%R#m-Xhp0z6v)pCC902E1au|_Sl6_X?$UgVg+lZF_#4C zq8gGfAB~5=9WCWMb4czv8gP8Gz^M{CV-!|@1siEA?6QRgS*Te9Tf~Yx3O~l9BwEXP zI!*O(aL=|xne0O%a>`a?u-GQU1mk8 z=DXYYNQyhICzMGK>{uvOn%N|oG8gP1Cn|aGcUHpGEU&*?@vb`z;H-|KNpp~CKB*S6 zNp`5QX;+&Q1adToMd~yebBmr7tG5&{wHIHr0__og(5I zr5MpdwMwy>i$+nkkmNJB#T)cIBUc8Ov(RXs`t~9*-E?b{vtE?``-93I=XO zM?l2;b`*~Qh4gxZO7dhLX^4Q-kA;EUPDHCC`_Q2F%qgQ8(B4D@Q zrGCv0uukH)vAEZ-jRm5(bMmhaNWkahrs7&Btu@rtztUbvx6d@c%rs2lG#w;77CY65 z+WnGQNt~!f2Z}BO;OEyZII=-oz}L>XP*bnL2H7pBNOV^^onv*GrA=$+FKSWcxAmJO zCue&MXCdoUxq+@gMbC~f>pvt83@V30OX3`lY!BSu^`lF`N9(=f=^Ru6fXXjYqOM^o z?kz!Ady`G$R$7X+q;+X?M#duxerG(K`_dD^MAg~hdp4up!Re4GkLJDvI$khdL}@ZRNog{)k-Fj;eXtUc=8G5(XhoHx$$!(y;ZWK_9pZ_cnY(l< zA-q`&=I}nL3*`K4&s$|pdDFJQ({7^POzEOC=*>)r?Y6M1()w2Mx8T*%t>2X96^}-b z0wCEM1i5>lB}PHD$Ge}^5x;v|d(1{*_PyynwemdLV)anjYNi@3hN%VYxkWAXMt%51v&`ha*B`?UnQHq5# zXmn7^^t5B4`uNX!XrfwY<*;iiK{XldjH19qrLe*;8hX+w@o-)teJ~zgV5@3aiH>Sa zAmRd^S9L{_k)5>%*mXi@6!GVE6=3skNuyed z^HLYiDlOI?xj#KRH;$6h9cDwgEWCi3p_Y<;O(~TdKDOcm&KNv5UF?4dWv?-6LsLn2m zAf5*|Qm*8taJzcG_`3vt|2Pp|cteI=F}EHnj(_5L5zZ&#)XAV7xsTc-(%#zSU$HB- zE^%6GL;|`6w}64v!?LYA9*Q)*28?QqqT~#$AYVp-kl{s$YA?2-%+MBf%%j?3-(@90 zsxRX=3J*+E)!JDLb)&!6q8sbvWHn#YR%|6H$eC-0wuiFWIyndJ>eQ*Tvu6T8C`{U& zT4-%8Q!Td!Ry)JZF>cLxdX_)cO|BJXboQm%F=NrL|T$2$SFZ&Qn#-j(B zw(X^m!|Z=KzY=|IU+XBojA_sjDheOB74N37@UXH@Eq=XjHlmw2T5|UfP6kPg0r_=v zu{^Cpb3O8|?P@+|YL0E43`1wTq53Q#Vo#)0;{gt@JYJraSkNZwr>!A^L9ddvKdU=b zn`qnaZAU7_nKVM{PI&T`b3; z0ozn;bJf3ZI zN7Fwf1Wg629&MqE)5(Oc*MdXU;a&^XjAJ&HhBBwpQb!@Z5+%OL*RxSl>_#~ownlML z@>MPWsT7!MnKLJ%&^#@bUN&nley!@!7Nn7@M9UN>T8bN#ymeOcznI&|_Tw?vl48j( zp+;$EPRP-97piy2HQGb%b7n^SV{HhTlvOVOsBfPS{~F?yh6Lu zl;l+HxG~j)WYBYzr#oUY$Rol?69kn1v=~&QIQ3jDK&nH>0xg`!Im$0okeiCNTSJe1 z&(7i-Jen!PDroJ+x;~iPV{Y^=tbp;Tfu24VB1L~3k9gy+sr~pfDuNmcD#1jhrpv#t z+gkjlQGTeewo(3OA_NdbS<8|r87n^vt*Qr~_8vD#^#mBj`^$Xk+I0ql?$M6G|F;I; z$@H_6-Au7-+|98_#<#0Rv_r29xP(K?K2uU+32*F(*rUHK^i{5`6e}l9TGaQGYP6@V zUM-*Ce|;7WDY<9f{S+rTN6?z$D(dj-Gr-rGx&I`QuG5pJeQ`BZJ}B8*p9{y!dqsvw zgCZZXwCp>*6>sw6RM!#t=nUez9@#ndUT`Ld{#zMQ_@F>2v+fK| zuPhQa_(Jk&U7XFJ^d^7+g{q-iY&Phbh#8YHw+OcOE<$5-F&8RWi5z76_Ncj=C3$c8>1uINt?WFy__M?az~O!_aT&h|dsYchL$?GmEM(78;Fmx#U<)Hz? zIL4Le7u%F&P(FeV@fuRg01t*mkkvxDj)t~0I7Z)kuwiawS z%!F0>%tYYHPgSFMG?$vLwgSU1G%!N5lL&OH9d8d(@q6uYS{`VRF4SApj@@)A|Z)6yEUA>*>(oG9wN ziMzzH+}SY-9J+2b1G*RBrtwg78puriY3PlsT?w}Nj%^X`T;Ca1ky^F4rZ|0##{9`B zk@VMtDe+bDt@`756kFe_$2@coc?nst_K0=Q?I@N*)$0l_mWcNuzAOtp_vE|(XEXrt zeMZ8MpOcd=F$8|a1-AsM2GbJ-ZPpbd#!UDAc}{BBgAV^>Y2^<}?c$EH&|iXn%00&n zwO8oZAE%YIy?BgH4!i>Q06t^a@TgPA=|@T}<54s@eIzoPvC*iWG$z9zJx5?o9-6u| zkjdlV=Us)<$hPlezbrTbfqI;@Pxj$nh~^rqI}sF>iu%h{eZgd(JDeA zXKxuLmX0^2mT5-PSl~<7j7HnSPSyYGpQ3&c{@Uc*BP%?*BhrmcOc|3MND9RHydFKJ(dhiz za4hO#+e`#2^@FS_^d#q5MstKrr#qB~pJknr&T%z+$nkl1x{>M>qY-1TSdF{IsxJHoq*HB8sn)ByabZ;#j56vs+TrG>Rk2V)?R*Ef}i!Wuo@pMNNp8WT)DwUDV z>fK^X_c|x%@?!hA(bXtAET~6=Ft9>gG|PNph;C;Tl4Rfa!B}*$WkGH2v`Bw8Z)iNa zL2np~0>QZxQL28Nt|o!X*5XDDiMiujcNE^}6ZAcHEuS_suH)ls1}dlwPzLel$?EH>;sC`a@Hj6aIw&%ecRY7uOlI&+|GW zd|kFRc3hW=kO@}*VWT{8Jfd8P!L7K~Frubnd!UDscWa1I{Jpa{OOBaSO{wC)l{F5o zgbxzM-8vq1z{OZIS;<(a)4VkiG0!KJptgRwEp%ubA|au6-dp>|uxqDu8yBeP|!jgUM#r>VG4 z8%Re`g6DJ<-s`LF!4>h%vOlrM0uyZ@fl%m?=#^&Q_BYvt?uwX@5w?1jVEyge89gkm z9Wgid51Co`l5M3G)u2kQl-77|H8>tttjdCcUzC<)F>ah(MQO65u(M`6-jv>iO;|1& zHLs2ZMESvJG(>;TL~JTyjE15fZbqVTtHW0_hy89xw50L=R-2dKPD?_q{0{rzcYS9y zeBX{4Ap=iN$O!W*P`=J!=N?FhAB>qN?Ptjmg?oP_T7aB8X`>FZ}W`ym|@Cko+)WEn}fWVXd&ZFp1o3!!dyR%0yr=o2CznFkg`7LEMzW6 zw?#I(PU>C1n`Fx!olkh?XiS#uu7>#A5||!HDcOiY@p4JAaaSNGhqp&5jT&SR7zf*& zz=%POBFyp7f9|Ad@5E+iahoJD{pKn}SJFDE^<2RhS7Ps2r+w5uY9XO)Dz{7V858TY zblH8y(=;G^Fci5{pF$UTLz#PL#9EJFNtuVGv|(0YJ^b&hZVC^ZTKfFUKV;vPVt4W# zswB7;CS+0C%n0O9sgJjFDu&%_LSJI_@3UiLp|D-j(Smzp%kVx_M&;w}5q12fGkn?B zl{jwzhuPZGq|y+c@n|I;91B&YAB=}g1hcRq-yw2Pcx{W$-z_==AKR@xrn$vXT7=$- zm`EZ{>lML{#U5=;C6zr~e&Of{jUJL9r{u_GuP+jWFa%d(6WUz}4#F0BTi7J9rjA)s3M57_cV z)SKY^8)&s_kM?aA=^e3sC5pB)=u3$3g_?5tYLE^3$D_w&bS#*q04P3dPl|;~c$MG` zn6hyv@2tv`@wra{V}B@Gql7R28V|;u)!RobT?=UX&kQ@*p%R6j>)Hd8Ji8-e-(+j)dr-@ZiG1a!*ZdI6!J=82N z(&=oDkDEm~962BG-dtF7f#v&o%hqZOjZ>(go!VW|)~Oqhb8eoCnuXhaP7y_%XVqb| z+wLIf7Pfy^)Zb-ff5_^?ZepG!ap_ZA6Cxp_QTVQgUi9X2c^DUdte{SP%3QX#;(7kX zy>?G4(RC&M6kiS2L)G@Vv6!tXo7+O{T?@67P2E0p3mLS2mX5$J`6_YRCLNItIY=uh zd1ur?9euw%pA8skHN=hSB0jH)BQb}+(h!Cvnhzo^g*X8@D?twsdc8eW{!yuc0rOwVkinJpuoasL^ODN)v|KXlzsobu<59U@p%(Lo&h=IN zexeOjM%fnD*!K)37J3^6FsC{(+o5VGBK~qbxH0dl!y?rr+9Tz8Q02uL%iGP~LeXzj z05bZ@33m?BtqrcV3**soFWPy`+tp%@DswcXcFIN!X3%eIYody8xJ5wT63=SrAa9IT{UVNzV@@v?bJpY6`9TNZi`A0fS)PWZV}^5%6jUiz5_@z+GuMG_fqScce*;Uw z)}&24g`h+=(8zUb(WU+c98o(?B3KN-E(r^Bj*JYHb^5S1c#<5JyQYFHSDjytDDi71 z{)hvsVeW1<=1k&mrV_tj36}nn;x08ilNVsWEit|Q^OI3@XoFs2hSVa8Xs?$y^RrY_ z)Rk;BpVAf{e8r6F&q>b1gmEnyas3jKJP}dHvE$L#&^sQlbDCKdrvK!)CHfU#;dD|V z+(ejO>Tp;VN$FQ**lZ%;ck`8;+r4tf`zFIK2@hD&HM4H`KOKRYU)d4N|FPD%0{>Lz zNX@ZV^mUZOaiRcVn`tCqk0j;@8hiPb3396ETv8p(wo;#7pyr4t1h3G9NOYQVo&*e^y&2 zykq76SQf%OuYaSwxK`TD?Q4PGE}qE$LvpmFDT>fmOMTaRTV#}%LSxf1v5x6~o{ zTxwU;@XS-=eNyH7?t0AjQmW&Hcg{-aovoqO2A`QGdTa~k(qm~4*T9(a{7`OZMkNje zQ47}J-e%He)WP>jVCTtnD)H7ww#Ac=w+1ieIFdHO^U4iLM#MFbeCu45HH!?<5eQV(p&GzMGoH00eq8vF}fb=alcd&mrrf;gR-a6*}gp$ zS@C%;5fN2Fqij7^RaGcqqOYZr&k_@{1#8pucQ$AvG(~{Hwwy&R*;Lb1Y5SlxBE6?m z_v1UtnmDeFax>VJ>X>M!9$u_1`=%}XhWad$O-il$Z9Mt5s5JeqHKy3`FUzIQjJ4Wd zeM+!eFE19InhGa(6SQl3WmKDr6>dZ1y%iNQ+R@ZBLQ?|`MJSDX&b{aV{JnZ~A3e^aZs+&= zeZQaa{><;^`-0vQSvZam3L~Tb%<-XDZR;|zPfnkFsXB%DNQ2s|Ego3yUXJL)h$twDzD?3 z=|g_$@QL9>3GZ<=>4NG?B;?QCibb}+Gd5&A04C*Hp1EnR-JvXgU4qY;v&w2a`)r5} zs^Ykl>1a4MoV>nfbcn??m7Pn%g`!Gw;{)CH9v`}|5AUvxottR%Nb|3BDJ%yox`AcW zv7t*)pUG?CdB|{?#AdeJ+BR=S=pp9hGzrk&?@?0Z!=N(>92=%^Y*$4|Fn&4@b7B%k zsq#Fff+_Za-loArk}8SXE#A#{8Xf%k$R2L4TTxD<@A6odYex5$ z1!$kp;~gsgr26l-(^D4VmQHQ2$`m%kA075HOW&yzcLB&MG>!1|>OP<8yz>q!Xrj)s zDKznzZmHO&i!jiQoajk7j$fU8nndxVLGOng5%Y)se^bCE`D0Udl3N}fd@Y8Re;#wFBojM}~RwRCt=R_L$y$8ys0{VvrVR zQoi0%mmkt)2?}*&?U5R}$A-CfDs=wr!l3wl;3-z>w6)CVy5?PUJJp89)k5ax5pB__ z#;5o^Ard?hmf|QD$lTsGF{J&;>UH5TWP7%041YDwi{w{8yS#|y@7`kxjU%i%$OuO_cVd(f@X!?N`MJl^VxX9Z?KZ4!yRMhTL*{#!yTGA z;6MM??jXZiLU>J&b#B-@!$)BLp6%W%9EQ&u@zXM|zi!&(9y11yg^c~4{q3BB^x9%@ zwG=L1qr-vCW0y>}{+kQX-YQfSX*RcTAs1Ww3a;tzEfH}tS2LMhiJZF?QLjkzVHPmI zx^u@%ix!m(noDyv9q%+NofQfGT=<`SdUm}~^lhWVapvEN5opTLOgdbZ>+52`ZB*)2 z167lod;C+*gOiNLDCz&vn_-*H znUXiT+*y{mbFB1QJo3|K8q8@^V&^3MM0XESC|8QZmgeY=I%Lg?)$R_{&~0n?2l4!y zN5uGUjt)(bP*XckH+56mwyjUw^6*j7nIyk6Z|BzPmEUdVo9kmaLiaZ9@Dfs2*X{?KHgDr|M?WBDheLlsr4#%?r17~=lB8N=D?8@l6fSAAl> z>r@U;k2uMR>Arx9%?RB`S1r%5iL$3Q)?gO}Dey8r)EeebVz220G*BYv-_q#L19X!J z^eavm-F&E1HrsbVz_a3OnoJIB*WYaB1xnhL0i5R3UlbNoIv zF`TqQfgSh;dA8K*ZL~(KE&Q*4>HB8+rX%y#+rJhh>|ad5ou23LIU~Z#0d6660PEs4 zR9s)CqDi(ah%c@!z!W39r%5iixxxEqH$O6Cs6t8~vEUP(h=D5il1URvJ0m|&7sR}S zcf^@z5t>f3!^Chv zox_$DMu$TIzmVV(v35p`hC+tls2v+4Ffk-2Wx5WkSN!AhJR{DpOc6I7$*h>B!%;cR$hg0?+=@jtpf)PG>(?b4bF1oJlZn%((`)LDy&w)stI3}x{g!lIKZ>fOG zODH!qhY%e^ujdomkYUkI?_Nys^o&g%eCdCuUvBbZ#h~IPVx>;^TOr+iN&G0k5zW7t z-JeK9`R$D0a?RxVN9T688y(`WV}+&k-T$grXD2a&6D7?9pvYPeNlUxo`HPYso8>+ou0Plj%yuKC(K4#Sk-+-haQ-%c-s7&Cc5)F zR!T#UBmYg7JD>@Q3kZ&6R}^bxlVlYPO=>bKuiAHoS8SGl?3yPmY7qIdfDiH<_`_x+ zh+jI+`J*n9yP>w0g`>Hm10DZHV=hjYhe^))8@@ib+GQMHUYSDD7+O9!8D{y6A z2d|Q)Sax(coAAEwL%F_*mvCv@&PxA~m);q%9~FN|-NW|BHKmK)#O$(ac^zjrRp9g_ z4M%QHS|;ivqTO@5-Hg;Z3X}_aF*$GE*PNm8qshqzBSV(v^(n%}npm1&aSi!tk|Eo@ zVk+>_ra-LPL_W0TWBw?8p&|-h zoE;Yq`#R6$bJ*I%X%+#%jxTvTAv__Dhy=2J6{>k|*@3IYP5HlV^lo zqnl1;;=6_l)iEA*KVK`)shKBU`}+UPnm~4St0K@xn`|(zy0g|MSSlrz4TTI=eyMNi zEM?}k%D{?}kUXmgald(43wQt8Rx<}3x1GCboJM8VP$ezYii z=k0abZppNln+&kH>&x}EHp6zq(ZT1@XUOU;oV3jDSTRpY##^M8Z+}BK$b4hwfNWTB zs~Bfw%0c>J+{TqfyF!0*!ReFChiGWV;Aj4HV(2lLGoMYwyp9-wMQR&ts9L;@~w?GDF zYs$?IS*4FORp6AoYY6;^s?F$tV=pz!eY@^y0*1J@nF5E(e$!p>-5%vmFOk`+f%e7j zf;n|$3c0P2);qxr4xdhFoo2$;Q!yO$pYfEcK3i7k!?n6ybX->-z zn&yY*H#N+Jf&P0FfZ2lSO{ZB@y|P`C^ADsW@026T09NPf0& zO3e2ojqyVJK-*;Ee+KDwE0WHN%f$CiqXn1Ir>gThtv|Pth+FCrWL87@d{;&13 zm`anIG(;$cmtD>SH}w!Z`8`f@A%L+QN>Kfjun z2+z-`PBE>yb}MywA9+8)_LXlxB}`kdndSyEh|l)h8vTVo=u zd_64I&*R34&c5Yhc-o0V035s89 z^v&*)E#(7e=fJ1eIPSZz%N=5jcEB>{(`ARc+hU#M92a2ep%%l)&Kz$rAX z{IZx0WrJM!-R4=`JvX6kf|Iw=jyW|a2(?=IN=-)PZ;%a= z^UbE6d@(=GzrR8GxXcn%TtzD~%hi-o?h2Z-^@}uZ`_H_=CT#z?8KJMq*Aiv_qpy4F z5AN?PK!Xg}eMs~FX&k@Qo9AyLpOyo*&#rd?K258Js4Of^fr?v1bI$e#vQqiC@Z<&d zy@OYmb|QdjV~aIKuuaEyTfs5S3VXWjt2dw26vW-&a5G)^Yg(rFoaa<>IhJW*8Wg&w zIl#>EP5H^+tg+`JPPLqWvmepz-^~WU)(NAjgO^2wE|73&wu5Rrf9A+0N8Qn_ugH5? zz80DPM6;W|l$`uQ^So$XGRiy(4jVBI)4txsOKqa4v@+0O&4q+=(VFhE2yl#!e6M+% z$I3=#_o4U0f)`nLJ;Pco#2`qKoD)cy-pz`>$FfHd`0aV-!q0CDh-BvTuAf$;Z`Vu3 z>$zeX{{$|Ho$XpXF6o*fn$Ezn3un$85;gzLYM&aaEU67x>|ZNG7A!h88? zjUZ<0uWG6)mzCw+Q0H*A;HO>tCGv$ZwEG$EeD+sm5=!vsju^3Kp0ie5>IN^N}=2g|f`$D%{^R7B)A~18P^h%XS;Nmc62$GWvk_lmrX^1U$AA`-Ae|tL|@PT zvcl-LewD0g*`Ob4DAMcObn!>j>&pP3o7nTR2FI~Su!VNdUo6IgEF|yR%Du|gu5a-& zvFj8CLyto>_H?PicjMOyNS6(LdsD*pD9f?1tTM7R{OSiy2!~ED>Lk&MOtuv-$V|cY zjae~%=KpG~MDt~9EAqL$IilKbv!Ul>O$PA5%2(*5++)A;ha1c)8?2k%f%$0^;s6auj(7 zwS(Myc2Hz`w#IeVs$0D{u@`q=l9k)(@5EEh&N|pZd#hDYXy^olBrWe2U-ax(suA$? z?E2~~;FV4JKA~xZ$G7(#^V4jB#L6hzHOqzO`2Md_0k}q%ejsa9CU#G?zv6ehd!Wh= zTZ(#*fhL}I&*J;b^s&3K4ynYsZZ(bzwe)xHX%<<~Za3biwkV7SFQiL!c77{|hj;Rq zm?yX@T~FV;%lngM{!e!TDC@a8ztl&e=+vP?A%95wKBU=OTp%3I<-qq-iXy+z!HP3x z4kyaDrD%0lSox-5nz)a$^KAcJ$_swptu#<&>MH);%wZ19deQvss>ZW%_L@Vu6h)po zOb@uVDS`*J>+cIBRfq_DHJZOy!+k?tuOBQyx;bM3oSTVfn^x}s9^b4p6!X+y%0rS3 zf+6nvn{uilCq-fJ@rEXp{#Ig5LZ<0<-_!6f-%=#S4Y2FCf?^Ip)9p5EzUXts&cqn6 zA4t;n$X8UUnWo=BqNx~=c|Aa?2Y}3+#C>-28H=f2Obtoqp-uVTF|Xq~#|f(f{{x+s z?}OsscUuHc1}@Fou2{u%tp6NbP@DqV`C9MrYYyxX?BrxK zM|bm$;R#T(3$p!$%3!sUAL@|wEsY1|Nw(uH7mKkV-br5`W_mOa>VGBf}t^e1qXEZGCtiFC#q=O06?NSYX)pTxyis_e)Lx~?JpCtV4w5eS$ zre?k@9e7-CPAhlnTcC&e6GbbNoa2;l$L4pN+IJq<{Mk_e{LOwa% z`*)o3DtfU&dxzow$@B4b$V``~hNY+X-OpB?@rF4lY?A{C!ylb^YUSs=YPH#Y8F-Em zB%hZ{qjc`tkR*1n0NGeTEAd?ttNKj4 zCz^OkMDMSD3X#aCS8Z8u$(>OTJ@Hxwb#4)Hc!J}+_w|epE8D*9}(3k|xB#oi$js)`Os z^~lu0_N~y~UmvpVmz=NLvoB{o{~&#lW6zGTp1#ggCFfTDq_{6z@OkC_d@X;oXg&tIZAYZnc%<7m!qUXfmZL{?Tueg4wI4kpx9f)`siyON~6 zU-(uYk`{lmO5z9P_d?WN4I4!5vgMG0p29JIvrv;l`PeMr^7#yV5IJ#8&BND3^KBsx4p8~w90MlI%H;=E;`w9+_UY!G z3%QE}zzkWkpE#StZ>-OX=Rhi6SEd4sw+ol($SV{(Brj{-IBp9^tEXs}s!GC^ebwRg zkbGI)q*c>iH+9JHcxs`({y%21zM3PW`Hl@KVuPLz6(PgUd8a~^Z7TxuvermW&d4`r z{jK&^KwWx=XFf+d&SOE7W25o*?b<_F;9D^8q0Kh4#RXwR)f@itNFbjYnu9f z-J^Xxm~Ar5rm}gBIsI-rGH3qn+F7wpZG#!25vvGTazqlXuo_Q9hs1kE9QX>#agAa$~<4vnK zfau!wrBztI)x2n4?my{rQ?g0Rd9bL^;1Bu6_{Eywm)DYiQhI^Zkldzo0okCr5ilJm zMZd|lCaahBEMJQq+dnk^Y|qY_cC%uj`^2n2Yymhga3)n)^3(RI9><3Mma67w@*Gy+ zA13G5#x*=zChvv*-j^%2Nw#3Qk}cnF*Sj^r`1x|?;0}^g6YGbfD~iaP?7dZ9{l0yj zz>}GOCN29KzR@n`s#<|WHyaCJbsNpZV<=$9(V^Co?=_`l>D-<`wAPn zei7&n)u&#Y%)B*x*eHJqhLh6$(oidBdACo}_T(_j?Ud`-QD-=;&fhyFS^bwXcb3Q+ zyy33$4=)rLl4?%QJH^owJKT?I?#mU&U6!XuF|GF9MF&4G+i*kP&MZ|dBtM9 zV!Pn=&5Bu3j3jt=!)UIKiRQk}S6r|e)Q?bnR=cmS-K1+PAJIDOq z!{@)SIpHYDPusb8-ns?D?(v!R!bLT)ps(eqGWe{Q0w?FV*LK zwX(}_EMxFFX(<0;R8mfl!O*WUlwC*#Cw%^z1nk<^0mWOAv7gl(J4bf#TYdeRnUfE* zP0d$_hkl<5tW`1)Ywk^A-E@EfasPdF*Rz#=nYWO6f%?pe^gGK` z%n`4*6#lnVxVmyH$1iUq;hxl+M!(cDVW(GT{MJl{I{`iX+J5=l#6ztxizrYOx|CU7Ia|c zJ!@+>8yRW=Q>Xaz!Fc^Zz9r>Fv@cE$nDA`320#^Sen(SSzn8Z|%>2XFqddKy+nla= z&DH-yiQjv>Vc|(<-1_d6vK(h?xlB%2lUc(A$S_?MFD@Tl?0jkL#$U1u6Htr=_5Nd$ zGz(r{xYtj)5&!wNWL4ezt5$fiUei`Nm=dh`L93NK}0H zU&-oXQCl1haP}$3Vza+4BX?WXKr81pq4_6klCgMphi<-`n_7gW(~*;l*?sYk=wI9oo#^r@Ju5xJYSU<1 z`MK=Oq$3kN+u2TRY9-Iq(W-jh$ksT|;unAg1v5 zM>=j+*Dl$nwjY4%(R`!gk$sDQSIEEJ3_(1V$0B|qt0(2L*af-zT%OW))!AQ`6lF<^ zM;0h@|53Xp50{2WK6FZ+9&eKveAtqzv-|qf`M10~IBDy2WgG77*^7(Wx2&Ip1z91l9)Pc3 zPd*8}tX%zBVcL}q@TWW8LrCUt*R$qfK9{${Q<{g<6{6l>K5dP@4twn#meLU(<~vxf z`ez994o+L=V52h{${~9pJSWxE=ZA{I)hSqQ7zJA>9h9IE2lWg4=b7`RabGOPRbWjX zeO1m0{Po4%zw2}WxQq%j|#64Q0Mjg;CEi$F+@ zl6=Ui#u^BGO_D|uAe-M=spyree!SBh@7MH|SC3y%<)7Ivat@GJNm|$-@08b~p^N7! z{kTfO_wtQt zDKHw>-d0R>X7{jMRRz<^t=XTg>ciQnr^vsU2+gmJVs9xHTeDb2kKN8x|<^;0Qzx8D+M zkp-Ik)eFlWbD6em*R&GCi+`WVn)6$}mo>3$+5ttGJX{;m6I;IG^X=N=t=zYu;{C;s zf60PFT6m6G+X!AtJFq5@?d|2qE-u(%My>fh(e0M#_NQpRUS|oPliqCLFitHLuX$^> zbA8s6r68+^#I=crR*5N4Y{Axyp^$B};_jEqc=$TF_+R&VZIT5C4s6OI`2e1U8*dVj zvRU~p?<;?MMRGw(zP@|~58oq8lpv+B zOJ+cQ-A-wTmCw^joaa^5bAW#t%|F~vYxZ?3`A1&&9M$r?;kRk%Y-u-rA;Y_7;4A=t zw`uRw@^Y>G#d1FLnFf{(-K*_;13B??wq(y!D~9La$^LE>$JZ&AL@{f-rO4e1S%IsI zL+|O%Vm<@R1Q-7aQ6JG)P$c;N*8IWFxic$^MrxnfuQ&Xts`r@;V(|jnGC;SYK#W^g zX;LvLZ~5ZbVaurGDQmv3sTX&(^3B^d_yTK3aL>YIhGTgw)8(6fVe3+fikF-o0nblL zA(cP=FOHui3@I1-00>Q5js{Z&<6~9%N%hY%3b#k?Z})X-OQOIa72Z_48Ye-KsEB*c zQW^8NVAA#FWDKo{$03}G+$XEW>wDa>)lYA*hW(1IsJwR)syWmMTJ1HRUcW+mt@U^V zf?)TzOQmkujc>m0yI<^FHWjez_a$Q|6qS5W;9uR}QE;Wi z+-K2BSB(D?3ZA~$9h#%eOHtd`@oxA2bKAatbrGmvSUgl*u6!mr|8BeZbO~*bp!pS% zcZa@9Vp0CcXo#Cr15$Ezd>Z{zu$*N9Bp)o}K`C(gsCeYNc7*$M6KLQR;w)0z6@&Gy zxb~I!MR-WEwOPO^rPs*YeI0%JdjfA$1eHc?+jq~Bh7A+Ye4j)GJ5c6ysmo3t`Tf0; zvQ?xgzQ_P&4rPWHSFT%hn~8VgG}`1x{j4(DxW zS~lfyNoh#~VAX?{=}U1JReeP7uLo&2NMdYkbmM(}-6?4mbCVK*p4rg^TQ`N?>51Xa zyR{~UUWKvo;p_T%ch~XN)QdGu!X)Ig49zV&PaE>y_l*u!R8hu>dJH1ne_tND1m`gK zNhmQsOt@OwjGdyJ_4s>bJhmSjY6N$T4WpbsJ35pzj?CcxFTqx-bLV7s z#)vK@OyVA>dxJeUejRoemB8MT*Fqb6m9>IE`PZ$ce9Jp>Yv>o z$JzL9Qu>zao&@-{^)#+IG0d{lU;0!vnkRT5^_0$8Rqmwf1z#K)=7TNDW6#U_r)l<0 zjNGQ~&>M}{qe0~Xumi=M8a@X`jZN8xtM+O%N)A7hNPiv3NUlx{O7yLX$+H2F=rTw= zDGNe|m#K*0G>ZKm?{;#fK$jnx8?QUv41ZQw3$7{&AJ|m_GU`Rs5cqoegt0+XwlKrN z*WIWtDC^2FnSno3c{d$5dFjo{UgN{@SRMOc&f$@EfIL>UYPaotY;;Jcnqsl8VJ7%B zW0*nlpxd&@4`**;XgT;ryjZaF8f15F#f8pU-##|TKRU4KSB?yAgh%HRxTo)VvOPXb zQ(tg=QhPSv&%v(iyqTLfGPvlAjSati=IC%1hQxA{&Ym&cJ?CuN364fiABse`IBCu_ zo2DrJUWNU|(c%6YQ;a@f&C`bYV6KOIuQ0i@w97Ox63da9FGtARpPfYv*M0L92^^p zJ|-B0AX8#C0KB6^xaw@$*}8%|-)*uPslIZ27!}L=eWGU0?-;u=Hr$@}gVDiv`nvl> z5+UgkF_eGY#U{D8!1PIUSa^Jx#c^mS$o@DoXx8UkJ-{wc8xm0Fg3d~pO)68o%b9I-63WMchr1N|{Z?xdGXkT^;r1d3H7HVEQ0qZyO)F zk&keTP+9;aVTDnv4{ zV*LTlJ*JySNTL>1f#35Ahz=K%x`9rc+4x`0oDYeIrKx+ZH$OhNCdQ;r`U0;^v+*C`n6PS0iYkRLKpR)*HxKMrBj z#`bY30FSG?(XIAT$lpxtd8Z9GM65YFWld>wPF)9uu5_cv_>erAdt%6H{&0Nq(X9XJ zj?6S-)C8pM9sJ?yxya1K#pe9X+I+3s1iY@m%r}iEjGI9$x zSM3b5B4_ZK(V;2Msm$$bm-+<5JwBK``~4J|?`A^=j*Q0nYSGI{dPP_k)mLZE9Ody!_)Wi@@>hR>utXh_m53mTQ z(P=(sTg-)1D;yi@;8s3gp4*B%=dy5qe}AW<#)lKTcFj`DOD`!Q1_tZ3c)!TKewc5Q zSmD6bDOclr&4ErWoilTi$h{?R?PH{(ZcjBYcJ>z7s21Ff4g zlXZm2&E>w#lpF4i{_R?x9xRK#5C6x!O;j=w0+N{3=Va@?A!U+WmRWJaH11C;Y*YLFA^E8M|nFm^S%kGH>3a*MsSZS# z>XW~EI~ST&5*bMpn^|oVj>4P^?9%aR&8|G1{q2=`2+}MdER6VZqjE{AO^sCL~ zzHoF%{h74(S1 z#N*v#L-OhuO>=c-oE0XV{PP_;8S#P69gn%#MaIBP12#x`n$($K;3(qgP$s)|WH zJ7wOgjdsT`^5z50Nmf0dK14<<*Fk+((eajfNB49>?XPBKDz4czo~}-_*HIZ+p7T z&jb9iYAYkp~2WRKkCE8`!s{d*L}3k^V25C*Q7aAXWJLJo;sk=)>@BDERo!E zhpYf|bZ8*77+P~hSr@mT`1&S%MajCRZ&A9J%VzwE?VCDuftrkZo}A?9P|7*i#Wana zdbsvH@`>8KwZ$fe`kGlQzbOl`%GBWms6_5M&Q!Xcd2C3sIqkj$OIO9s$=P2@!jBL6 zb1~@xQu-4EmO|He6sb+uPd+xp>^x+uapLGOtPg1XR00{W3xfEx=wpUq(?33_bnB`S z8LIAj7&AXg2!>M()97FaVU!faN4Q=(dB%sJ+q9F9lhW>g|93Yf9qd{ZmD+C%|)vvpKmcmRunvA$Xz@)KAe@Fy|%GCsX5K!56ei1g0Z+r+)3ou zh!O9#24 zPAQjPh&mnPa({}fV-YB=2ZozY4#yL29Ul%)y=P*$ZQ*bEFvR}An*7FI*XC}p$HuR7y&Ir?8L9ubY2rmHM9F*F7|SOWIDY_7a$=bhmjO!Ia86(U`y3CBz` z2j$#1NBi-GEb#}1bS7#*CD%d)>4gsW70ytsz#Grs~gE*T%@_HUiNq@ zQJrDzq!@I)KYZI^bTAPA0$fyBY6z%yIkibdbpON|Lv@ng%~eaohcSKXl!LU2@ef)2 zv4Pswj4yCwxelF?4gS@7w0`=Kb1>6&#GGgOt~>-*?4k1Z=A_;1yWLWKg8m%kV|{w= z$fnoEi<$8vH9ccE2zEq00O!~9aM$R7W?PI6Jz!1{|I?(bYNHrRk4~(g^Z4;$F;}r) z0vTt4>-6}S25$>fnrt@CxlNI}j@QZpreA2zpuFtPT#3PM3ZAuT7|T{4oTY1j$r!zR zbMt$@*%ZgG)^iAC<`nKFA#B-+{LhJ@WXX$z$(UmA1i;tH$;Q0#(P8Sy9HYZzIbT!| zypM0Dyl4`jg5bpD8EjCqn84H~ob&Ed`RnMAp*m)4=+he_ENQ?_IBqjzh$-Cai1L?? z4^5}0wE#W7obn3Fpu^Kmy51@azT)T*-yNm_jAv`!RC%5cibXdv;iMR$A)2X?Kf{{mAl)Cb3hI|^SjF=Tk&HZjyPh}l|6*SnMnMu({6{;}b_)!*C} z%}aL?a6J01__WErUx06tuBN;?)L=|~hi4i3mCS{6$dQ-Dtc-O>BvLp|uHP&)WBqTO zKDmkaH{1O9rlzo5+rW^{wm&~MboV_nHXL5yy|CKMhcnuf72iXo&mr7|I*bi2I?wz);o&!V(X4;v zPC31`T#w~|zniI*^STprz3eXr!GtB!se{IbQ{G>iHsncu2v>O~m0Zok`L){d#Ly;$ z=1-|-jZcaT#2AX%l@{SF$r;HqY+e2Qi8BpsNw$CTNE{;2V`Y?e6Dw10OJEM*% zKK5!Rle4_jhBD<@)26(OYMS-h&|^f~tkb)zcgyWV(+4cQc4D}#X+6%W@{n20WPWt8 zL0>VWTX}S3@FJeCME)XMK=!&F9aUh#^V?r zK4Nf{M};}p$TEd#8JqHYyPh$*l~w`4emA*r9)9Q^%y`E*^47?VMZO<~PakZzTa~Tn zmszo0o8tlTX45-o!n3GqmZ4wz>at8TM+et^Dr2flU(saVCU){4z9WJjpIk%+Z|0=$ znlpK*VI9t=C+Gak-J5}Ypxb^qz-F4sMT7q>fHC$K#Yv>ba-M*KZq}vbBug zw7~|wBuL5I@$Z{UcjP6~%I}X)UIx9>GG52Q(m^^x-AZNxFf<+;{EfAn2F&#AmTqpZ zk#%CI`vAcacC$%_Uy><;#`%QTBx!E)S`DU^ZxO69Rj1Y9cm4Q4d3XK1H(SQy-!pTP zmjzTe&{AR+ke1qMLu>96yfui?#MHSn-{P-}bT^grH)ne(-U5GH*0ufJm@Ar2UN( zLp}6>@nK%*HvIFBw#_TcAwN&j+!~L+g4B;8xplmueOnW{$n(t~to#Wx6Y`J&T=Ch5nic?H!I~B+E$y8~YknNW@mQkEG z*fO)|Nv}}rmZt06X)n#MaGN=5ynB4Oi<1w7JB!vnz0=6h?f?CFv~L{e``zI{9Oqe% z%kB2E-=_Gg!cPn*QcamVNUFbUeV)~00MDll1@z<6?rrEuCt|KDoPae)qFQ_d*^|2iJd=y00maoG|!G|H3G z%d-BYl$`TOzwhG3%5XP#QhWET|Ldz+$YTA*9DcGP&@j3o`+LE}TspeKOq(_W1Zv<-EI@z$E+z1>DgoR{ke*kJTv0hLZxnm)Gsg zI641wUNLN0i`%KzprT|}wv1~W_xP>(TKg)#yFpK1Ygu%k_*>~L*6=G6LnF}V$0tuy z#V8gNS$~5Oblot0k@MHs4~@OK`FIQG^Vr>f>gL6d504MibT=yleD;iCklJ54R9U@y z#GciL{sEuiXLkq8rvfI&!k3m~cPlrKqNtzT!b6`v0)FOkoh|ELMDvu>zGK6#h4Z_s zQ@dbMF}E4xBEfIlyJ@9jK!{;niEcfESxiRNO)V1S+cZ6Lq)Uak0dv51aVsaGoC90@ zQYYZHSCw-C8I0=W6ze~ac&(d~Z~?HaQbQ3RmFz*n)!3TxVZ89~CMJoflo3(MgeU;U!`G$6wgBI0R-gtIAZ#|6$U z;x%BzLA6UkS!leDyggJjuSTw;L%4Dd_m){g$=$jy1CsNX{wgTrJIR)J8w-;sn>ug# zc9L^rXV%WIuhk8eBzeEFVN%&%O<)FqkZ>WDV%pJswll{kw_)k{sE|ZNrp_hvsa?&z zgpgr$3V(IW(DlOj^&%BZuXpA@PYnGkFv8c_Zp&tlmz9|*mq^Fq(t2zt1dvP2<=CJT z`d9MR$A_*DmPkCsTjO7yO}vdlVzb1*cB=njY>1;*ZmK*Rx^I~Z)Egfk>gN9&pOh{6 zZZnZitBOi=S-`&)!EIT+L%vAt^9L%EulN;S-^AppGBYZA#m?9A;l%}R!RFo@)gpJz z=N}s~imS`gDDF4oj!6YC^GpoK4dezNFQ*5)KHG%VLz;DS!RT;ijkNH2@LK#U8eOz0 zH%s^%sgJbd3SQ{MFr#}lv#)E<&=PrcCRkPF3Qj`zxjfr^^|RaczbA$);%o9$ZzwjI zuWHMun?v;e61BJmmStW+5n>EmD%`0<)l{~=z(hehOn z$U~JK|1xKoKu0cxy2XzO$o7esSp?70Syt&u-d(epepBu2!e)(BGZtFm?Zn2?a zZ1LSq)q8h$i+n5n`myPP)=cYRnrV~bl%l_j+|Z=6V#egfX0u>Td4u1{mY4H?a&+kC z;%m`ULIpp!tIzQL?spbHc8kp$CWet*5-EprmbxQDmi}%rraU&9sE*mGS*c;moZ%Ac zi7cMy^Ayu@MOcuRYXXta{1xhvQa)I7nCUEAxmSZ?ep#>n@5YDjh(p7Non{Q!xJV61 zdK(t^R5S5D(oNQfi%GL1uS>h%#XFrb?BdL(%Kvi4aF2kf1d8q_sOOoF4FILG2AxiB zekNxdf)NiWCkJ~k7kzwahx<))-4_x6*7LSUC1AeLNo5-r9UYEV9o^j`81x2s=$7{6 z4JC2PI^U8L$Uso=3)}VojZT3%G;0nZ+03-BR^s_f=Ofmg7bvjmaziAR8i71>=N5rO&}YBL*qBi9IhRlR$goE zItO^ypX}7|*5s#dqC1-q`*@OecdisT;=|lkMo4aFJD$9%jm(THKrt*?g^H}eT}=$T zX=G@w-o+fSj?(xpGX#xk=uu%K+?dj7R=~b_XK+^5V}7N10sv}?5)NFU)*C-iTx zmg)^VX^NXM)NoH#qHToKiELl|I-#lZON|Woc>(Ru(Da&-^afy@OyF-OC$FoX1#mvn z><(7o=k-2akgR@eWJrUYD+|icte5!a83V}tx_O(AH|_JbzDsDon}qLAPn!z6Oj8i0 zo4(utifzkV+V$!)hQ5w1nxZ_llf0j;(_Ze5&a#*kS*8n3MB+DTC8f4y9p!Bnn>jR# zvIxZfmTsGc3|G~41{>dizYOt+=yuYs#hajmblp(7@clIuEg!b<4^fp_U&@i0I(V0? z|M`xphkjCxo0?m4O{W{AfBpW0%`TRY-Hr7$c_vrtM?L}b9f}zlPG}zN;$X~eF8UX^ zPbkmzQeT4x?HW3 z;(8DI`uNl-oQKzz;fF95JXcd~Khfdn=XEiuUCtPy@2h4EwH1NJvQwrk7$U9JOoOM> zkz<;sJA0Eeso2l!I(t^1e08P0o2I*9MtSM9`&%ZNSMNZ>kCeA(1x~DHM9WrvC8$wF z6)B`&Iy$`Vw9e8}u6ID!=51cmG~F%x?##{s5!AcICY}9}`Gg*SD)K!=oD#3uTwgy7 z9(Fo%-H{<9`qGpsXY_I=a#rnXywj%HUz(&nP+p6``$LarsNgrbaep_Y0I_DAF3^1P zLo58fAnPsy?S8{`W)7Yn({MTTn>yq-&eVzIq`=5vd0{w?KPaY#+nn2Q$u{lU-^*7+ zt&N%2w(RG!BSZaby{2XUCmH)&(=#4QJ;f}4jZ=L7kLIPwA+R7MnY_vEAE3`<>Tp8j zcb)%Tr>JvzAgN2rG}OL$g?7j0VB9ow@ICgc7n)=2yY%8Y4on(-ViQZaspr>P`FmY$ zo#<4t{eV7@dYO)O-lXpOv+7YK`S&!vDvyPGPnj5>ei) zMZRP570r}mQ-0K0xf42MBUx32X%D!t)@oU7)_Zk2)%@`IX(0K_2hs?yO-f?BLI z@=7G|VZUZTE!;VGz6yC@Vn{nco)Zo?t3;*a*VVId{1i;=aP&{bo}Fh|tE!4PdgD@* z-|R38zvZ>LnvXSq@~CPhqn(uBJ}ba`zpXx4dTr%@>tqU=-_x%DPm^1dd2T0f&A?~1 zPlbndT5c5GHgC;*HY$1g%)w84k`#5A<&dxi#~)Nq714)L%*}&EBHsCn&JK^x9IQ5) z+wY6*;>?<~rRJWto+h-2CDHkDNA@}h@E!hq6E7hNFY(i2|0EoLK@)0UT?mX~`^ouq z!op5a5{&#K#~54rCy&Y7q}Sh>Imsge8gGQdo!pU$x(f@!F;Yq|s1Mx)$lv6v)WvGo z=p(*(KW|q1mb7+oc49u5*#ngQy1v=ab*4 z>c?i?2)eod#75*}?_}kh(G4yr(J~u)a z9jbiN-?)&p(~%HzHKly}s=NECPCEa$>Bl=at7NqT!}B^pbLUXqo&09?FWC62s7=x= z=7i3do!+@ft2(1|cE|KiYQpZ!+37qvz4sZ@n)Sc7o_|IV3R%3Bz=YTH;j{`9}pUDqM zv6oFdU-pS+qwS;OAZDyRmZ5r@7KjIF!>9e=-esIy(6m5-$_PjMs(pJj{LQ=fq&{ugt!$a`S7H|%1PSt zRRI62)Fu1c;i(&%A-rwY&^B-;Qwpv!1Xo~lJJ*i>lT2M zXAT@(xRWdgbZP^4h|P_iKlSs5bcBasNf7e>JX_Y5F0+RGsdfS>C7eH@@9O^aYyX|6~>)KHwTjdtq{za<({PHlKXFuS4Tc zhp6O`hxX30!Lu}ii~_!#Z?jhx@bh&m9olSx-6W1W=%9C=r;BSL?& zl;G`2nl(TJyZ$~~pReU#AQErqkD#o$56Xe@s|gQBt{#KWHWl`XBEijT)i_nb1@EHj z-tAXr0|KmHyiCj3N+cq^r{~olS&U55&Y%LtBbMsZJOq4SAtYq?J@RZ`2a%)cj|g3R zgTH?`EAY|e{58$JyesKCf95dxftIbn=g$HPZ9dScKmxf_a_;Li>yY*?8S=dbPhBc2 zSj_(AX1uWeR!bCVIvqi69wvAkD;mOaX!)~F716Jk%2d2nywIet1wyA6%h?j1WyBQu z;h*fhFl&OsFzrmiS1|>dB>A7o^71|2SmhCC;S!BLgwR%r^EX$9E&QMV=98oO7E%*+ zP21f&v-@xfr`9ZPJ-+Z0f5#4t#E!Q~VIg>W z{pUKgQS7PavpvxPQu4OGFjf{Q8T(NCy(WtQOg2#&>Fbif{@^uCK!ShD$hpR9UHcLR8+uUwZ?avUuL3wV~Cz%{Ti-R{MK>7gF7?Z>n97v}>qhg@+|+ zZ|`Kim#km-iRl+K|M0jh@2vUu1P?=J&+c0-Td-uarY~;h$#(u%b2DZwQ(<>JmOsmf z=Py0q*I5cE4N?8hfg$0y^Yqru-@sh#{&$r)vdXHipDp`sKEwCR3(egbmi@B(tS8BN zJcRK=nk(K15fGJi{#iMSEkcR!gDe~Ds5_vQ0NoT*Bx3gj~#XgE?Dl(mTJt2zmR9Vc|MS(?|*|lIc+SeEE?B^V>dgRwX{N!uYSz*nRrWd4tZ48JLO{B1WE_?u6*>l?F%7xW7!)(ZQ+CLrCMr0tTY zcVFJxX^mNXy5ASOE#(YmNWNOsMsvGfx_FcI@t+$t>H3FFGXP4y{)(h+)7%WwLP-AG zU02|OMqkk>WBP@;XGZgnHTjC;%J_aL?S44x$zx%~Ua|Y%cm=MrM62at!cum8)qMMn z(~&bLCm*gg;_*7KXbO)Jmmz~sGea!Jb9HoI+XSRz>q&TC02QL%1vWcd&EmhYMq>8v z?d_TneyG`EED?P0f8pVDo6XBwJRMn5W2exgnUhv7B11!xb99lb$s6D}o%MA~T^qBCofmET1bOqvv*Zoj zJ=>fyzlO_bLPN>w)kXc^%8t+le>{aF^cn(hm7n>g(jd1eCN}$m*mwikxV^Z>uIbvh zlJG~fBcI62`iLs31YlpMR@U&K|7J#u#UKnE!v#Oj&oW-pxp)+Vc%{PCGcr( z07T-qAQ2o!tI?)j5JLXaDa|6Fk8jRzA!&1j=ilsn{r1Vpt*=shAt}E6r8UX}5VpDU zI&g}Y%a6q`U%xm!T%vs721PX2WyL(Zp!s!W+81di!1-lV-V>6GDZJ+`Wo#hB)9Sp# zrYHYW+7_kx?pklS3^ZxCTXuD09li{%OZO1NfK7+2=$-R78!N%RHZ!$Mxo>(=clD>8Ow6JdKBY_D%s*yIxOPvPm-j z{@QfprfB|N!C;a`a(ErGMsNu@@9~Onnce?bvEiY^rib|4nppuf|5LKMN%%#=Y~rhHn!~V#c{sQx50OYZ>zrkCN7`gT7fvPo?YHJO}VeA@bRnwTY#r@ z;LVJWWlc532i{lg3TeoZ1pW<**wEU}IrXx?4wE#PdHO-IYe<(a* zfB$0AG!&-$yEQM%8W7Vv*DepkJN;EOrz3nkXiTF?8XYG9cQ7L5vdF5h zT+-J!iq{B2>Tc#*=jqX9whxaUYMKx^BqwK;>!4rg17X%MjXVoTeb$ty%yXDpUh5Sf z@4xvQ|JSI8Ewg|$`hQ%$@^a5dz5i=;#_CP#V7X{8FAEv%`gZ%}xE3V-BPpGwgJ#PV z?-PSJRfD5nD9=XY2)bFhoQa)|Y440sH#b?_N(<)TEfPiMim3fX>99Ov0}vO&Q1IwWfP1atGw@(kAh@T-!> zC}YZD#oe>{%QC3;NamLmhbE=_gr#(o7xby5l&6YTzfD%HjraAh@=GESR=^s3an*JW z1lYb?n^N-W{0-PbTK>^@IZ?m$&SqucIN#=s5Q&bU-YYtCeO|{K@})o5v&&`y59kfM zhsN??c>0r``fXmvw~}+lh@8JYJoM~eqB%tUD&a4DqbW#t`EL}4(T&aNge5t854 zJQfm;VqYxpP0oK=l9RqHo3g+JR^a5GT_{QO3k3DMO@*ad@Q|PUM>({rDn8-abJOmh z&z$tdArINJ@L|!=*t1*2Yhbuyxx{&k z1j!^!^ZOU(RE#&4#LD5-3DH~}L{sKZ(&z{|IXNj^uPA26n(e0){{k_5|9C5B>*uU8 z?(1}fj$hbMnn-fqRZfY=1;Nn&9+os6xxQ!bF4w_dBGtcZ&e-9StHHuM$_1gym*dwO zMZUkN0_Y{z)d>SyX&Kc0bvm+EsPc@$Xh&{Yg!!|5kO9YUYS%yjKJZwq2bw`$o~@k2 z+;Qp$eVyCpS^g6Auy_1=IXv99pVua-(nhKCozd3ir$iT8(RaxZMJksPG5>929;`p?vO_1o? zcZ%8XPgD2|%+|Cl(Vl(%ptKvFkBFP{8J_6t`)htEU4X4KvP7#S=cwoF2T4Q+DNt^+ zX8X|VF%EN+$+wN#1QiX>3l$eg&S4q~-`sa?2QsXjj)l3fNpTSI|Vu>ikr?&SRRIDUDllKHcPPv%$O zlbo-fc5~_B(vu)Gp)@P%ub+@?7~@B}JC7=O7))u@-F+kq9GJ&Pk7v}+da zj)dX1T2LMVo>hlgf%%JNDE`5D$Xn*E-5bsMiZm3yk|T1{wd+%J(f= zC%Uxt*gQQR%U~MPugk+0`?3bo0(fU8Eq9dX!3ElJSG)dpO!syBvg=3H)be&V?H5j{ z?+6wFivJ}oY!EL0ESducI{Q*Kl#Y|te`bAljpLS0<~J@DyT4lAYjT^nPgCC0NF37g z)?$H~rf}qYoZYjt_LDbjJ+D_-w7fj7P0E5I4k@+rc|$fUe|=H&Z(B2=y1NEBp3H(( zg4i!e^0qidjL93C!=-bJpn17W&x6H5yY~hN;1jDV--oN@e4ZkaW3#+i@b)re5bqU_ zdaSP>Swh~D@cNK;{dAV-rLbkwzDwm-DE(_aXl`G2ZI3_B3ZTiYJqsUJ$qvHR+4v`U z#TOKn>=4DsnN^MESKpb3`T2fQ4FZElX!7qOI5N+AeluRbt|;u_(*FM{uCWHb$4ONg z{+@pe!AQ${quYFCCbtfD{9ihIp~P$l>;415I0o{r61j=7U7wO2@x^IXM(okbSp<@> zR5jLxbmiq^v-s?we@9^i@t%|Nmp)f7!Ms&fR;ti;M&_0MeIo#D%{cYS*CGFYj%h?9 zZ`i7dYVRmkwQ^c^0Att7Ny|1#i(OmWqm}+oYu7jem%dxvy!0iE5688? zw(={Z*gU=Ar&&O9&O(tiyI#IL!?nE}#nAkMkm1FiWm6uk;&Xq!UTD7gu(CnRyl~OH zV$`tfnf?3u;Sy>C$%mrVw&4Sjd!P$g?RvH{ROm*n_DaI-3-2H*{d%H2k6nAju6>=_ z(w8I4C0h9$QX=vCQ(?;`ar~oYL4P0JNGTuwie`fYz1uUs_mR4^d)D6IJquMVBQ2|C z_b)DroVCcpu2KFQt%3RbS7-J~(VNmrl18?73l%{(NjtA+X%-uV=J%?Jho}SzQ;w*X z_l9Ur&Z*=ldXG;mz{if}Xf;wK=v#M1w>1;vQ;P^bnuJVE((sGbJ~{2)pqS$7@?(#b zv>|mQ1d{(gAhnYBmzT3^+P6UZ9 z8KS-`OnzT}@(Jj_*!olnU{|9c$wtl#%e$Z$<|C#2&Z@$5I^_5A7A zNSM4~-^Gr1bTu<6`g2cBhDbjtg4?jj{fgf3-dA8hve6vfu>PrJ=JMWy zAa?PKd~!%a0;jd>CHt=R_(Y6`QScfC?}|Hfh&Izq>Po_>v|9lwp&d-PqO2C5rH zx23Y7q>j8nauSXOzI#zVD(#phY5!-^`K#-O5FwyXiQ1KNlyQ_@chf4v`1t7o=|2uN^hL0vK{@&I; z*{xVt#Wghd$I9U$-T8A417Th_?VdeUd~Gszbo_cv{5mLkdp<4m6U{G<+N9-%depsV z*(~_n=?J-9D$~3|xenTWQ`q=SG`}!3vno&hB<=Prip^Q)7`iRd*l;Vy0@8j(c>ZkK z@jzO(XEbM>Z*Ic#LxeLl<^#Mvj(c{VIt1u4TXuJG0qXIK{LNo0x7qdQq8J_V=PjY= z*4e(_=6mdv=Rms=0Y3QLjYTosA-le^3MM+EIaC>|r=1Xz7IMB$(Gba85Od?WHT*k7 zeYnOaU*9~M;}G(+USwdSwK@mbR~oV^d5@>J=_>#GVa)&}2G z&dx&i(tn^1vz4i$zjf zq^I+R539HG@3iK(#NNi5%kj(`QkK>^Rw8nk+Cu_RLL-Txv6sExLC?c?F9@8bBE!{onZ zQ%K8#dC1m-VxuG>uC0?r11wPG8$C-;?aQ+?<%dZ-cEhFbgsAq5#kBSLnl8Njh2&)K zXpY+c{*!U-v{wGQzWceJy)q5;)BC@bZ#Q(r$mD|SKN z+Ci*eGm#O>xa}=_ZQd<7O$2XYmNUqxKrFQVg$gjWm9#xByM?>o@Is z|4`BT{W&}>ySJEv7Q^=ci(+R^8UE}QqQW%5a!32!F|n~`a$~qFFU-rGuU+rh%JGP#9og46jBfrFN*odn(A-@F; z=Z+~L(O2H+*g~lC{iczBwS0wN@ZI0HYj3a%&+}Ax@YZr z&vt!O=RSUy|FBs8#^;i76q_}P#g|X_b>ILKzL%u!lNH-78+uCE;?47y9U^sywCkgi zG#q@Qg^{#t;`qL0Y5cTnZ=>Bb*Ha&e*VYW-(cB-BgUfdPw^#F!$K>sNHY;ZRZ|;H^ zy17vKnLXO|(Q%x#(60~oEb*YBCqy^C2c@9nv()xLL#es5PK}t}d4Ix5S}sgZHqQ#6{2*I2e_G`D_u z^4cs2#d(G1bM&6md&#a@-YwHCQcCe@x1S$OzfNn-=Oo()_T2@Fp#PW!B;hm^mZA(A zqPdkX{z~&t(d)uutEflPCM!@fosb-??U&eW62(xSrL%I{VUgCnUr(Lgf08s5-no~9 z8nF4}plPf4PG+p}uqZ|jmn@S>Fiv{a{=Oz%!{}F#;nF0H1*Eed%nHy@>-o}dQx|4$tPcKYY+CVbmEx?Pq9p``W>87SH{$@x!0m3`9g=|vOxMczIc zzt)M)`$sW?v`pRLi?wpw{6%tt=0JGmD$X}%hJ2A|Z5Y?k|1GiO-G!fio>`vcS7|q{ z(e975{)gK2fzkYZqGM!9?|hDaOd6<=FCvUZ+12_fy*M=_Kq+tB9D z811`o42eFRmTjJnT+;e&pPch{NX{)Gxi$Q^h`@KrgN@sxudkW~Ts?}>P_*)A`Fvw< zK&!c;If{L=uak4ad`6Mf)_F~^g^l=HT1E){!g?X<55wde%ahRYXG>y0q(`Iq9%0(r z$q5ROjq}UqQPmY3<_#6kY~`MPrombF?Cx<`ZOH&9lYU2(L(hkag zd5C&IG$%sz<(JtYy7`iFZA`>9yGBfI36?EazJi?pI8vOOhxXAVji}@JF)o~KC=EOK zg)HDj?RvpnwXgMcdxtG#2HmzyW~dwav`*V6V_5K^#NepnN3Q~XYZeflmX@(5_H|ft zf{DNB0E?erE^z@L&~v{emN1~+I#tt-iQ{x+*5WdB`*>XQ(Ld=(?CYP1<91D|X((#@ zWAD57b##MQXJ-qx%LSb%Q*mJpiV(>zwg@E-&6a@+sIpaxhE{LQtG9C2gwcP0Z@9b| zaJS@by(k9NVZs%)C1aLfBj-zY?02?$_Hb(T@?&0dU45|jzCm`u*GF+DniJ8bI}3?% z*M}`vknP~m_=)!Zp|tG4BKMQ>7{8R3EuO5> z5wf~>viiwPDov-wtQ$Hn*5VHi(=KSuXD4YNDPurvJbO-S-aTx=YZm7RI4cLvq;x!9lgW2wWT`4neLH0M zT3@Ge*6dl<^BwJayHMR57Ah8=B@=_Q4rB^xD@y z*2}>Zj$f1SaYCNrYsdI?w{Xv zczXKzUQ6H!ImThxN$CihKi3;*H%j_C1-JLlBx$H^*L&pK!=;CEDSe%^K(-S@hLiJD zua9B~xI}&?oS`X;I0ciQ+OPh&H?RuM5%P=9AW!SGh-d|(^;c?1evb>PN3q1~hkaz3W@7Xzv#7VN{y%4hy2d%8+ z`+fZvecgY;^LMsLQcXg(YS(M0-QeA_0Sh_5I&ArI)I$ev#!Bn=tX<<5YLjidfC>H{ z8SxaUQ5b@uzwhGaxk)%K`0i}6d$}~r8~i6-v(7d0l#tGMeVsb|Dgu%*I(r+l8vCHZ z3Vm^{-ovKg0*cWBFQ1WG+50cs_2o&pf44R-v1Xq`F<-Y$(nSVP0N%nn+VS5?OBihp zXhrs}%;)i+qnSLix@=!xH;={F@q*-An}ylMz5PC}S;J3b1YW}sU-w;VwP0K$b>09E zSILIHz8Kar{+!XrabKraa2O2f%Gl!S-;J`vTJ{Xp`IlnC`>G`%Kl!P{d%CU z6MOqzvS-l^#r*VV(H7=k9!@>+-O7FLhHQadxY3}P+QL2+}H-?AbE_~S9cmyO1!5| z*_!3WLAN6{4*C$Os+p6|>Yj3#BG(HAeo)tk9ohT#;M7TBpfoZZgIaB5m^iwi+ZM`% zoK{c8h46pIa4g8I(OC>LF{7*&>*p)Y@5jgPWa82zgPdGEbx?J`?4Z*L?wXl7dA^&- z%vbMT6@8LNN_wQcU+JvY5hH_Ves*W`KhgB3w|Aa*<3P;JD0H}O#?Xqe(Tw5vo}T0- zyE|Zd>-;Iz7#YCrhfVxAdFqrcL9vrNojetoY}4Id*UXr_&SujZ;)?Ho7#Yxb*O4K> zbe|e&Kh98kkWxHIvnZ4~V|ej>lcT_(6OK93_jiXo^S$%P;EG(|cORNMOg8g4fz*Tg zE@)|b1CGxaX1l$;$s5Oo@Qa%?*i(>X?(U}iLigFxvirCVbq*izJ`tw5nZ1|Hn0#}$ z6E5HK{>v5D=?$Hl`*eY^&jH&1H+2`_wv|=e$IscI7zkpZh#-w1EukVQ&7nc0Lqw!Y zQV=8+q(eZEkQC|e4oOMLLs%awDyXl)-h99HU+;b|_jRsy?X&ir&luw#_lW12b3SuJ z1=N!x;wvxX$tDt%9jp5OzMe-AZo9P?joqJ4vqIjU`PbV)OK5kC*IGTlv!a)L;e$6X zC?8JOplXe}qV#gR(H<>hwC~fNO;q=4>Wz3j`^M;CVe_8$yxOa$?6<48nYZV&JnM9k z@-cDH_T%c$_^AB>K&N>9xTo4ZxpRv2+wJIQ+Lg#0#KGO4|A={9J0Sh5l)GrsRjVI% z#e4^A4r7Cf|D~gY)@$0@eq_E&x^8*a9mvOO#V58W%*bGuutR$+YrT5Zb;`i&cd7A& zu>pbQ+qWxu?7{uxsMC#|B%(-y>!#Ln7{2_e*n73#4kqEZx2EZ1>%r zX&x%rO^!wft>a(r>qaW#h%i6_VjlDvl|{TeQqu;x-n1ng#7(IU%+ce;FWZt zRgfD2HF1K^(I((LzPEXC(y8~P@@-3YDn(ghYb9&0MHB7fmyMOpk+7-_;l1pb@L772Ce#CQ3ql44M z^`Ep)ql4nUXLmtQS6?AmfdA;ogkxmmcGvvk$bih-^Ib`Am(In&$)kh&m*%r`H7Pn1 zjivdc`-?ooBJ=k~nCsdb!JYGe6G2`)WaQ*d8XfeRmy8as_#fO87MffC$o-Y*wr{hb z=xt)|8ysn8fcebPY;$eJX>`yl<28ywK{s{GdPg=1?tnd$TW8*ZsYo^LnmlX6<>nga&wYFx`_;*JuWZy`E=$3G2KC zKnAdHiA9OtG4Ys0Z4&3{73Dt8Y~Q{5T)#ujyfDPl)3r|{gRKbV!_73$)f8Dpmfom& zI)uNZ5p6fTSF3~bwumQ1d@g_8Z&EyQuzUa9z2IzjFF0$pipP~9v^>1GL>Y71vdU6b zcDWAnCt#pGDjDlNgY^<~HiP)Ah^RwT#lB)?W_0ja?rgn37wysMk*W#us36PKZ#?HE zn;4>d@2>BedQxoDR_`?u?mX8*I{;!r7y`>r94xYJIWl1nA=y4u_q8T4U#+jApii)W z;NoWJYFYbH9M?MAE?p0uwYy@TvVqCSU|%&Eeja^`=hxmH%tqedt`}c7FDoZJ+%xhD z({e3v?Q9p3k-_c64^A9hsyiEEYFzVEh41q?Hr$(Kbg=i4%%)7N-|mY~^wNk$I-ibA&@0X`GMHK}J~GIt=l9Ir z)pcAe4zE1Mv58~v;-p;=uWY4DRDQ6nS@-l(t^DONo8@TkIm*^tC`ywvkx zrphv-X3TpcimV0dc0Ob^rnj*Dy_xKS%@mw9y*g`$QtMRSyJSJeK0i%4egm#u8O-JNouYxVf6CZwZ-1#o-AUfq-9T=7aPt1q{o+tWRb@=!ZQ>N@UKdfJ!Z zuJ&+{hxy{k(Lo)Y!7j@2U)S;P9t!w+{`#MCk?Mb1?JJ3^b$bSXnGWo1ujA^195_WY z&CTosFGuxE#6s;XDnIB7aP{^Q%N>>r>OH=1*Gh|Ex^#u3N~C@LrMu=l`>7{sH@t_s z3wl(dZi|&&>*&OtH>&L8u3&F3Qf0>8oh-LE>l7D$7ms3ORXZ44UhZ89uQoe)tvynP zPq%IDJ$-skUAitZ>7VJ=<;^EfINKU&?+5Wr<(n-~OCV-mX99O@@M7+>)Gxa~jSeQo z>ZvIEX7h9|gRri{=ZU>d#y%L16yPUWS{be$^xTksgLKci3umcA2up*Ho3hzKs3*>r zXjNIQkuF5s%Uo>be{6vEf$eU3Q+tm{ysvgs5d6_bCm@&JCXh!4kAcgR2egbv`ZP`C z=kH3#LSyf4Y97&^uE(3*mr)g#`LvaUW|Tkf zdZo1Rq8ijr2tFgfiBu|bF-XAp4-BzA5m^k4M zt8HFjq##Zl+)R#&zi&&f`(Z08t70@E-8dKar2o%bMiWPp+cmv~h)*D;${YFuF zf^5+I;-h1O$?4HOaQZ%xC*&>J7pn#05b|FX8%SfNF4yI$;Ri9m0ROJ1ARs=f|Eb2!)d6Af>T z4mz&QMh88>i@o&OpZxxZRx(MKb0)Wc?^ZHocc8(1_AXJ0jvv)y{}nU4IwoZ~S3qs% z(qwOuW3_54qmqk?pR5?oB=u$ck)wldVXLu075-gbQ}|Zt3i1cq8SgP`Q{FkHO|oxW zmk!y!rrqS4jjBN~R{_=HPjqLI`m1_w-yD{&ztcXD@-w+|i<)?4>n*x0X6ovBYMyqX z-?vA?sm<@VwmP?ZyK~=b|7VvT#I)=`{Q%hC<6})D&?V!(ZzPX$JaAeTyn!n$mF&-Jb|K*yx`WL(d z%VJb@@Y|z#D=(0H2$^=c2bf9IN+e zvj}b5!8-ij1+&S>;NdkD{3lJ6HtI^jI#wgQK(?E=Bbncg4rI_h2fTCgSp#Hy@`XrAdE9-;YOFLu|jNaTXcM-Dkit=)Pkob_Pj1V%PpI^ zTLWT}|LEW<1}4mAm8?re7qok!Ri;nH8KuqoiE1SS{J9Durwvvu)cw)He&%xHT|JNE z^9*mHYNZ7-kM(wm?RuYxthlm^S)TV{g?D<|YqXUXGf;Lh6G|VgLnHkU7KM5{+AIN8%ycEy%|{)dw*^n*DEbAXu|(l1)L_2f3PUKzZ-{;c9*i*wiX%Q8MHdQ z#)JPXOYcBema&+)e%zjnj#oK{IlE-tf`}}P?*{(Rn;98#E{sL>@?MCR7 z8(W2zVf1M$WjlnI^(OHa*mWJ|UvN06`G^^`ANz;k^Tm~nMaKqm4==vn9v$p=YQ^C9 zUBP*8lQ_tGC)8VYdKc?bS^?OF2-S8khQh!|32z_J>>3+r4Wjr?!XE-MypXJH0FUxb}Shcp)rKX}izp z;Ht-Ut!mC3-dJo_1Z`XS5qIW{+F-G4te>tumTd*V5pW4Vm{oTdu=GZzV0{~ruw^@_rG9;BwuF^c{}nwXvHjFTW@*0nlfv5md?{U zzC+6l zILyDZw}Q7yE4SVlD`UW44$Ea@Z_inz-L^m7u7Hns#bK9bbK-)Y zawDEk%XcPvUVWt=bz8{v)|4z<)T#P?xt9ANuXFf*UB{88ZzB_ycQwDPenu_FG1?fb zH`V}64IpbyQLpujx{e=qLGXoQeEQzDprY}(6^zYOOdcN0;G~H5HvfHZCz)K2yVb0^M;GL-ImZ*o|WqrkB}(Zk4TL^e`K)h$ThB0 zg!%SbwR)|U)Q+M<)?Knb^X2x`m5BCjwg0=#W#+S@+kS?_xuoQe)hD-?;5SAG_iX>G zdNhZs+O=BR?$m`vnPDnf!j&3VTARmhYDuJP4Rp{M%I$ae_MhdGJ_W71F1EiV5wCxB z)n;UHmGE!v+xhEy9%)1dd)ZrwQLeXfnG*?(MNKWsiW``Lw$kFt6pzqBM%`4dkXKvn zr@#+gnDcHX@zOYlw#SZ2v4!1rdcT$aaQ^zeY1+5*b+e%`e_(8IUyV5wt6f{9;g-Ge zVQG_>E*9(4W*Qmn5If7r;3*jMxD#Ee?*^U|s{7Y8|GjJU-P<*d)u3D3%W<-e0$Q=h zG1X+Y*7v5=PqwF%Mpr)6t+CxFT0Sv4c{|GIyKKC<+dS&l`0Gw)HouVcwyn3{ z->j;)-&!A&ofd3wB580n&YC!2i@Emf^$+r%?gb#DMfX_QY}fXn&@(J4qDKc<$u{5X z#sK~@n}9rD6K5XEvk%t_${zcu@7-Rq{pg_2yuzFQ^d4D(Crfi{YiRQDENXh0SuK!( zSJYDSb$x>VaC*%^m1u*pfv;y$+ImL*fO3L^Z)wFw2X&40^QMXdlhZ|(3DjY|+w0)= z2HdnYggq!+Et|`|g5f4J@Y=HN++_-OYu8yYZSB_lb@;35bCRy5Jgk*K8R_hagS!{Z z>13p}+R+g3*&&hPq0Ed5v)VjTZO*OBEG z44fktiRo?U0x4t-TMr; z6HgpGesxOk!TEFVF4yHs^`C8p&20B1SCfm=KiBD52bj>bn$^p7bzo9&RI#r+j{QBh z$V(U2@I2PM;4iJtE!ryX4)wPub)kW4-spxL`CJs}jWO9bV)+xTFP+jZ`g1mOf4zF7 zUxGOb&DeaIMSp1?F|5MY{Ll4tLsy-aX^H9Hsy1J*RQ<*Ck2jC=3r$?zkn7B4W~N5( ztysKeUb$3F-`8uykr@oMgj=bUm(_h#(X zK1e!b$Ny9{x5nOz;)3C3O^L@hEy}Dit=BWW`&CEwX!gCWd2X1?Gb*k4ddiKU-Bn_a?ajHm+z&VZov$WM)R<+{=kiwebPiYZc-KJZ>@Jb(yT)OyK&~?%na3oko3&o+ zve(7s~{YDkspyylbLJ#c^C~fVQc829vz-BeJtkJ&H=&@4!XhDqO zQiP;xb;!)|eeEH6P_x&gyY3@EV17+A^k?h$bZ$SlY+G0VgEnM%XKe7Ms>@pYHI9hb zN^0tsLK3%X?tEsmU>Rn*uB6Ld!tEbbF`@KMZ=J)j1)bHkyq8+WS-xP|rIkG#d!Vdx zrcT=gq3m}$&UeG4Y({^*pS-Haa17H1VQ-$@vlWs>lfJdW)Y|pA zEoREAKdjHxfyjqjw_bN>0Y0G}j92MyqW$vqFUN0h<8hUmMOPNlXSWyPyp?8b0l1>4P! zG+DLIcKhOMv^yob(Zc&!dHA;094>9$W4q-3A->U*Rx5OU zU+1PX+`ro+wW*h2dk=l@-yHVARvXqs#TFswDu>|wdaYjW9p;n69G)UV4f>zfMtB5E z=hY;%Ps?|E6mn5el_SCI%TpZJO%h!LY5}llM zoG0t1n088+)jrw1UUY`i?cLw9LcS)5-#^fO1ygj#oppxCt!Q;K-qcZI{iI)RZNyw7 zrY+djaP&}v@O<_V^IuGp@x;JCy0u$A-?;nq#4=<~j%hUKWl2L$I4S7<*+SEY+L2!E zd${L-WVXA)+>CGVdv#E}$dK|RA}!Ub@6&xAHF1KU1>@?K&7qV+nDaOj?(Le$-R*IT z`I|J^*|f`fiXGfwGFw+dkJcEpWT##)-wmY~mQ~)U!-SY0<&2G6buaD)^B2jFYN#4diZIbn$Hq%jB^F$mKMwzz(-`?zppj=IEs zTBXr1y;H}?r-v21gXG`kl@*FFwqvPN4$b3lh3cN^yjY*&%a^N_E3WF^^}R#;vCuY$ zRb_r*O4zfF_jC`#FXAWki~yevx=y71UMzx14!4#fX6TCT^<59uq51i7(l?1w6Z@2r zrql_yf;wk6b==V{HN9mWoPV{Ytv~m> zb3vgd3R@2PSGOj@YV9o5UqP7F=82Nthr71>J}q=NBRx}8-kj}ACij6@`o-=;X77J` z&W=!&n6)G}w>~+B%jA4iDH0f z8T-BY_tW)A>!mYgKHa5Hx}8-vcs<+CE3y_1^8>msQHSvIxa}$$KcXMMZ`w@%weimn zT2ZVnAUwH(>qDM*==1N*BNpn~-+6t`QXbg0Bh?_Rcs#zrT=2eFO5CH;ze?A|=F9dU zl-mWbk$;}tKUWc+3+L1N97=~RRR zaEsClx$?tuJ*VJ%OkSs_VB~Nk-`7nVFO`{fZhm+KzrGUF$i;t=LhhzzMHvrIwoJO{ zfRc0N9Gc5!x_wMLGGA{N<|B2gUmFm15N@>PDahXtVt?vt=@aEBR{yQ5X{3LyOsM*h z3-g7{?#m3**PWi(>X2M27JgbkJgF5(ELCWZEvHQ^AI{#x8W&~t;zjYfS@&2S4z|D5 zLYav8L@|pS|1m}Fze{8(=8!JEFI>?V9eXMkAoFlGGXDoWP9Y5&v?U)9!8QM{nrSSZGNqGLQq!{X+ z9@%rQq=5!HmKW+7R!;igdVh`IDGUwl71Vz8OMmY=CMmC!m-fkDO!#eA#9+Q^S!L$p zTD(Jha-9!OQu2XjuQx`{BjSmX-{_XD*PAWUgyv#!tC)s_e*a=9!I@ll#W?boSh`)( zZ%^~@bZ5x?)H9c_8$Z7JGNDetEWVwe^xHyy=XfiA`Io;SN#}pME>qRpDdhc^bVuSd zU;iPipSPG(&Be6m>VbW~b;nPg?KB7J%US*HtUfKCKUA-HRet0Mpj}uj&sH9rJG5oj z_chIdxfp@L<#}8;4&giMiZSrXiZz#=mgXNy=LK5_!!#xv1oGd{YMF4xa{R~B8dxW1`gef^^Bojm?TmR?gn^!+b!zKkUQv;BQB2UC8@tX?jAKUP(O`G-p*(^Nh2ZCH8XM%+d1W+36{c`hnK=L|j3 z{SjSdxs8!rDxR6mf@zJEO zVo}e(e&*k z!VS)glpl^NN6puhvu7n8&f@W57no#{mN?oYYj3qe<;#Q8T%wzyJLS+-&mj7q zuLk|7_);d6lFQ4V7~;Pl=&T`b55?HE)Aq2Ag!Y@|G=MA@@{h%|jmj|M)My*Tmh0=6 zK9uxRLi-Q(me*#*CRy!ZKW8L;h7g;hCqU&2of42~zIoR!FHJhGvis$?v*Tj@7?u^%B}BN4=eUo)p6kHE(>ww5bBeXs0H3m`|Pv zZCwPvEZa-)you9OXuYat3=GiwB37l z7+dgBO+n7>Ylr^Y;_YSS7J2E#qT82PIz{v_CL5<|&Q23KnGQc_oCTA9e3@{yIz6_F z1^p5)-yHrhmysYQ=cpj?g!!v&ZShnfGmw8}_@!EH_-3<0Y?-p0Hf^`qOpuT+d1?H>`IH`mI> z(~Hg5%Hc-%ON(Z56t?{H-FLqm=1b)96??GjoK{*s)Uy&lO!~!n{IdAAYV&s@CbzGh zPgPqY+Av>V5as}6`?g7cK5p|GiTl-OO1$FnC)qyqIG&3~ntZN_lF!YOdv5cBOOtze z9_KH4^%r^h%XOP#lNI~q!DpH&%wK1CPJEURkq7JkD56`b&`CP}u&m~<8LchE?Gx+t zxOkzsF>7~TiG{PvtDDw4ag&JNH>YEZF}Db9{E;yxcBE`f;v>~x8Ahc$uE0mXH(F+w z({?EX!~9ddXK>yor%d~5ZR%cCgdb=75m~WBqdiowvSo5NFPHAtiWW3TM-NUpr2gdY zynI2@H|q1Rbu2vY2h-jU;cP#lK9i7nT*K{FHzR1SNEazvmXRolsHY}(t5%c5^}oAn zK<=;7VX35FU4Mct(#0{YWql#9y;Tl8Dvv{(s=F3tVr0&i@8<5lDI7Sq90jrW#vh(S zS8S2f{#>N^X8V|STBH4;-f!nPrusYP{Omm_%sCPM&nA7J`dhI%ZEGz2j!3iDUW#sf z`-Dr4<;H`qW4m%j8mx_*%R8IyuZ)4RbLf=fhln z@QbMADVlHnUsgX*hN1Z^T^0IdZ|IPhoJ;6(vG<`EbxbyHSVh>h5q{F#MCn211CuTn z@s2qCZI9|t?ts@~3){b5E!(MNbyEEXxOjP`GWmt$(LLIYOn)y8V%p_-YTxnM1h(wg z+Rk$gzCqc0zc~4`&^}>&g7#u*@YmH}zZR%0pBZd{Laa5))_bog?f+9Qu&&>R-$ zS-NOJbYtrbr^NFeW9b3)ig$!5~Ua0!~sN5o}(2aCld*^FBcfjg( zr=;`sMR6N?B#Lf>qsp6AvC+5+pP}??$_x_Ar<()f+ZE*m8Afz(mRv-gT<3stcmfmX zVcNO*niE;Qad}K8=iF=49OfeBvq{HE{|KCdYf4mj(L{(V25!%8IL+sxfw^0{0yPgawD zVwlTpr0(|FU%6HJ%%R)gpAh!g+z}oo`6|K&w5Xc&lO@`Q^6@y5hWV`hTbB zh6Cw;O!|_=m#U4HXQwSq(0k72M9KjL&Zq*i8c?zXSr2D5w#;7LC+NsLen>e%u7e)J z_&hAG?pb_c+SK_AbGRPz-%R>NWfd{b%Nr+M1VR4p`lWA`VOTAi&WmqICAudyd)+sy zWpbErTcpgIX;;?vuzIXL7$I|L+3dk$oXhYHO9u?>_=Cf0gyUnyt9r zyd*;LZJ!V`j(qZ~Vq9>|+`dmAFS_x3uOdZ^K>L{x!xkJ6pSSlw2^UWsC0{Nsc=PztI zEvv-{xnJz89VQHZ;09#Ay{~g=PVOev!K3Orc8@bFHjbhE zyR8_J|HapSX^#2MdbiJ2!hEYj21^~kc^sQ1%rTAayyp88lI~naAwO8e0<`58--vDl zZdWZs1IIgt)oQ3DBO|@f+^76-Z}kY(@kB)UevhII@=l@o3pvQ=b@A;dMcJuU(%ZvL z0+G!eiF*eneV6o?snAR~ko$DLX7xyW@_e*gL$SrM&J_#3S-;HoosR!Tn9~Q-kR0ax z`$qc*;xHXReShdtnAMw=)7Wc#h_UfYkwLiqvwgatA+p5v$;HUE$>lyCmmlOe68%6A zR%=#AgoEg2Bko<1)i@&(@z(Lcq4O7Fi-^W>S%O^NXEpK*|KE1j8Dw{T%2{$6ZDm^i z66|3AO1b5LF#lH3v2-!+T&%k@U-S4oyu;=l?fL}fp5o?-Rl|F&R!y*4O`MLO#1g52XhGJ4m$G-+jnS$eZq|na9%v$ zi4h2#7(b7w0v=Oz+*fovob9U?-OlCBow6F{xb440JQ{RomXnP`Vff3#M9_S>tY$k7 z>=x6$T0X}jn8Szkb0e*DhuIi24UTV4_YYe{J-LqDGFKv@1C$|F-&p-+HE*63XMS0v z%vFr6QO82A_*Ki`EHk_ry)oy?ei=rGyaXljZg2iJ^Y zUn_#wm6PQJR{tblKaj7b1^-qqzV<9XV9PtLERo2YK1I`5(xG}}R`VCD#fVRAA)UQL ztBG50e4S)+R>S<{I-g^cj%hp#9PvU!tlKz-%f=WkM&PqZ2cU}FazkoB%#lCJUUgpp z$_%7qH4exLetf>&QYXcf9wByfav{c#=hQXfo-9FOd|NVx?_X9CDJ)$rR}R%9#W!Na z$SMNq`~@pX1H<)HyiapH*|c+433D`HudZWgwO~?rumySeox(STHVUwso}?QA=3g%=m#J&Q1SDs*q$-<1@02PL>HcI`XU&t` zU5hVPLuTvp!>4kn(~R^6{&n;8cp_sNcUr4ix_b(5S%VXD0zD820o_?^O;H`vvv;;B}wGLZ^oA8^A~W8 zAQC}`Cy?YX17eyZu?WtyR0Vb}vPkDy)kLSXU|z!(guFJ|&>YXj$S0~nq;tAYvgm{7 zMjm3mZ2c|sx%J|*%E{q&Tnra+#LTBerRW6lk_?^{a7DGf39UO@# zG*@NlK&6N3vOqUmISuBb(tlM1(ueubFX}Z;VhfHL`LJ=!`B#jHug`ZDoY}Q|P>4`o z#ul10!3byNFC)PGlB7$OqT9bu=WFO4FdieI<}W8g%(;&D4Y_{UPD7aE7zg=WFU0uk zoiK;+n&Sb;U-#xOu_@=A5n?#c2V#u9yAi-h|JBnR zKQUpKFkdUVf(T-;#nls&ened5JJJo#U$ca{sT(7T=T31fR4e=&1Z<1jF9fxlpD5%3%(-iDgAcE*D$; z;@n%Z`jmLiU!+_W&&S3wHyWNBYVT?R>Eeqko%>)`;|Zn>E8tn3`(g(O7SiQ00Pa+b zZ(P-O?(bV%5}`217MkM{w!{3%Vtm#R8^#FD`HSr`FwG%`mOh8M<;QiWtVS?iAQaDG zzTkL-!hEYRSC#z9fAIviprh^Id-ey@t}Q}kRQXLL@|UdmzGejfZq`g&W4_)LjP%RH z?VK_y#tlP1SkO;kftZm$+dCgd(&@4@3RXS-93 z1~Ea0%p#Qja1#`ecg!{Q48j($a~Eany%*bcPUFvCzs)T zy?hB6}Sl@H})78zlr zdmRlsM^^gM+nF*^TksC6=itE3+KgEnViQTDN@kJ zxp?*Ax?H~4s3=>qiU1tyJzNF6y6FB{OuHwmr7Vcikki-CUpO{aPU9og#W&`Y4rjky z7*}sDC;O7^8V@>uC97#JB(Y9$q&b`&L%QSP@bfSyol3t*I=hHiA(pNk=48QFT%^^t1UIbg<%CgNRVX!*B}!GOML- zRxgs*mJK&@acQ2ilVNH8Lej4aH#!6A@r(j2d%4?TEamIf(;ROh%pB)RO%#9|TW%N> zvH!i_Sm01wcsl(Hp{H`OeThau1IIg&&T4^AUZeS4@!Yu(<~R~*?)SCmln&0CH@OrL z-86UZZN(<(SSmK@>_=!rj1FJS>Q#$Sz6OW`{@Lo#P-ov<9RXw&8XA_pPNx92Kyt`Z zxDm=CYCDm?o=!U40L6DaP9HiuT~yAU{zeeaK1b_VoCtiJp{L*PC`N|oJ|9GXLT zVN%i|?_3e*PoRJ~o)2;J&Cp*@=J9{W)!E8s@+bXQDWAiTjU3^48j^l{(w*yju}O27 z(;UfZP9hv2=4YAWYt}LqI zJYkWX0LE*pp3jxZoygs+CVj{>c^Fo+H28nYrSbs3i_M|0IT71dj>jvuTprGHDQtC0)8tB^@ecjhjVCe#tiBx6#0Z>a0H4_&a?#}+#n{p4f{s6&z`@q%fXY94u3k&oYjOv+wnBH z{=cNdTr>d?^7QlxH<lMX?i2F!NqKZ5q999i^(T%10ADVLq zmpwTi*W%p>M%Dw+UpXdyhK}bc_Bz*Dq!Y>)5M$j?b2_LaEQ21X#hu`i?a5Uu{mx(T z$CfE#J}CwY`YY+bOlN45er8^S!~2`&_~cod3}bWSj$fsmKy!fm zCpq94+UOZ5b3?<59XM+?NM|YMZX0^Td`@#{YdJ`l8OS{&FVmky++#A=zzs8fLoScg z-|@>N-2o^eGeG{l#yPhUj0R!gkUnp|b{6!$nbm7#wLf9n^JQb10S>2hF3g?F7qp$N zb63t^n^(1EHZd|^OhYzVQD*o*%r7a!U_M!Xh>%Hq^0jY_AQte4EWhCPGexLg#Wyr( zEX+waGU;;_p}5UKxC&Jn6~ZPO(0+M*M!@zfc8%p_mv?6$26#-|&jx2{EG*>L(Bg%N$ zXuM_w81gTzgxIip!i^fF0}68tJGuNYWQ(i_vCTq^?T(}mv>D5Gn)@Bo_(FujO@tzY zUr47X%vp^yFdt?`_7cHUcut3(cCJs3fE!t$z|vXMd7TgwBhFnZBB=$K_EJ>GZDyXG z*RBfl=aa58F@ju5`c}n@IF&TSoxwrGcQ+*++L%v&(rG?*hdFD}Vsq0fk?4k)&W*Ot z{ZpDVAII<(ZbrU6%z@zC#q-(_^KWE3P3TW6zI*n+m^O?ufjN>q*$8|}`tspME+oI6*W_6GGmxdur2~fZ^^eo}j^r8;V%TB? zo+2G?qt#GW`x54p^Ein-ermX3J4eeKaGO23YbSU6GU^aFY=OD_W1Oq=HGfe?R;1x$ z$(6AwkC}8J9slKXI`bF*GEiE(Z+zy9IY#i;JL_^O39$vsPD7?`o7I@Z(iuYUxjc1a zXVHJk@t)_!TEQ8M5z>vrYjj&7oo5Pj#tM_0OOMz>pDP*%8ls86ka-v*TgNn9rLb>k zh#2DK@i1qvk@1|>x7BMgN)eo^x$~;y(%)@o5B!|I8R)A)CqfUCAM_LB>MZHl z#a|+nT==uvn4-)vm;*Q8;22Md5jH+r_ygJLxJnS|w1R+f2xkIT;xqYR$NoM4BN zac~TVd`GD7jbnVuFH<&?k^HPxE}gfWOmk=PxU;V9Ea$@N$)r=Bh&hT-BN%GA#_^5u zohyXd2sgRK2oR$c55g9tJUC|z8AddY{i|BwPt4&pC;BIQ8^;LF@;j{XUvv{U%;#u> zV=#w+F*)L$2<9v`)F+*J?bhP-`4}rk{QFX=mA|M;2O2IC+T02;n6uqkzK5Ql$Fkkv zq{~0#vYHoG%y-TOh+!Q-3b|^%&mqHkpZ*e#17;4TrN?b|JpDEr@|uX&I@#(y)|pD4AeV$S8Yq2`bQ@08^wqmk%)Y;g?SWEHrPh^RjA zPjhndfvA|EoD!fc;4xr>(@7w8-H3x~PMn9ijw&3)3`H_wI}WgO#N<2cLr zMxZUtv4ShvE+JzYiMaPvw!fVI4sbl0(R`s~sbxl+tI_=8dkKwtF!w2+_{4DL2zH;W z_UU+NV+eeVLnWGXhhKa?*C)9!dBJhHz*$T-{%4bgtGrBqR)`Lt)b`hxvJTUH!)o8K z+PMs&IiIq0{w#$#RK>MFDG)vZ?(@a$r2}RB?gtdkg^baL5{)p_#OKhDq?1TnpTFo6 zPXO{-zx&dWZ1e+XIv9uTpGgzy(FC6jBFg+uXH4)79UKe?NBSB13ul}%XP>C(cbXfY zM1mekF7!ypUMTsK@1f*Kn)`9CF%?8Q5XY^!N|reFjZZ=`+z(3o5nm9Puiu9w3~e!> z@9FH92h-u_+dCt-)=Tr_tdaG zC^%@hmI!tZ{F9QndS|U#bHWMlt9kebdB*l`;xAezuLk zWbx55S$a_m#fKzU-nc#6{q>CM;7u(}oL*xo$8Oa%_g$@@OdL!JFHDDh^QQO`&gUg} zVu63x*r27EyWgMgW}!n2lqB;(PF{f-Q8N;-HVjqPH_nC1q#KrxD|(D#flUtZiN;r3O)cJSpvuz;Q$Ic2pbnnNJ2(Q z{gRI16$?8jHH-hjlGkLyQjBbKu}0nM09LIQc7ATJU73n zw7jyqw!X2st)r`_e`sWEa(Z@tae4K}=Fh!@4a0!>Stz6T zAb{$##y9u&X;zY9;3gIqkuLqy4{}q1s@7-=2~G#)`CDoYqi)^efCw-K2RzeK#AAZ& zL-3<$i4Mk15Quw=t9(}#rnLve(kNO){n<@fga#_=F*Q(V81xcMWK_k~|JzQK>t!-g zI^_#P6Kv|4LL495z&+}h2P{tyPvj^v2DZM5h{dvLq?UCEPR%qb=vPcA;$h1g8$dfX z8VfX3x&7!w-s$oy>E)fn%g@IaJE644)5swWj20^9FoARBP5#AD-eMbiElZli4Oe3zzhLM^&`H)^){ z?W9KFbz8V4x{k!#qV?dK1evh2e9w1GOc}=g<`~bBs zlYt)D$d##3MD3R9Cwm;f^7TL29dN;6$KvI1{-=40!x=Z|pnYiQ4yBDNkEcG4h6>U3 zPDiGmrE)CAw!a;nK6*^~hy61Jk5-FZW}uy%P9#76_RUL1K)|e4Q7LX3=p2{ZPQ5-HjQS0Qn`wFW8#$$SC`9gFeIG#}8Y=k{n6aB& zg3tKBHBUq;M14?&NDjaj8a`Ha`{sqaph?XZ?68#DfkDCJl!;q<$N+$yCfG-))W{pn zT*=dy6IbpyLEwg5gzJdMO1DQ>jIui`{U>~R;1fsXYt5&UBkd2Ef`kzrl11jxnxeCJ zgz6b4UzPsMw$hd-ibpqhHt@1Le)D?s>bTte5ADtp_=X?(%w!>S&XbW)KF)O{$AZ+_h@w?R#pwV->>jgNn&Nn<|p; z$R$@IFqySN!y^09r1S@g%?U^`$vg|AId031!NOFUvJB`(?Y$QFbOEJ9gd_yX5bOe=)|htc)9Qq zvOxDMkbK>MzZoKB*&HJLiHKHW(*Cydm0Zlh?cCl5HXKe@!sHup_oC~|K`F*HZ%5o0 z6*{6$2`WGOhq=lC&!kl8bt~@dOGyZ`2&KqH&|A{WQVc!Ys_1Ne4hknl9@`ZhERSip>_BQ3DbdNgoxCV{ zpDC(hE0Trd^^b4{@rK4ceg(xCVxuKgLw~CVBrIww1vttE5+x=ImQ*YkAv+yij1#H> zxfc@HwrGxk$&xsyfxqLYX7yAyF;>#*Jfh-s$1|yxrg`(Boj^)=fGlK@xrnTqRf%R| zyH8ZS?i*&64byHd1g&~uI0`>O)243kF%!FggJSeb!Xq9C*;l4M`S+fsp-wgcZG-v?+hi$6v) zmGLuC{GbJ^{5;fu7@8P(r0wDIA{Py*8S|0mI@J52QLr1})CT`8$|pn7Y(`LSn>5kE z$257^Q2e8cLMK7JG`=5=6)D*OJvxF$iISQyM z(bRgaY2Q)tBOYL~uK*N0@x=#DiU7;#@i#W%a;jn`Iwv?9#Zmc=-B}5>G6zR( zu-j}g20YdYjj|_uI*JT1Q1PyEPDwS9eHt<)6`vU`aA(h| z(6Whev3MVS{Wz84rooQpm1dID^AV?WBdcQ*PT}JsM#%ZqR^O;BrIx0&HJ#ciy-BNWJ80%Ow3ZcPvRf`H#qSHX!tMX?6|G+? z(R*N^y5#SeE$SA3WW;a+_tzj4(+AZA8BAT_tl4==H~Pau!2H4kAHhovU|(=A->@C6 z1z?6e!L~*HEzbF{WpB%l*5)y_j6?qN#c2v}gh{HwLh;K!^FHU&lUR;#mQC{I zrXPH?qwJ~aFhX%-yoh9Q$0wVWa+-*W@F0fc8@Avvsrc}zE6d^x?X}){*h|{J!A9_9 zX43?{nolyz@%Q{OpXr-BYeFk{((bYRuCTVY((Z4HPEdj1v~JA%5bJA$u+Nbc`pqmj z?a4>I|0WOhNg~IZQ#;1O;P_Y>r&7dmGcs-{Z5QPS z4XWFUV|@-Y@_|+y7`;&3tS<24OA#-!`^V(zeTJN78!KX9=$BCZw)avzh4T99;?cZnn<`BUu%n=*IA2e2u=@<`V6LZaOr<465`svh<>Nki1_2c9yGZt z>?@6uS_&H(;+ge5!|t8#!UGf4k$L56uLb=p^oQzuXd6P~5T2ps&q3hvSJ;5W4**SW)*yPdklyCo2G?_3O0%3Ytt8h2h-=s z`2mjwLNmDZd15+`sz*eMzGRCU?^7KkCO&z_WBoko2oAH%H{Y-)AJ{)m#zz-X9E;!=F4)M4$iKB=hm+I+Nz zmo)fjU(~;cqbyD6t62C^1KLr+`V3u$4cm;U`gVgo+4h-J<;vz^vM)A$!XJ-D068}B()sVSSs6x zkme6RC2zZrYI%Kkr}&oeUDd}xiK}3zg4YTgo!QZqYlxezix?0^a_G8ZqP%aY;DVB3 z*a)qKcQPYqVs)^7ozV+I!L&ov{v4Xa!6zB`k67O6o|LlbaMd{zDKk3?;%?%SoPU)P6#l(|Sh>XY7^1Nxl7HTsiAwQR1R%{`=V3P}$dF z>|SO$%IcNhXaxT9c5{03!MRhj?Y0XT@d9$kCRuKTVVrr~6r({(zJz6*~sq)Z`=dYrXKbFx$7{ig05wRL6{XdV} z1-zGwzUTB9eOV#uIl8{{kI1Lv)dqbV{i{<~YeF$D_qy2Fg}%)-B7KpquwefZ2~Bw1 zv3|;da+-?GIGO)gO6@f= z4tL>(_`3rGvNw(@qxhC_3K`5XU$JTW!MU$ay55q1rC|1d3kITG9YlFCed zSt3i_Mtj${*?2Tg0Z-9G^-2lUqMz~UBWV97dY+z5XnjzwD6ga2##5(4QIC9J#m-RB zS5~TDHmaKcWZ=KU4he>mZTRh2iSpJJo6^m?)BunCyU|Iu2TQ>V$KWm;J~Y%-B~~8{ zSY#+J`?pZlBxDZcIW5p2D?URd*9)STCdF#H0jng*`v~9rH)2qnhqe8TXgSgRb1&FGU=^*V9y1Ds zu2kw83T}oOk9PRzyYy~8q1_wE>yAvhNn zA8MsQ(4GCQx{<#IE_(n}t91I>_$hF%o+0hRSz`I|mLDpclZ8SS)F* zS>|l^(6|wA?IoiMq3#mC?jJE98^NbSu!9P!VSJCz(F2zF2d2g9Y8|t$@)R|>o&~w* z<#hpwph>^g{+*#@Id(B#-NVX*<`)<6+)up{Z$c5&$D>qK5S;E17r-nPs0yDoEprdW=yBa$5~pM>=yA z^FlzP#a5^+gh)Opq15usc#LM%255pQggcYR8DJ*~3amLo$%z~ztT=m7eLB1rl;xwy zAPT*>W%*R0r}T8>N`{+aZmEWr7Me(J@}^hV;&;G46S2W{QCVk1`x5oGLbHcGzJC&%1R(5aP=M>@*H>Mqd8 z{`(#Ay!4lPpI^@@U!>Ck+I;}rI@83ub+cVU*$ekj25=7t>V??2c*3U$q#i3k)u!v_ zwJ>*POy$d4SNxy1PQEb3d@N=)q<#~q$Hypgi8 zwDiyxdf^>3fyW!+U?FA|i<#tKx7iz3mTzP~NhBUiv-9h2yO;AQ|Lybkuj4$zNT06= z8TgwLGd%$DFl!CKFbd2ymjWS#1K6ziQ(XXI!&^DCMHuZk+d*413>5_cP?JiPs(vV| z;=`!>ibPGQCLN17UD(nEK>h0MlfxOccjg6@a63f18f#(50}mHkZb{SRxG3e zSQZyO5>kXz8Dv}w%@WL{D#gVtkA<1V&R#!`YTY=>^!w7}7`41f;}9sN3zfCI_&991 zCmda5ACRhg_Ssm;w3=ykUb!qQf0Px!rDslS;gfzXJ3d>2Bw?rV+!x;p?vL@~SDAy} zsbgx2q&%D$9B^VHwTuWcXlhm9EwT0d8v9~>_gRB6FYv5pR{%S zL~ADyqMrZV55)c3rs$CW-W*v4F+YO^@~mOQ05!h(&9+^xH~zG=o!qeqUz%K1GkZ&VaRL;N1X~_597b(J7%JbX84pxx-Qz zlTZmKBzP#{KFXxUvjL=GdSFtL$<9nr(DNBmBV@@zD<|%#zmNHuFx3qN#V4a_A=%7seIwcsb*jKR&IBLH>e2Fq}1qPgg>e6$l`ZCAw~ zk<6nr#FsT!pc!WP#EG#kk{gE9U|7@TX6Tos_M!b7aS3IWn7oM%6Cc(#m3N+7&3)jG z?*=Z_-^Ry={hL*i{Fbv!=Sl7#=l7?E1;=;S7UG<5Wolbv6NS>(cXY)ZixXw!h3@qL zR7C(4&X$d74*PYGyZjYSEAgkB41l?-D`mP2;S zWmqd{H3}YtNJ?%$H6--9$&>o;ttCEP!kyEKNrK)Q)J(GZv~8Md`2Bkvz6zf3siV1h z^OwIphyeA%LKpxT0D!SoWingu!X}4OxoVv|=+u{@6=kx%mU}AD;cx^WSyU1_aK@?Q z_=@GFSNm|r80ki* zO1hqf0c&mmCEZx}L5|AbYC^ftfnETYq15EG%;`{t;5fJ|hUV^k#VS{A6!~s0NeUkW zaSPo7Fr-K#SIWD}d3ueDNC?Bu%Z=pMaIqL4;LP{3dBl2BMTigpy4BAe`mR-dLJnSzKTfc?? znwV)3#2QL3DtDs}J~AmZqz@^Yfe?omgjH^vN{c;>1xwXE6{wgN4mMPhE-YIk^3KC7 zpT55n?p;`_Wsue-55*?56B2!=3R#ui>Qq4)!SBL~Mhdx?FMIU;X4tSX-I)qbR8k=WF!}2TM*{Try$iKQyoI3|6Oj6_bA1h?OunW!}v& zZ5Z*DC7&=qNS4`4oJ?kC?eFpLm2Q%XTcwMoX0fW86}sOvvaiC?;9XH){A97abWF*5R8{zF(d2 zx5oSVO!ZV`cg^&s{Y<}K#6N*Ng23q9$nwWF-9r%sdJO2P_Rg}-JalT_)t?oj6q|Vl zZscDZ__6$M&|C;!;Iv|?72304cPtxtZwi0m!#NpxM!hA28;-wRDh*3?H$BW#4Be;<|jXwe$-y z#y8x%2|lg2o}?JOqddzDD_JdoB4+ktkARb=s7o%;3KO{xmDMkiT2O$?1xZFr4)CdV0jm(6*j^jSH_V^cfiN zudzpBhf#kC&e@uMd9|Wsa5nN0{B?0VyokWl-)#Oqz1zWy)dZtNu^E|@jLd^pDCr;yCjX3AQM+m%ss(tl@1 zwLmk2zrJ$8Z@F>caG*>z7e>(uAa1ZwNFLkb;S@HHrNU2;=^Jq!!aOW^X!cj{AxHcN zJ5B(_MoRxdiSphc*74kYP)J=)NAEckw(UP11^rG8Dut$pg;ZZiy$&8{4sn<>2at!j z+nx3SS^C6=>kFjwtsyn zq$;zn4wU9-QBD`;O!2=l)|8gGgWI22+$`tp`_`tz0G?zJ@8o$!GE-_evMm%k-WQEw z9cKXoI5HM)Bs7E2DBihg&rSBMJcU^?QB+0izL~1Oavccpx)%}-1$%mMKfPSLQu{>- zZqR?$Np8g6zk8hc_w=hm%Os*k-bppLMGYxwPLNBM%Q*U#61CF(!1LJzl2oYh^C*~` z6Z037DT1YEf4985D%X4D+zH=-KrL;#@vU(4hoqJ5t>yMMg^zN+)_UKrdk-o*;*&@$ zwOESpTMY>IAr3$Awm)$HflkgB16u%i%a-`zN0_*U1eA4IShviZ$t z5hSoKDpIUij^YllhKJ<}CfOc>P0QG;Q_|wKwONWCuf`UB(B*WD*wL?#F2W(a)QTe@ z>Oe9F^o#=r0Ev2b{QKkWi}l_Fu43)iut@)+S<0wu0N{xWtW?2CEsqdmk^ZME4Cb_$Mk2 zG&(b1;G>kQ)F^f3!}DOG#=kf6#cx=YO1sc5t7k7z;SKS32k|NENvbqG+&~ctwFf77 zx8ta9<1_34U2VYc;SA4S`T&0U75I`4@cYU1OK%6q!9YAZ ztb{`)4*-bW~E^`>27GJtPvu~8t4c`srfHJL@<LnM9A%D7kiB7Wr-ha`(fKvK@{z{#F%jMm00(z<(#_K-`{nETRE|bA=_tLx{>*!fv_%oN5C!Wt+)4E!v(f&+6bs%BVQ{RJA%}kh!)f;+1(Q7z)5FeKW0CKX@H;4c{ zY$LQ}p|k;(g;8-I{YbY%?pC{hPw-*U&p!k;8;#Fs+CZr!bi$Ghma+|eLll^)20N4; zqXW(Nsuc+{19Wu63`$D|%n&=@5&(xQYqN8FHsJZE=^%xx)5uDKgCaNo%iCyBUwp2m z!dBy*Gotif_yjgur`ayWMK#r8DHK6ygBa12`0HFY7hoG?cjaQPg=KGaVx^)QwgR@- z>;wf@YLO?Cp{C-&+#}Gl-aUtw9(>VDlCM@dnke!^dYVxX=Ej~2E zxKX_0QE0936mg}Os9)zOtxwSI9-6`k0BYo&Cg&mOsoCf#Iq~~{CrlriGcb2^s?3i} z!GqzHb~7rXvFR&{d6|A1j5AW)Y-98Hc8YtE^-SVA49N>?QEXPe^xUenapdX|7;VLY z_SD&QyqGU%!y1xHrZG!&1rNLdfDS-1an6dy`q&U&l8s&Bl0l7BPlr}7Xz7Lv)a-|_ zEWm~aqxfidU~gI5Y2eIA-}ToCubA<_gt^jgh$M zM|VYi)v!0iuVadlGKgh{BFBIGXWLf0+FG>N3-~lGIJ;?17xAY~&;Z?6oX)qnw=A2B#&>zh&_#ZvIY(4inJHKl}$(8URIw>G@RU zR<@4Isk1H(QFnQ+rDaYQzN~Cw^@*pp2IJ0oBbjEsSLwd1A0aI^NvixB6Cn>F56>P-{Nsb~#Qh?IESJvCYn!ZsJb2*(bxn`@ zGJMu4>XS8b8;&A7+EYG|dW$8&6M0G|x~7%%IAV>rm5M@ox4{Ce^IGoCd(peOiGal zY*{FALc&AN$RFbIURPWUChZc4AMx{-DWjcXq6GcPvl%KPSLoAD&Lor@ndZ-Wgkvc) zLawlk+KD2WRl9jekSnFc2>z(M6P(m6ANAEq0Kzgcr}aZ8y)&}%<4eO3DR zETl!AZ?vl0Ue7W)n#s;KeSC;ueB|dWNQ>z*O@7h*a~{w2-O3oRfUWS-7ul#l@n6j= zH8S2%VQ8-@^aVhU&HEXL^%P3$ z^yo>(?@rO1zyJm~k`e+D9;92d4zUen42EQ0>Az=DAztbU3!zR&z`y_@5*IF zjTGp-7GyC{8=uN3C!(@W(!)wmJagxYP&^(y@Qe(GS&zHpSv4#JL~rr1Gs@t1utVE# ziAMc=PFBVcxwWB#BXDvma7ul^T_V4Sd|)94DGp5(!|;5=(L+qlnlnq>^mTTwE~|Y5 zk12H8cv#=7L39#%jxDHv91fU~3=PKxNvfhWd-^x=E;m(ahRA1S{cr{!c8unf)7KPG zyBLP~(ifaS{fCk8NFxC7wIX!g2jS50zvUPwb#D3O*jJ@wWZJ$uqucU6vtm*fzBhN? zu{*M*&P>>4{m5pzdi_{;F}gR^#ts79G~-D4i#WijW-Lwi^dkS zID{_Hk>H>tQ>nPU$AMEcj!#E=@>eU1SMMlF%_3qdXFB^MAY4NXziGdHmFYJz&n)Wc zlhOl6=(sbJR!0btxCNJx z6m>0Gb730{Ld9rb)WyX?CbnfSWJ?Uc77x)}P`%O=6Q#Y;bVv}+<@(O)+_*0kr6oBO z9#=Mnnvf8LFB1^1twNn=CO$wO2vKLN@N9ML$Zqy?wBJo%5FJ$xAlkm8=-5p9=Mwy znv!@bu5*8V!_ZNY4hQWxAEwGE5|qHiZ*=KHF(v3UtKU6~lFx<+fC4vIc(A2vw`ILA zw4mq1x6ykh`5qQQpB%-yaHs(tLNH^;=>)Q^J=O|Rq-sqc7~g`Oosd5voWtUmrNLVf z4-GOJ0e7difuldDU*qvqEDf+;JJ0++j$z)cGz4kJY*U+5>RQU;!Fp4Mn%+Ka3d7yyzK> zeb`n7!&N%`CiujI4j>Z9NH3oUgTY6xc^4Tk1W64q=H}9I z_r6M=3AV1yI972Q)K{Zlr&1~ zm_LV5EfRpn_va#s-NNoPe;y+0;L>t+{#iS4=DdkcM(}6n%zX~dk*cTstxDWjK3)T= zG?oyiL2t)?xvsRdo}SDX?&DFp(N)?UsIdIxw%81NP4hoThi65S2Po(~AApAS%UjEu z+d4@SMtL4d>U*J~dyt-B{dM9k$9YR;>+p?%bT`X12(h550>rDzc^ zTxZRw2BvY)I=MunE#+Vpt4W}Sxa{s|Z=Xg}tN8nGRcKvRJLO=_!Y6Rdr^~~xW?Mcx z(Bw@$50yBNYLVyg(JhaPw#87WMGDxn5RvMVzN0`q;XSLppqQ|Y<6hz4gcyjQ%3oP^ za_hz@Y{yd3-dD>dDCed7i9mdrzY-0j=bMqe>Bb_JXUXwX&|%FqGRs%C%`3ANoh!Ok z^#Va+QrWd~6bQWa;t59gW%TSTK7D>=8?u8100gR%7%iz+H-HCIebgUEYa36G0q#ir zU-~Hgve_XNsz}V?l{UzdqYXTs4QI;3;nKw4N@iZIi;JfAxLsasoc#WR+iwlj9>W z$N^XTaNh@a)vyO9F2&@&5mCX#NfCl8tRxB-xT*R{Ntc6Myec_uRYY0Qg{!q}d4{Y? zZO~UMzIlVKmd(fy>DGd;CkmA~{L@&L)^9zDotvs{Z&)&zegDe@b{Gnq(vr)wHD+MH zI#Yv#WZ2CjclRBJT*A!!Y>k16ZwuSG%NDIn8=iiK!vMT$P{!tuaO*~UPhQW+A|-RT zU-&L6h!2?XnZMi9f9IjRAv|oO8}hcRC&BSg|E3pMpdIdPfZN@o zVnN>qtF;1X+^JKR#JVN`ASbE+$S0F?4ouHw)S;KR#p);YxG0%gVO8i63+?&M;lS8 zjGvCUDddusxRL8$bw7Z2A_=ABA~Sf?!~cGy{6@M*@_UYZm@kV@yU|Wy^6=v^hok;q zLHt^z_Z^-y=^j4*-+SmaNJ*G_%x75{(Id)2 z3BQK?D*^x*j0wo0IXm}rbY{9e0QO`V(R0!Gakb<$k)$ENp6m@XI^U71=S?U@@|jKt zwGHV=M!6SrvDD1_CtAZxr09!}4wS9}W*Kw2$#d7pH1n*gXvAYi&p*?FQ{kreLirOF zt(fb~k4FrM=<=oP++tC+@j=DkV-%gQ=IQ@3PwC7x33bCwpOVW9lKoJG>MHgx>%J)5 zj`W=33wQ=Y&E!fqZ`clrss#ankBI*b;yoTc;9kruDd%YUUnXF}P=4wyBJBG>W{1MP zr#MI&1Ug11C&L!D&k}Gku9!u2&bs^??p4A!Oix_>maR70%8|Q-N)utYiXBDtsa!GB ztHYTiZBI=ImbvoT+#fEF=WtF;s+87YAzQ2BL*ADGE4DkNKk#Q#bElS!f~K`!Tk-1m z)cH12RIR9!!2q+EF$%;NlnWN#fv~A;?0kds_{(Vir_H=>HKn1m zJ46~aN5I?3E6xdD9TyrXhZ*Ve$ij5*$jcwo8uiv8EWlNza z|HkjP0Y@&UIfsSNJqoIGQEtU0V#iHwoml`!x;U-(;le#V>vOsye`y0N8L#311bGrU z+(>)k;aKChwxN8&o?p^CqUc3Wke*#`s)N(TYKic%>X^v1{0yItcuv!5GzalW<+?%! zxPGTzvrfjKzemp>0OX~?`3P5BiZVQIKpvN$r_@rhtRYnGd9)BC!nLPy^z}~NwVDV@ z?l17bJieOWa1;q*Z~Z}IW0r)t|J1`$nufCF)gFU%gW`Yui3thCMsX=nm9o?PD4@`q zK3?ro#ptxit;(V5Sga&bY=)l4hBrahy~Z2zy10Mq)g|u1Xk_*(O0vf2mMPLkUb7~E z-2_6eYcw}JWR+&c(8x%wXCY>4;|X=q-q(25@I@b8d!o!X+A7aVVhQVAAQBo9fm4K* zmCh>lz3VR~+UVtlW*O#=yjEGx1cRuB&{q@$FWCtiQTPh!s4lZT2gU4C01_Ksj(=A2 zWfT1GDK@N4hMF?a;xKpZc>|E3g- zx5){&`5#Z^;?A2^qbAXf3ypwaLRNQeHJ7eOlR>FrrMSn{#c&b7Df{!p8 z0E~2?ZU|RA;wAls^D`y1uP-)o{4~uy(^b#*UZs`S(Yp)eRnm! zNV*Hnb$?v)L(AfwB2ZmUp6y(vRSC#Hmf1pH{5RbxbJ@2VR&DqUMji&Y2WON^rfO3Z zj25X;+v@NnfiWSAeZIZv(_1r1O%Hx}713>=SYEM~&awKE+-y5Y$V4xh7ntN{N*uJCOcz4$bf17?>_4~ee3?(_8lNBSS2I7CYPNHwv z8xJZs{N~EfZqBFdug^{m5L!?5Le;;(>TODbh^Ge>$OI>yuC|=ut+=9T&_+~v0T}oVRSvk|6`BFF!Wl7+w$9&6fK3P z1#*$`>VTZe%~juEj9)#dkd!2ePl%{g7C^G%B96OUc&r@HIeHZ>{-phA3*@PRf}mP+ z=eiz|ud(H%2U%BRi>v=&&}DEBq)$NCtABuIz5e4*&}Z4$P&d$)>cIRM>w^uw93GN_ zQSI}L(|hKl$E*as7HS$ni(?Ano4O6i|eNJ|R2I{h|GtEnaRZ3zAzb+qM`o zDxz~v&D#-;4#eg({lNX$CMPiI+}zF!S5KqWOINzv>!3i!Fv4IcMmLOOL8+3*P~qxP4fOhmr^r%2-@pBE-% z8>|+ur2}Lg<7XWyaSR0&a0uQa-**8o(SVbrH~+y76+l@w)H!yh@;ApCpRutL(9-(P ztqHIwW_1cq!wV+Gq9vmy=CWvRzX95#5n{ zUax-D$^4YjX?Q9HK_>+UPmRi!WR%OZY{bjshtL>Ww&pNgAktfIR%PTnPZhj%Y-N3* z%;0?3K-_(2O_Fb0&ijo8NthM>w_m&{U0rsLM?ucRXN~UuYfQc&8gW;cb%#I<>1vH` zQy{_N=u*)#FY15(vr`5DenKS)gfpQ>JDNINc4~_8gYInARgj)y+eJm+`sRo}2r$08 z!=7Oer^#X^{J36WyeF%VBxC5M~lty@u`gHwd`-_YUujNeeTaLio zfGTE$zQ3sFJC^QH8LV?@XH`B5B~_vqJ;YvS=1xqEXhGt3Ul9m4(rqL6tH?EFu_>2*B%)7K+f6SRCj zve5I*_jroTLqK#CQn>21rUH?W6Js%j$`*p5Uq4k0_}w%gPdx zvzGj>Ox{90_ReJ|c`+$hjiGEKA_{-}OCrI5OhKVoZ=+JX96V*apzk+^z4OVt^=Yo_ z{Cx;3Wx)}VD&Z4HPTADVtD{M;Q!%X1ZtVyAj=nM#x|F{HPwyxSZK?v zexz@QSp0~O^4X^OEcev;{+W_KQYWRL$fXST0)|{%NYtxV%RFxU|0i|%H^pI zLWs28^3HVSV<^8>C*`loP|$v5s<3Pv^NM{`KW(+C&&TLzJL@@ykc?@U0;J^Kh(7n- z>zE2!Y65@QwzY~ON^paVCZvA9J8Dau>y;H#^w;WR_2a6S*p6)zLMv**9xaniP13HM zl)~?hJI6ErohjF|klUe0F40lvvjrq8eX8SEkoAtws zwiI7=|K-73XlJ%xwD;=q-z~L+3yG9%@xEoeNCHwms^4SO9d|?xByo{N(%(oyzWZEI zXKrF?I#!#38K$z`w{P>A-_vpFl#SJSV(_#8QsRSn2OrX;J;PEx<$;05N_W6uV zZuO&3e|iFLh-_<|JMk`nG=vdQ0Va@M9r(O#4MG2INX#9*tPM9qUdy#k8scUp7!okB z{H3Ox!WAuAx@-GUZrSNN6*Z)H3+R&d_ukeN)TI_kO-I1eukaw^HXE=GK% zR8B>$wlkTI#hz#4eOA4(sAzDS$@8!>u?GB86>!P(AUZ9|JvM)X*kTyV+<1jj|8&Q6 z^6^ZTc6R(pSn@%9W96J6S5;73r@g{cbH`F6q%r$WYe5pyYq2K-5K7Al!nk#n{N=p3 zOX=htju`fgJsl{$bz_ueST)Q^D#$hn`W@m~zuct`3Jv2I)2fqvfUBpAS*6$&s6CnJ zE9QRDclt$9R!_kPqa5-53BWpnr_s^{}GgX3$b=RGu zQF^4o=#5xjm1B}ge{PfWxqU6bGVsX2deEtCVHd7Low2ZKVorGDTqMYkL)MQ+qTyU! zkt)KLi9E*6m**_ght&0@7K)SVF3N?Q#rT&1Mu;}dtBfth>%t^E_^q)-DM^{I%Tjrv z+eKNWynKB=aJdG?8i))F{T9i6s7e|)fEC#GqPc^v<5NI~0*>JF_Jy~o0+`;w;JLrw zYX|F7p)=$Xcdz?)lR8jE5G5Ph^{}+sAR%O!jO7szmwSp$_)myo{!dG# z;bE_k3jY?_h-%7cND|GWgHTE4!uQZW`y2IY!;u&zErQrKsE4>gV^yWZ-T{SfgHxW# z?HYsy=WlTkgFa!MIE(Hi-jon=^5UE79- z9$@J1p@;77?i_}0h7L&qk?wA!q(NF5>6Gr06nN+qK?OnK8{hBO{<&w4z3#Q{xUO?m zGzUk6#&D-w5?C$$P5WMA+|rrcCqtU=-p)V#D)yE>wbBTB9xEw~H+F%={E?%nAIS$q!GvGGKW@`?Li8~R9^7jv3i4}^eG5;#~b{-r^ z;QO@-kM4U4GyX&z(B5>|(^}6$Cz_ni(&$aMDmgKRaCnz)h0Er`$f%?;@jCf`w-8RW zrbbbb-GpFcs6s4GvQ?%x8eWe1H@w$D zLW{{yW_rnza5QUc9ABIH?}ngn$cnXm&xmI`T{DlO@uK=-vjX1B{Uk;I6LZUy+ZS%u z7BNY1r9|(yp2-}ZQ6Ed2PpmLs+4C#6#X6l~*!VR=4Fhe(6HLf4W`*!ysIrHls39~? zY?O$pL;ip@Vj4?tq}Bjh-j86Y?y5*ODC#tgBrNj7Bpu;D%lqqiavyCi$b4il(#^c} z(uu0%5yp_XAkPF??o^+bd;^7yi)$G$CY0@3)+YP4W7?Rme-Wn`&())F8b#kw6^!lB zLVX|1xVjyo1Q+r~LurJGz$?j#sB79C7@3oj?i#iyi4Em-vk#QdAS@m|$`Ptno4Q4e z7+N-hezr`Yscl6P7`!W9RIl!4mO0RGXXQ$0vjZ^uZpCvnr;g|noEHF1>GKt;`7zhf zp`WNNDu%5RN1>TtT0I8-O+YeNOVR#Waim9D(QP{aN$g$Lh928K)LRocG3!z=*7h`O zvZRO)FpS1+8ve!vSzOqgSlZs$4*$UX*qfO5RP&xlV#@wiqg&Br(Y0g1T?Sd`QvtCv z<*V{SO>@5NIzj?If60xlL<*AJa{9Wi_DWE)DLR-O5N^6WkIrwWlvG|S5(x+MlLo!Q z;I}9)a%QAPPKOk#w2XN*t;C@|Sxe&0_*gqak;5f|VElzh zWzIl%f39EAlHMP_*AHZZCrpSqu`ZphZt{Hi~WIiVS2+AQQIJub6rDjGXx)Dw77Xqs9V>Wpp zRUomQw;mIR(_I=r0|s%KIdTtafr;EOi6v)Y7xZ{o zrF^aKMNxhDgp;Ba5mRLmNsX%4t`?qIXFavX{zas1mQ8&MJ`#odUl$c?k#BI6l$z1^ zY}%!KrZ56$c2A||Ok;Dt?F!ZNq3B2jAqwD-;mqjXG@6nULsMB+Ks{PlO$Ddir(H5a z@jBKQ9Q~8awSeE>=UxUzAq#2bEY8~-bC5vS^iiJq$t z-^6PUb{(gYP~25Vm4*uE(jCszzE+%i@8n$}9v;2M&*gYzZ|<`$Hhd&_YCxf!z){$0Ma}cB1g1_Kc%oFZ~y4 zMol-!wG?Lb!8J830AJ#{DJ?;+D*wY2C4r>1HT@(yI*6PwhApd@FN}!}*US!XYjiy1 zU{Tg_F-yFi{Km$KErG1y$1_^1t{3V=7}ilqIgGmPXBQ*3RbUyoke?uJrSOuyjWAb) z{K%1T!j>Y2_X;TaZ)cE=^$c!h^e-#1{lf41dUC>wWe1F5NJ1DQc{Jl zQH+m?VJt}}oxp8B@-uffss87y2mJ{Wvll0IT%0-j(x4dUpoCd32<1{nU9pAN3*W0C zdP0nQhFIL$Ce64dBXUlQ+hx^*@G@gf(YO^;!QT=AXl2RYi6Q0Gb|spiW}2 znaTcX{F5e|kf3MYHXAxe8|ckOC||#-8nF`m;EuCaXi~tyt!-G>j7RXVuY@RT(qQ_C zx%?-`8p==~6e=y_0<}xtDYc?{$M$zgp2O(Oe&k}P*<0jM*Ll4G?|6leTGZ!L*!VV* z0)`JV@tdNF1oL$_j2L>eaNqQ9fOZXfC(_`a4>8s;|Z78<13%f3*^0eFJRTrE99rsh}2pm&$I;Mhf^MPxd85CLohaJo07vW3<&UXE4XiZf0*J2%k>{(;8RZKEx1 zP1$gReps4-6ZS3$=S(2ZwLa#3B@N@Mt2W*4yOg)KH4~>QFA-)g6aO|~gi*2hpHY!2 zz@*|)(ms+*`^-kEk=NBGo)gmE3*v;7nc9oLMkBv*V&S)$W?~nVhqPnW=cgYR0P~WF zz7k3O%^5Kf$;%+JUZe3r+YJGXi%vW~g#f?rwb8tcwvO}8bo8%GNWG^pkk#@+FI z^jETE%$`p;9ZM(uUC$r}PkqSs!ph#9)Qn=VR=e;hV@k zTb#$kNPQGJ^-=3=C?;tBs^_VJ35ks<9lHY)~z;R=3+i_%Jin$4Jyf zx=Y9b-y;2bj8+S`M?&J0`S(2=V8r+KrxNVC6{N9on$Vp1I5o8jYiBzgB3Xpa5IGWs zDLUcXR7I9t>S%Cv>C9HxAi_KQRhZAJqD)=>heCD(RF~La>TRj~8@8FBvLCc=#ju#5 zm3j7C1Zht$Z^%82{VXn-p6y>|p7ZWszBi8rc4Y^^)cjxEaOI{Kh?V5RWRnVOmBAcd zuUB84(R6x1b+j1;TM{DUflBQ1n_Wikj0`Kz)BNRb5tG!yjE6dq#DBRGJpiRdfAtAM zb?%suKMx8a%~G zicF|ip5pOc+8O2>9RieGwgWp1LX*6+t{epD>lsDqQzBMhiL!YoW(=u%PY|6umE7nc zpcF0nRvh%Tn0gvJQck}Bl*)1F5c+C3Wiom!zw|*X*v3G$)%f7>%jxg@CgbM`p8zh) z@?NNd@Eg6kF>h()@3~p@dIItIvIY$(ql6`}j9q*%BgGdWeRUm*w3DncAv=0F z{%VeW<>h9Y%1;GzpLr{X6zs_{=5os669J|^*cFQH6WxaMl<8%w;LlNuMsZlacvc*h zgT`oltbFCLsnA%GnY@PS@=l^ZzoVma)w_j<(@)Jw>u%0-OrO_PF}t_i2i03Zoh8J?eK=_D3Y+AGcC&v4nt9F;<@q zx(rDfaa`4<7cF~+1AA7FtCC_uxHYn37KHPUJtG9XJXwV&!ogV6|3Vc7fZ}JQd%#Ay z4p%?da%L37^>S3ZB!5+!&v_P{mb`7escifo<5&$kuB?6uY9fQ|_|*uqTpbu49}-U? z*_?V#;L^ z1<4%}s>4~+Ztt)B6J?#d^w}ug3nG6NK@4n0duN9^6QuCd~OVzff9Se(8ozT6t90QROzRynMF~S zsbGrfU}KeSkfzh*5vQ7Le-%k_#X)tD#bn9%C>*wGB8clI&2#;n&_|U1JLquEd?Z!xsZH5c3Gd*uwJAXAy%uI5Vi0HZg@xC9j+r-5h1 zyas2Ys#7c}KsNsO5J(-e0DmH>)O3oYO;@=D&(Xt$pYBu)Dsc)c0F(!iDW&Ea`Be&5p*A>cq>z+x{6#&k05@{S^C--nK`} zLtImJy|uVoR@*2B^jfjJK4KJy;5 zF3kcvih@?`(h`b2#vR*kl?|;v=VZpf53hcFDAIRm2hOuQHp@6_zi&FfUvN?;OhwR-QP*rYEI7gnbdYQ+hUO=({8mzXfy zvFGo#&_w!z_M;WeHmJ{jN zKiR~EVVy6i_F2P}%7o#(wnvv(6kgFY8+u-3g;^{E-y}FHAAa~ z5wR1=oT!{M6Qh zSg7CFV&>1=Fv7Ilp=y`89`O;x&cPYUT6sy%JxaDswkJ`h$AZEtIe1keTsZ8pfw|*j z60h0Mwmf_oecl&yY5%7A7&=pHL(~U4DwOLL)~aDLn$GinhiPKUp5p_7U*AmTU}JYB zD=yqnvFOJ0xNBC_etUVkndB7toi|}13E(nPyEef)twx`2koOuf^JMX1I*;Q=I}uvQ zmzt?kw5}sLq5r6xD&Rm|En6BfpRZ#pGnYPMFD$^JrNv<@246+MBN|>i4z1uV#wB9X z=_!VYY7ZXfIyDVA>hle>49N@Z*W13xu4x_wtjSVl0Xat(whpagNHQcce)LRUq?#@Gal|L3anbN?hvkM99nM}%Ctz&?;EC%wj8JyAD)>*nd z;8^cdXzyoo5zSXj8Lvsyz!Ie-YRbG9rxi0eZ5BST3OitCj zHcVp`X$7nVHp2$qs0`vpM(>a!3RVQrva5Y$RukDJ&zOHh2%FG#!H~7451(84bW@IRi9R5VfN1Kq8b-6&IppC_!imsGd z4D}!KZ+-NC31F5_W=tQgiI%`P;y`UEd*jDQ07g=b*Zm8jlvR?&**mcgB?qu`y%&c! zE`95<_*<15b8%Zy=tUPLli_KrTsO9~6TfW!)$yDuVG%hpt-VJ;{e){izg=4M_-W%I zy0m{ouLtj-WGM01z?FlG77;{<&cn?j7Dd%1n;BnAHb^q=)P+8_-QiH6e@~?JAkGd z74<`c|39rXJ`~wYF@q=^8wsK%4Lc{@Z0uP)AMeVxrhI`DrTio*dF@q~Ib!}~pkFTE z_wlh%)eLY9RPhsiro3JpDqFxHCl}RfJo`I1Xw3$n_!3FOJi{@Zm*U?_Kscrb4D}E_ z`Ue-6DK4)_4m=Vgqf3O7G?VqbFDzMUl}(60seI;`P))+lk`EjF!SL<_W&s)HE>Yx( zmTA1Y<{1`4El^jSZd5R=oaoTCuDP}$nRt6Bmff3jtd*q#eySctElW_PU2?83=lYHz zxj&5_8IqOmIhV%wUzh4#!%lq(1*M?<=la z4+YYzZ8xk5war0pyioD#xMw_Pt&|;M?rWcXB_GKw?Aj(XIPFX_HXC zL%E!mMq3)w3xp56K^cG?K*E<4XFJTFgirdZiK8!*|CAKni^)^7pHYpwPU$}_dVk@V z91i;Lgp#dHFAH%34{0TKq?w+|ZlVHwYp2m^f_&OICChOasBotk` zJNLFRwHlG-=mD|&9O`V0-H_^ZlTY>4KJI@%VZRqbuf5}cw80vd5QTH(Jfbz18%i;J zy#Mk5Ul5|k|CP=zkWZ2SR$6ol@|v7pCf#<(W~4~h1Z*w8L)1=kP~QDVKPBxkf4kAd zit#^HR}^V2iU&dzeN{OoUt7;G~u(6 zs1{wp=BvZlhNLy(d~K_p)2Aqn2ceD~(iAaBqk1`08%ovdtGpLcRQy-gu!Nx?I15jj zl%me(k20<(*tn~DD)oCqH7RwF_8d@Q`=q(7x&Ra55`K(b|LbPT9G5YBy-I$EO5z5N zrN$8=?6QjhzIbLf)i_zEEw$wqCC7NLFbb@XRXbI$Em_X6bL_+5KMj|jTYc6SqHXks zmsfFDRCRx{3OLpy3}-dbwcRFE{IRs+%4p^biXK?8I z^@iI<-ZJWH%}2un_G!Tn%WStC^eI|D5`>p!E+%%53e4;CrUr^cT+S#2D2X)jMy@SY-yB0^5FR-xT#J2uU{!Wftz*!D9&3kW1%UY?G~vfL??Zgqm+Xm4q!X zdRkP)83a@KP>v1$xSv57AUL;a@ipujPy%GXI%#x2VrzFNwagMD&4S{Wqq~=>e0?8q zw@dfyh7;2|-8ls=(Ql~LnMBu2sy~Y_H$9mUlU;wz=t&AW6xpRHgyL`TpZ2rDWXatb z0JL`%*QqZ-qDqby=|W((mpD&fL~mr9Al;whxkNs%SP;hEnjvk59ctoX)G{bAdnn0W zkLPD(r8=@RD&_M2pnx{WuY5f{AD~ejleCBa^id$&%>2*ynCjxVEq+zHL8PDknXS9l zDP*rFCVm_`&i`Gs`r*S^z>;Qc7!y>jVoJ0pj8l<pz$;fXuZaO%={Ual?YV(c6;P)0W|vJ z^}=l#bfGvxuZ<7yxN}+#xirh?=Y+)=+$j^kuR(hPx=8kNJ$^D%e@^sm=aBtbuFHH% z%H@z#(Ncsxp!3s1_a zpDX#S6Zj?>PF!_RGk1E3jG7DCw@A5@#Hc|k=N-S>T<634ZF&E*3Pt?n0vDawg!U$c zO#r~CY-x*`VFS?q5K#gVMI&a@rjpl`HJ_=WOp-;`qy;SdJ@{VA8*}zH1C;P8hIz5m zO5h%}|LLb3DGN_0VUzZq1|?JmKmil0Ss8It;bcVDXio zHJ0spKG#(olGa}lALhtyiF0!pMwrLLnWA3HRLYhrBTN?uPdS7_5#MZeFa&ZzS6G?7_um{cmc(V#`m zcPtp4m$Q9?9Vv408&*nmKIC=F8)y_zP*vP10GWHHzrExlcyAAH8o!r*#y_k1?fp(3}v6v+bCz<<$+&r$L>EkHI`5vD)jWK zXmlyx3*jni|L`^7crfCG4=*r}S4yz8WYR zqxG?Oz$9&DFMQs1oUUA5jeKfDL2svvd}7h~u4B_?<-)YOqzNtJLPlC_bO6|R5oLjx z=xB}*3o9W2fVPN_HGne2NuLO|jr$7|jrebyAxti^K#77m)be_2C%uL=`VKb`3N{r} z;?kUcTL`)CBK6W>=EEjQxXD_g+i9%0>=O6ZFrLp^Di@>g_Z|I2|BGg%45`GV02}pf-%W4iY&Wm9;({#QzOR$dvChzG<%u*aTq<4TJLnP%@#0h?QV$ zeqK9oT`q$gj?h;kS}O6?7gE_tM{gFUCr~;A+gm`!ovDYy#w`)869XpPBjd&irF;POo>PrSzEmJ1%D@ySK(Rrcy zusp@s*0l~2)26wC){W?8^!OoOa$J5(r`)^ z9B;G&+fp-%_)wNaFG~p=G}N_Qh928$nH^(DjZ_#Id7+P%O2TARH(~f2z`FaFu)U=R zFyl%~OH#{31hmOUeKj{e({IMtJO0n@@s;FBmhjSHlzPS*U#AyV!rL{4s!<(UqR;I> z5<<>au?lZRSYW0;*_##JZ5Q8dQzn6Cm}*kuzzChzk(LwS|IL752=vJ66T&WUgE0QU z8LhM>cVL&7qi5~~bqEwNjTZsfvBv{#(Q&Y1wt}WfQN`F6)sQz$#647DF_4jwn&H-& zY<*kOo{A~k&rQk&lGBtHJa4sMx}sk54Pz$h;o+~0N}_9*rM;N<*`uFsFSoTt4ZYaT zDuB4;!w~y&IMD54W|EkpNie7B_wnnXu{R`tHAEny?(|C*T^)bTzY~%XfRd{Zb9JINwQy0N zw?PrnE=9PpbG=F<+0XwX*qAulOYhsV#Foud5j{u+x49+e`g6ar0NM16Ylq1)%JS}( zidSPC%UbW`QUCp4$XA^{x)U93)PZSpVN=Po5zkE(uW zm0d3k2Fx(-%d~jm>P!3<#!vxyQB{{?Z-u}W1se_C*K{rAG6XnUxtpJl#jI481qkpD z>i_`NDu$F@i=kvx$zfg;EE3Of)DPz73{<$srX1-dGD7A4R)I>)PRd2~GPasyWqBH7 zm(O;n8fRRo|Dl)TTm69h(oT}s&wS)F6l8=e+B=%|*Lsifv(`GPS8_2k<`F>UX$n^b zZN3BlMm0W?=^dBzFG9o6L2mXWh&={8M4}zm(k?$xK&*ZG-HmoibY#-uONHlO&C~-h z<7^-0sJXk=ucSt}BBQT~dXC#@+;_BHremh?BilmYsTXnD+_A%Dlz1~CBPkaDEuu_R z7}w~SVW@Qh0ATU}iw?6na@CpeG_Y-KQWANEPm(QKrsMxkNC3(nf`sOVP|#o_NN5Kn zc&pAj*9d_|Q3KoGW}HKS6&v7l7>@T~ve$|mx9Pz+hF#tGTO{+f_Ei>rCqkc-vkTjVUh zz4x2-foBZC@qmH}kJdCM9(nagz;5E)iq#fS8$Z!jw~*REov;*j3yVx560vjvQBvMuV}ouqv$1t5DHz& z%a$}AFKeUgG0cAXv9=rfH(h>kQ@a1Ev+l$pFUw|?nLjGPl#3?7UTTgE_I{mW3Yt!V zmDcVEIfTDwjK(NcSUe{cOcq-P&1t6R|;hVv#>KMNzrqPrc%KeFb5Jh@HVKK2>yeycn9%}mTyo@rBPLh*Ov#_&l5110fa_RbOR5Mq1)XU^8lk0ewO5;^a3HVCV;pclVx zqZ<`sYu-FQ%{dYoU40Dt%!U_RYRwl&1f&@J)r#1()8N!$C^d#F|4vAU^G$>^D;76f z!07VQz9x&~9MRM69jp`2GR!`ppba!IK1^DEMrAU?!KM)kDClJ{b8f_z7t3N}g6rE0 zIV^nniV+d(kTwGmf@x&hqx0+9(R>2P=Y~=4c$^S={tn0VECj2njpJ03i50Xa`ML3ZUnwQ5M8-H+bU05qpx3U+XIP*tB}ygG07hlkzTs06-ao!JeEcPt8XkcYPV-wg2iM z&?D|OF0~g@?~(-b5Oyy@0oKh+yhBt-77`gBigJi(oXwQ=;NEq%N9{d8h}_c;BSY_W zLoxbLz*5z0meoQ?0G}zMSK|E@#n@Z9I6j2$eA)PSH9hH|Wx{?*XB9X82TJ0|h>Y|! zZ;;*(UO8~nkNvh7;K#2yn7r0y&4==Vig^prUI4Pq=<>kd2T2PiUU&*$8wOYBsuP~p z+}+0I(?^vciFR%yp3%`)9?Rdf=kw38vf18$d|*t%)E>$l9#2Pc2;f6>AR#TKA5vGf zaukpxI7pDY?8||aMp1_Rnau!Fe&SRFmwO5&{ctiIP@AK z1r`%$H0h{d_sQ1tvn~@VC^DKcP6L}=Wpm`%`VRHhOBO&}9rJbfVu)(b4u<4ZdrO(F zY}z4}cJzU6M!db_&wS{B`le-^eY%lI56H1-V4na+RmnTCv{IA(T15hPJC7t8Cuxi4 z?(*s7Wp}TNH9CtCjWO_Dkr~h&z=aTjZ+S}ld6P3L#_QLQuJUbyjt)R1`hu*l%`lW9 zm4v|b3R4mJ!q49oGx_QHy&v?o<+zOa&U^C^J+CG>9ROt*wt9uI(^_G^nQs6gS*k^< zbY0@hk3dfTF^3u4r`^x85`|kEzv|^s?A-t4 z*+bdqA@sA`M4MmX@3o+8G1Z8)>9j6g#v#)JsUKM6cC<}WG;#(-Qy;11k4!(Ru@aiP z5os05I3)}yATCFeEAiwt8Q^(Uo)$KakE+QNdq|DPf;#xgx*w|w7+d1<$5dIhWF4+6 z$U-~_a`(h^V2W6?jzO=&lU292Mk+}Iq9O2_nwWgmaHSab)|{lFm0%2Ivppb57mQ3} zMv6&3W+EmN=MHX!y|`l)fAi~e(PdEpGRs+?=N9XLL%OkbN7$`G+v4V(_gi<~hl6%{ z|HoUQQ{nQYzk`7?9dsAz7oFGRK1oM)ZFl@V&|fv}S|J!c0)+9F9|yWfR&kg~z8*Z- zS(#Nfyptf#YYhz=@#9(i24dRYVWuVVH?u;tA6{Pm?#le{DM9ip4AF#O2BC9jYFsso zCScf7%OgLF7Vc&PGc5B8p{ddws` z3~U7LEHs; zocQ%VH&K3;W8r}+1K%Z;ZeWBSbB}zkldkC3fL{cMn^~vO<`0i)Nz>bmC zbIAVhyOV3zUj>s7S3&z9PD1~DwL7>d`Cf6|6qXHp$X`j_<#~L(x7-J#MrH3 zIUHOz+aaDmkP!E$?ZSl2nS+M(6X_B`#5ctIo~n?qd4-0_$VWPlp9!f)N-|zOiSShpl2S+RHQQEdUV3Qo_dc z4x!FhZLrDc`_I%_^Ur2yw$OIr{!V)RLr8x}(oR8XRohu-FaoR_VV8slv_H0=7)!9C z?>@XM;EfPTUqM4sy>o8OVyK*&@1wOOlA1+>elXH)sd#xA%w`85!5A4FUqX z5f0l?nsW#L4c`PFw`__fV?g&kx3NIPGzm?Q|5gG?7|MHn!yr|vH&zMVh}tgje}#03 z-hKw#d2l_L`m`3hhHkp;&NF&vK3_ydqg(h^kz6iaVMbMv*MpN^JOM+~AE+!q@r8ep zKx>Ty8aYEqygKQhwB~5eNR;WYMF|tcYEoM!jx+|Kilwm*JbC(}#xO??WL=v47rirZ z6ATjbPWeyo)l)Og^K_2QRNr0dZkrJZ;>DWHDn;MTn}>p;ATTlYE}5jX^z!$KIYpl8 z*cBrqJ&E-~ZYJ}*sA7F2TPoV5Twff3qb9@XSXDk#WfjY<67A}wNP6H+duD0Ff42KjBg9g z--m4RyrKN^=~w$t$EdjOgtK=`r@G96sWaE~IUUnx%}*p4KI!$<)fFhX1(*e|m_=oYanaY4$|7pdtGT8j-cEBe{I zNqH2x;W%Z)x$d{@DA;~bd{u{4=bo?`iWmhT5=QAj=w`*z}Q1yDgo4<%9*uqlUsJ~FZEJtp(!LJ z9W*SzJ@Hq-bwpSvlvlJ|5ep!I;M*eCnZCIUqB>WVl#l zPsDvoHFi8^HYa#_aV$8j)e=mUGM2`N<-b8w*n{H)KvJ?(?tBSBWg(!>8nTu+C6f+< zg39s87Yp;S1B>l<*CPk;23R(1Z@e(!xY8{HGTvro!V`Jvcj2hj6|Nvs8k6W|c4evB zO~+;@N48h4WAAt7O@mk*77Nc=)a(dSW&ygi^syO+PJ6jFm6Krr<~%&M{;jYtI&Eu{ zpE7mg+c>?g3CSHlh1LAkUw#>`{yy>nFUbAwNHe~*AYKK_jp@cF52!RX%=yixssS_y zy&`@09##3x(vDm@30J*lGC{oJvb`~TH@PUC3x26el7Jg1OALVQ$H?$)AO!dhD#$Kq zgY@l&_w8XkO1N5!2@$~Bi#FC9g~#-RNcqXX4@3e$iBwt(a-)1>S&ax)1s!cUEf^wU z+>fiT4bDnB6rcO@5uWVAI_-qbxON{|zuFkbndS4t-(hZP;CZvYw$lBOl7nG6&b_6= z(JuLeS{z8{uzDm$fWqu1ZIIs9T11=^JfA~i4M2B6Cwcicw7;JSk@Sr4EO4HuHj6Z< zHaI+%$S1d>y1r?oS`Jy52EDJ!KU)`^u7!o)$kirfzZ zFwKdfI!j$&G37lZU)$UEAP8bP^8x6#+)?A5HTu<6dxzr2i5soQ26;$!uwLbx*Q!PZ zw^5+3DQ@3aeHQRA!8+;XxCpS@u?+f)#T4pVF(*Sp0;4o-?=Xnp5q--7c~#3!wajQd z$<-$)Vy|Rm!HUyeatr?~PU~^#jHQY%BT;}T7KLEE4jo(T(;33}#vleZ&oKr3B!h}W zR=vXT1ptO-;Y>}-$BzZaQPgJyMC|tBhssWiXRuCp6E>fsw@^XFxZ}Tyw%#p&&$ut0 z&fRiqMFa(+5-(HFk=c@A{V1g7bCQhaU_6H=mIEaj%$@MIU z-KDQIFIZTgu=9BveUOq(00+N#K^;S#A4e7gGB8tQd0_bw`+b(ihf2dv!qRvWJWm0v z6p%bH+6rJY|AlOj6@x8Al*0-(6o0Wl)hu@pR;K?2EyH{$xcf%dcl!J*bO3QvlWC@U z2UIskwID!V0 z-W{Nn6vKwFFAnvvv=Sla6Q3&Xx4+G2THd9Y(-O73YLECJe>;ny&lv-d$_U>1J$br> zksns|$`$AApqUnu4U>fz-)VeiO5uUl1iMSTEpVX7HlW&e{6J~u2 z?t|-pQ&CCu9q1;R9pC4Lrg#e+8~=U$CTOPhX49ALU`|HsT;PX~oVLKuNQd`PyE;CA zg`W^nX93(8n~NycC4>dCrT6xb!5kJUdQ+YdT{;;7Sr2-TYU zbE!g*!M%vjR}tr}-@K27g}WUj75J>eJ+CAdV*1>^)i3-e5u)>^|FeMr9iiho66$VLDf82R-ED-!!hxxq9W zZ=N&-N~Tk$YUw}LB+Z=nAu(^&Iay?XTE63#!V}Qr=JIK9p!2f~dp>L=+Kt;-(FigG zeeu;BMYT|3E{K%Q$3h<`8Epgg;x{@nB~l#uBwRMDrnWRV1Uwn11aMQ3c<|XeX;9<; zd7(lFzz__iGSn@%%tY09A10Lk8kAwCBEbb|Q^N;8WTut*&HY~HJR6%x{WGO%X>f-@ z!L^$8Ufw2K4rLc_$w?r#dKI+*kAsgqfm_nBUA@8wdPaFuUfaUn+>*;vq0FX2dbDnP zf&AU-U@vU77f=8p30~WC2H47e4+sB`01E`5WWZJtf|Civ2SUIEopDPwj1Wd9=O{!Y zGcEZrEe=v#I65gWn?sV(^NLiMM`00)Xz-~?B(v7gSFT1AeN&HSrxed#hY*9fsJ+e9 zJrKSkMGum!xYD}{**kW1hrpG^^fQ__aLs^D=X8MrDdUAq^zab}Cr=k;y5N=s&~6w2 zqJ_@&+!?vv9EICGXLnB>Y=-(0bV*70rNC~o^__6lgS7PTrP|yr)U$ET1p|qucY(2X zOu}}8c1JN4inj4HiF`i(R-iZU$O@X@l~ZhF_;}qjppr9EpAkh`+$Kf`B3M8C_`$c= zxFxb9Rm3;p90r_9yf8HTup!Y#yTj>MMxobGT~vcSR3Hha#z2jGDF4w&I_`{j!sC#- z5RW*VPVW0MG7>;Er1Ry^z=J~ocY=_-eQwFO+CEvEkvLrX!%Zw%w|eAW7i!~3c5M+6 zy@%YgBmlsXAHe$i5|{JoO>+CO9JT+SaARrfSH*W?( z$@*lpwE#MtNSw;Lbm548F7$08;%!LE>KoZWoC?$q>DnGYHgzQ&X_vuR;>glC7kqqQ zdBV<@YY;K9s_ckd_ymEcEdDKy;kP){j>D z5R%_sY%t*H%b4r}gsRe>3xC|^U#PMGP(Hxco*bx+EwK=x${2*}#e*PqymAhOTu7DT z^i8X3{syT8DQ4uazyAEYvOVnAyEByL;4!1Fo) zcQ3YHQaoe4Q7HKSFe6Yh|LRuB>O|X$bNwc$7Y;3g6}51zAahaQGptcbThD--43`3A z*N147evU*9wMM1_zfgTH0)|gw94#)=Psg0N|CCj}0RZY9Y`;BkQ+dVs0vDm^dfiEF z!n|tau>l^_Lf;&2CGfUgbQn-+q1I2XSN;?n%*E6#@e!FJmv(2a5g;|W=W@|j5R(Z@ z)}!PQC-ZCFDQy=;CmpNiMUzS)j&}_P4*s@E(y!WC75LJUQlm_@EuwIK0B~bs-fvwE zf~X0t@gwTFZ~w53PLtNfj@oB;I>@8he@#kt3^EDHHZ&}DaT@bsW2SEoaz+3Xim={S-m zUkHEnqbF`3v+__Li=!18GsI^vsUGNo+tZoKP{oAmY73d9U?n~)pHwLlM$1K?wRGmQ;W}ha zO1nVCc6+2pv#G_$m!DVtGfGs`meklyK_EHKPW9qYkbh$f4k>?`Mx|EP<5BJ z)mt^P-a>~Z?fl#ow8_=y_r&ADWMxPF)up*n>?!moJPe77B2VIF00RZ&_SyPB0Ll%1 zeEga(DnNv!QtUE)@{c1+u4p8IdNnxjtR%U#!1ANWbSp!s&D_sjCnz`&~mwxHHJKuk!bmlN{ z1z<6DEBQa&yjjVv`ghyMtW>JV8y$zh^t}N3!Pm?yCOJR|shxfpv(uf4wl)SiV43s2uYA8i3CGOEwqJyfQ{6;|FE1VQfD9h}dxnFCA&K*{{HUkAxjT=2Q zNc5LR62W`pl>x%CZIHnd`?V4PLN0`CQQ12cF^LRNE#o`7#nhHqUBGS*N0_94ZCmzR z={C&t9A*WR`Yc`)*v$oYxGcD3!{+cRtodPk_4O0w33iXdp8<}#dkoTF_t(&f(2abVJ|vO zh*%uELtOZ&thX))4#suAy%CMeWdlPa5qhrr!R&;C+Vb7%Hb6!P^?;z6kW$`CFwmFb zu2SDY)kD3Vme9>3DzC*s^d|04A~P=q)liffZXj$ZYVSB@M{{9SujeW!BD){2bi;Kt ztR@bbL8bdU6=VJbAbjEGdvXxLW7X`}d-$JG6dA)SYn!>dgt4E<072q8d++2Tq9fa1244j@N&xrNeG& zGac9-M*OdjS~j@!_|R#9qSdt<;oR2R?iMj1-oqKLB8e$=xqf<6(_g!~QvXNOSw+Rs zb=|sY+#7dm+}+(8cXxMp5?d+?O{YJfk7qwPuFfEz5TYOaC8KN)9D zJ!5|X8cQn)9kivM@ltq~i1&G8arQ56*5(>yINzc|O4>_Ckx3^_>G4~*cnb(UU*4Mh zWb;454z@c;W-F&UB~vcbitzi|qZOCzAN6aEp4(2Qs{7-ld7}nHv zCC{~5g;^;7w`%{u!L!Y+BKaSnlpeM~t2AOxHcY8B!XHRxn0)GJp4x50Psvt@0t$dc zMe6rveGT0yj+^<$9`aE?N6TTK$p`<>YipUU$tnz$35D{WzkjPeT#7kBKN41RzGvyk zDr(4qSbKv5Pc1vOOY>E_1`K+!)(^(zoY=e!setV`apHK*1FnS#vD*Yw2Y)ibO#2}Y zM$BGBoRP3%5z6J$4AdKodH5$X*2#aRsfj1Inb5I*{Ib)2-o<5Zv-B)&pC9mF?`eJG zB;%p1Wej6R@=Gszl`K@AIp^_q@#!Yn{~S-p>wH>{;C_pqjxfTlNVP%97d#lcbCR{$ zVo&RoI41-{2}37RE73Fy(My6A)o#@KSE}ex0gZu{6EX>d^(m8@%^oUdsDna%j+;eK z10!!Z`WZ{o8p31%dYgx=I=632#045Rf*H(LPJJ{6Cbh5#4G5Yuvxcp>_|QH&wiQ$U zphbW`{shu6rJey%9NDJey}ebG+)u}5g{%}%>yL7mfw1FSe8j+`^q@Xev`CjD=N-OCsz!@;t`60M?4r@UG4-!h(A^}NwD z1cpe3)S9|QmPo~nroNj#dlT2(fa(?<&fIb&MIsXqb9uDH_bc;H7adE;0=uq-jJXxL z2r-0egF7LRA#_f_=l?W`O2f_MK0;V=8wzsc1<{?#b2gN#St^xKz6!Rjx1cGYdWJN9 zM28}*%)l1ri*h*UrM2Ji;gO_M_R*EU3!z4!lu4siyYWll*`PJbQrD(=8)D3&uQPmT zfvKi_N8GtStI}B_>ezM7P>yAIh858u^l}z_%k^xq@hFmX>OlIfd5^DrEVlEBQ!MO%&p6 z^4*{n1{BiW>eBfn4xgwT6Qe6*;P!J4?u%iW=nk!z4i&hdt63!|>I(wNls%NIZb1!=ld+KfXL(61 zs0mC*JJ9r}ac1eIyi00iutKd`cPbAbAYelcDadAPHIVr>{ z^eMe{Yg#_gS-9dDSM>;XIFF>vh^?rUKY6ii`>1@Rzi)oD^~4PCj^vg8FV8jVCUbi{ zE^HV;ORT!R290W|q&87ZJ~C(fM2~5u{`|RwhusTOT#;0^s|y#1 z_EAb#`A?>zPXrK5ldi&$FddGAsEzZlRH5Ih+1i=`ES$9#$WZ9Uh=M9QG;od3!y&WW z3Cdz8V`>XQLJj!@BSAxuTEf485{k=*(YipxhGS%4q^UnE?P_On@JRa_TT1R>qsvUZ zb?WNz(!aj{Ra$N9Ic1sr)wcKf&#LK{eNkiKw!a%yMr=Q~4Mk`+8T?wlG={VULt))U#kwj9dDc0oR8omsV3CR7{Y;oRS^4te;9OTkdA7ibEC2_I?S$Ls zQmWO1?cup!fm|)7+<9k+`f`82JY>CNYx&DU)_We9@opkcMhezmqj_VmGFqL;yD!{a z8;+Wr7O$e{5*qFh0}RKQ(144zJi`j%wEgn%)PR73i%v9o9N{z{N!q3h!(}-kg@u*5 zH!PB<_^#1Pi`bKpvp+ie2hCA1e31=b&W@BWsy=d7Nq+H)t*}YLJ%N*EooaO)ohGAJ zu-ex~)}do-3qxjTBprf)meZy!Ipz*H9+F@Zjv-)Xbw)Gn*-;LooQHjDiaoxc6ZI1_ zy!VPWy@~S4r|3-jaNu@Ioqzl}Y^|~as(O^60|1|?S6^6&dTgf+Rje&Pp{95korzR9 zL{d8SN1@}jNB>yIDqFbh#`qgK=D`iJ(hKcBlu1L@;L!oS@%*ICD=h|*#@Z8wKP+TY z8zc^79_TnNFK>1-t~O9n`RmZ_)`dH{(6KpYBm8HX|877!i?+Q`m4_W0QI*lBJ~#Y@1$Pg$L^`2^N)XEDiaG*R zqSHy%z|>J&m@O?GS8n5}KZ~|9Gd!zC*!{gX&`Xq(ajtcLG*Ys;Qn!B@0MTa~=~)*0 z5l9Y8QClQZ(k)~v#D{K>&dF34^nN+m42iHOR*4xlv;3QVe5IwqfsZike`Fb9315=U z#`6iIh7y`B^nCC&G}U6+a^`kIwvNQJyU*xQuA9K=yw&bNy&v>~zPlA^ykyMj^`O1i z8@vr%fmdC{n9`5|u4n0Ex{7!!hA(OTBgp-mkZpXzg%J4`8xx`Yd}$q1^uklgR(Zq= zxt6_cf6-LRJj7um>PSkq>BqaM(wYqQ$`590-ga7@Rx6l&){-B6amt!kew6!>+nLFe zfHQKjXLP^6Wnfj#$l9v4Dad-&40Pn!QTJRe03Ki)pi9CHc@_oajD7QcD|q^l|EZ!1 zOCwPWAza3Da#7u52#M5RjTcNtt#4_na&{sU zL8*@99zr=LZoUlKm$xJ75Y!hxnn>_1cFihe)FtHlk6(A{EIPxuIMg>vEk`IKHX@sm z#M0B?nxm}Ny=^IzcyFH)=_XoAMffgFKg5e6j(0J7)@XxUbhecK9;bDMR@8Wgsr&~T zLzXMYEi>cAu7IcuHW3et_EC&5M|IR8Gh-YDU8X$pBx}0GdlBzR(^TOELLL`(zf;LI zxM3H+zU1|?V7>jP%nytgp@v>7yBK-Lu3zS%igC^Qa3w^f+Unif#beLt3%SS{9!72G0v);j!`8}p=SvluquTN6%V*#A<+h1&ouFy5l)^_P11G1w&3ncVI8YtPK?#L?D+SFBnp9bR9=NLl>gYC#0}WNqN;3cDxLp= zInH2W3@lC}`_!BtbR%HED$<=$SgpcPy1#anN-vlO#`S5~O3yV|vZ)gEw>IhaQ>7O_ z6|%OKvl-_w6>r62Ppke>`=vo+dvl;SJRw{FluT(rMVL2fCq)7w)u~Owb3~yFlfOuu2w|rC`&B!b4L^0t zZ*950?{?qM=N57}h^oKci@dBK@WyobS^~>mD(4%`abR}OR$6SH)ZxAn15n7nl}xm0 z>V3=t#HdV^P@}fbj31#2`p{e*;(l~yI#KhIM#fk)lRKw$ zgR&HlUOUj*B9in~0SGu)fOH%pPC<~6*r4-0ppn_8GQ0$f$RuR;+|e<$V;d4$spSPAD)_r?n zy)tmZXg*vsTgj$If>j|5#4vDs_A~Bbxxr)oxiIvESQX@xD`K#AjoMP)KG;aqb8h=C zKcTrJ*_=y)YtiuBsbIzu@$~f*yKlIpMmc@nM5ufm!|0M-NF1W+7&t?{zu%vOpM$rV zQxA^>F`gI*WC?r_DzieuNh`4uW(K7_jXH$GfC%{(uw}rI={Yl*FrA7c&n#>;fE2|E z2@IjHr;rf_PuLGxE!sH?X@xq<%=@|pi!<&BMX8hBH9I=~W|U(0eO{KyQ^c91IwI=) z27M3ep1M|FY=KOqiBZRsdRd*BlzQJ(P$E-1&%n;l;#8uHQ1G(Caw9AU=MeSHJiO#a z);A3g8fi#11li{nxSRj=ET~e>I928=N`$Qz`^}%S`|mjuV+P!UZE+}^SngL5sq@sV zo(Rb$I#Y$3zKusKPprdBQvRby{^iml5o5vzNK0Smkl~+bQFOJWxB*7aRT9=ZKh(d6 zGCG>r*p%m{y zLz*C>_O-n3iW6$ARX$v69rq8K^4IuQiD6d~l4^q3Dbsk+w3Zg4!r?3*bAl0NcjDSL z&+(>ZN8=2v_jxNfwwErYN0U%6`$Wa#{c>!z5bzpg&jBrIL zY|-+nn%!67=uA|4#b#f0d;Q8)m)E@#uJ(7uYzGc{_jy;^T1x}r%VX7lCC@OxeZ8BY z!iYas;OrvX4s<9cobIH{UPnlY3l!`;nQ;XjXTai*n^Psc*-M#b-`VdbW;h^)ifiD` zaHTe6@j3p@*@nyZGPoAxpGjWl{M?PLf?}F4D5tt6ttEAxX`hpABjd#1a{-iuNwWYq z?F^Y6d`V(Gd2%-}$4CD$l;dDmZ~5qgk;hlC<(M3FapkN1=jlm;eNqpxEX|Z4m*Z}6 zo5D{kpq%;VHBn5I+CbN%YRl@!S*&I8hvjIASW8}(lV*2>WB3xGGSPg0^ zcB@IKIf2R<$XY43PX9Rb;aki4+jto-Cmj5zXADi+PYYnkVGVZqnyReIn=+u9O zDpr*77|j3tZ#9%@!l>*PUPqoV!Eyo=_qfnnMxOLW{#}t_Tr?9Co}p$QnUy@;MOqK- zhqVq&6pqbe_)Y0onL_(YO=|AkJetfp2#q#0DrixW-MLzFCRck>J<=6Hv2WZy(uIH9 z6@xk+7fx_8F50J$MJ6fwN~n^pJ}YJ#avR)7RUZ8JW|ro^#``hE=1buw4mD+|e47X~ zD()7I)#TDNkw5k+GnYgfJaW}4o3o(b`M16TW>_^5Eu5rnUp0R);mLbia$sZ2R{(4! zC`XX6i3xRwZwdXeKS^M@St`U$rAMxN{QT1LuT-I)?{z)srcFBGs(8Zg1&fK92qnlt z18oObj$X6SNfTtQE23Hk<&VYrbiBUANf78r>jdT0@c zE?8T*S5}4$(DRZ|9Xs8LVJ?1S51Evt0D4@G%8Q>4XM-utoy*`D$x(t<$@A+30l0Etn+O!t4?QcR+BoD#n->glb_dDTl3Iw4B= zAa9BQdz5#_)jSCpH7cmIgrla#JrwR675k_Dr{44My2b}k0EL_`P;@cJk0&7Do!k-B%t}eH6!6@ zb%}}<&R~i8cwLD)M`*a*TLUxV(9SzC&P1$CeG3ydGMvRNGaQROQWJv4KF6S4W+6u) z*Vi~24sb0L`wdAeYtwNWdb&y?{~?_08wyn;rdX4Nz$ytWT48d^>&X#|*--0Xu$st0 zligrqm}^95ka+%nd0?vao)8XZ_%{CIDo;7f5b@VH=MOqO*~cGgYxj#@E;knOl~X*m zZ{e0yaA0E$&MnYRj$*C((W939x zW^IVu+eTJWg3QZUi4v(#yy=a+%`r1))F4KQ-SOJAx!*%O-w?7;0^Ox4DAUkl%MT8Fma&0u{38_?KxJqnP zgpA4nG_Yi6A>quwUctj0{6a(gx-J(f{dwKt|1<3Kp{y}FQbW7iya9SxR5c3|l{1mA zRCUa?q7eyg0Z2u``xb$7!p_?1td4X#s12|h5uR%CuY(JG&>^cwRD!uOnZ=^ADE4^G zLkizF`86p^ektuXG39T*_4kYG+PZem+a(Ev(Jl#uGCw7j0g;}OLf2ksb)E0$gI!lP z8H4@1XtT@4EnGD1y+kDxyWlaFR^wVtQv8yXA-nXnu1!GsWiYSgT(`45rDzh0tf*R( zz!9KlMLs4nEuRADc+rd9ys9ZhZGh)cNi*z`ZeASQiGyK74oGJT_vWF7J4;hY)Z;|v zK|ex3BLereu_7@q`Fpk!NI_EM>M+PHCrckMOXbfw+gOQ2Liy<)EwGo<%4QUQ%L}^x z9`}B?e@s|`+~e^(jFfcf1+tpxXt!tc-9&zyM{icYh9}~C?+Ykc?+`D!&7Ht;ZHUnC z64}T7JcY8a?IUa8)3bSSQ&N-YPJH)ScA?`rCM9N`Y4kx=OV|nt?4J=h=d_|6#^Tc+!_s3!cV@wLYm)$Oxe}$H+3^)WfKxgTNTE*Ya{zS}y+DSPTnr2Y$ zSd;MS=sE(u;q-YSo=fCm%J@g?z?;VBmXCJmT&O~04&{`uBq=np6Izn*DnK|41s>5; zlzMrfG_T^YjS8q0AmDGV4)&oc<)6&9YKCiLJhfg-qfJ~(=JtH6UgPZQo9~Fc zeQo=cYlCBJsQFAl)I#sYrdR1;YxfPzRc^PvIBhUpU#BK-eu4c&${HNa-q>HZ8C-l* zYf84-*y+^8yCc8PPT{T%@oitYu4LC^?94%7EFQewfxK=gQqxPX(!lCg9H9JozU8}N ziGO4iIKIKt)9Z^A#*@$!M5#B6k9$s6}OR^pdoH8n*L&+c1!YlA)^ng{q=?ab4 zWRfT$%HuRZkS|U6n!rMHitX^!`?(leT7RD z4>;OTWt}CC$k{8g@RBXyB+r4dKwvniH^9zE5L8i2C1K6)Oa`TyrfzfyB@7E!H4PvvZ9 z#36^q{y8=8KR@MOx0@J|Zms~Mv>PfzekYSfTnRWia|vS3)GB=|M@3(RkjHL~&$|pI zr1&f~L<-!(>Vs7lDRS<{?u+sw8w)s5G(%cU`Yf%LLO%#l56tN<;_dYfdfy**5;b=- z+Wxkg3B`T*9%P^qheY0gC0Vh$GB)*TA8AXfIRgAcR^ zxH`&36E;lQidxj(=U%?t&8aH0ddIjNK@>tp@~qK81PI6#I$k_96)j4HRuu4kK1Eo| zl-MdFhEbR|B7+!4ub}*UI%SI`3%##-ntBUawdaZbIPrFVQ>8S8as(`0la!?U^|{Yc znU8J#Cw8`wjX#_s{_I};QYuM4AIp06gj0T4&DN_vre<(Eu*Zn%cFq$eXgHLQ9~M4l zXU_DFU;%xQiDdBLQ)?`T;%F_vHh!MSA8OYqC}88GSnAE$_xURf%`AcUq5iiLuW6e{xa!5ZDESrBgI61~aBOF>`7qm5dj; z`aTq_19IkB4Es20X5jqDf7y`k3*-==zlVQ|NtFBfvjYncNIRb*)T(BI&kcPAsLaTC&&20hm zyi^G&rPQ+8-zS5ze{8gYMZecN)zTi0l_%v`0)9n)JphEM|D5nfl#E@ffOn+{w$IHP zEQZr+Xj`VA^}>VbDWoI~Gr`C!iZQb^r!mBjQA>B{{tl0TCYD+0;}VCW5e{&sHb$o) z>*_=}jIV-*f$~yOg%fMztli~|o2jR?A;U1-Lo%`OQJ8sAtgrJ4bP+OK>U8-Y?Gq3t zY3i{ego(vaN>|ym{~jbue;B0Vg&c!-0@u#}?*QCxu=Ik-IpLxtByMi=8AHKD;hfUt zS9@J`U;!G|orF>k4W#q>0Uq2aGN$uy74BPhYjq z)}kbfx$4+vjT*k1oR51K`U{gkz6^|*BQY{nObwtdNYUc!+^%!a{EB0l;VcY*k*!uk z{RoL|@@};A;t^Zp64T-m$XK;qU09!dT5l-w7N*KIj&u^<5D_DxAk;t@wUz*Ml6V#t z4|CELb4Zkb0VdcXul%ZmeMRIrA>NTO9lSowODkOOyX$E)EUQncGFW8x0>cXpF!N5* zOAAE`BLPGOI5wDQ-_-PGGM$^6c{nGM@j)QDb%s_htDf#Fs^Vt~%df#8h<#jv(ZsBk38nR2o%sgfEOM5cOZD$SQW3ybVz=8#Y6{pVM@t{APG z&=+@EB)k8_heM#Q^nc#uJNMP#a(_>M7l4 z02te9kN&iAz-#Nx(lxJfX&BHiM?oZk1&fIdDD2TO00i^MJUuuxszfA?-@LQp*OU80 zpoNU`p9-S^Fn}gV9f^{Ed2e@z*-jTt=-2{S>QnVFsp@a(wHj>jeY{zV$rl7EYMjTi zNJl=%Q2+09hkM@h`l}>29fv-gbpMhPkQX(TMCL4u@KOf33135Q(cy<#<>hxWrY$@| zi87zDGjRz`$SjoLwfG+s#;uo}R&}2H8BnO?kAONvboe_HO{ZL3E-p>x0HsKWumfEzC3)Sh7FZs0eEOmmIc!`M`Fs^}XeLUImoSgw6~t}{DMtqy?* zXyUx3EqXW%G?TO#7la%sDc|y*Tb2_Ahp;{92iFP1P#U=+VL+2m$?x$Tmc=tFghB>y ztk-B2%q!07%--q%FP2d8QGxNti&^%dE?sNlFINLy$D$-aww+*xqaHzdro2^#o)xyE zv;FB>hS`)EdthsEWofo)(8a=%QpMexg9F3$=d3e3(+lhx)uz@$hq#*OZdYN!iPkVLtTrr1E z)PEneCm*+)Bpk z^lIEpoW~O@-STb*Pwa!!FWc=r+jd@4GiPRjjBCoJHl8Sk3=P4x1xYBS%e+dCThce# zmw)F6F2-%2-v(YkJ-)0zJikN#UyS+9NCE&80^oT0z~DzW7KHl?8L1aciN7y}zqgq& z*V{VKU_HV6VLKS(^z~F2UsMJ$I);IAH)NM;=Rij#c%u5NR{hM`%=c%H=J2RwT%Kw~ z&)E2BKc{n1?Ddan@s7tqpZeE{Gog$QUNDG$+hkC6SfHLE;-)ZVl-F0D1~A;Mf`03E zVd!U0|EOYjOt9+p8f_Ovkxf84y0xzxO#u?j{`VFl2f%t5{2Oj`nZyeXI7_=>AuB1h zj{+EfFdh50nZs3?tZ0iw6Mo5qiP`fa6^`~Ehl?2X0jC#0|Na+? z5?~KN*mJ$5f=_L(%t1pj2h}8fr5}MHNjt%l>Mk$Tc>Qc#Zm^R38sFb2By4c0rkke0DvEf_GrD}9#=ixUmgHXF$W6O z=3L2}oIcdJEN>OlD3|aTc`-kh?c`zJ2HHIub&F?s+JQ8?xnx!&zj-~Lnr)M{2A;WE zPy6|e7d+3~1-N7d(4G<@1>Mir5AVKD#`e(CeV*%%lA=ksQB>&TSM%;1f~$r4Pr)IB zfJo9T`FxsmObG-O^4#bJazZ=`!5B(7LPosYG~__S-w}(+7BM-Ua2oZLV8Sq@<4_P% zZzYNZf@v#I0@sUdLh(&pQfHIiAhkj20``so_MZ)zIRuf%QuQJL0O&eXc!h3qAP*&_ ze&|YAU~us3Fo(xU>(qtp(`dcDQcHCXD8xsEJ~`sv#L{SNWE~`C8D6MKc6_l@ukr4n zWsU!yASp0y4I=e5^6Wz<$o$f^q^z{XU~^Dfm|>D>Qq?JLde&(BmG>uUjis)u!Q8CU za%cNSu-};*@yq#%P^*vQZI?z(7VB}><`eAhF$}|p+9f>k9h-w00442 z7a%@++Q$=;Mj}Xsiw^!}Q!+kXvT5>grBdo|w5N8;-j0npc@*j<{3O0(d*{qcR zyVcIFOTs3xouiUhLoPyX7V4ANXjn^EVW_81I~$?)WqR6XEPjmC7WmVcYuMmYVKUa~ z#+Qvt0H=eCubEuYT$;=pa6GG8-;yrvSFa61A*EdBw1 z!%_V|Upv(X@=k-HxBT`U>KuRnQ~sFm3VntE_{{E^BU!a{7`PdESR-&O>;i$ZIfnqD zcM5Tct@m=7_RaT$kSh~znC38sCLu7UIXS9CDCLv>&8#3cuF2=mPq@(nrg2wg3`KU5~&Au2=&!Vm3#ectCEznQ&EV{YL?YB%Nll1aZ?2jk)i z0}ud!N8^GcWa)Tib(Nms;bF$_;Zl;H@9v!idmLQRW*>xu&N200KaB_f`QoDYp2kw# ztP6QdW88i$`1680*`fT^5mz~%65_~j*TKi^QFQV)M(KLmYGD*Z*%mjFUdhwGFO&JJ zq0iv^@oCqBwi+=i4}b&!Xlqo(5H}nn#s9bWAOc_q43vgM}pG@xfi zFFkoMNu>2Y4wtqA7)GM$WzG&!MIlU++D|~BjHwYo*FQyVeHs5n9VLB1ZagrOedtT4 znVb0E$_XCf(Cp$))F#$!n4G0K-~?$;PxI;O?=uzA!~B)Cu25J1P-v7RI*q$8z`cdb za-25B++Nx2jf1Bc0F;8^4!juTIxFTFLQ1e+9Ry|;GNy(XhgRn~)d2?xzsJdxjxu0w z&KL&<-Nq8OK(d!ba-|Ls8{>0@^0B^_U<%MilP%Cr21;2s<||g zZf1W+t#F;vE}knYken?T)f){>NN--1xqW_XN81``BqDqvCP+>aO)BC)Yc{7yM_0+IM@-v@`1#x4BbRR5iTDFLvhI;$_xm7qVVIIm9qLFMk<;5^5* z#*gWwzc}%)1M&!-Pm-PfjgH7WHS?3;?}yZDsOe{;p*}2}oD5fiIz;e~;8(uU-tMac zk`|hq-LHosYXATSQX=!?KnUi4vX-^pY9rM;4acQ@@rbc75whOO?7hv_vX-4iNFCx6 z$2e?`y*4=Tzb)W~W^fJ!4}yYB?e2ae*j82}$2r%4^5cHR7CTD=lWpPfxpSc&7chq% zG%`}ze|jZ4D>Ph{P`!E+Z3vMn7PQnRRoDq;#tAh)JBz&o38D#AjgM27b7%I|uE(DX9t^?8ejelEkcnqa&dP4Qmey zRJT5fl+OCi9Z{qzuOtno79Y7CWt`3QMLmGUdHgH$#u=_PrjCuzpyni86mE{h%3?p7 zH>!TC9!F$T;|d?lc`~03H@R8Az8SVfz+JO*m2PP9tRAt{c`~a zwgu5Sn%jo)?1v~~LbDYjthXmmN^_5^?i`4$1_YBP$4d?Bl=F?{_sQJ|CxPtMd zzn&7Wy8xEcxz%w+Ma48#v~}lk@>z&<>XW#70$$Dy$eI|LqfVMwLFU!OQ(^O_I2tg&w_ z+COfbC!A)5L<10=#9voQf}vDSK8A?HGXn@s8>0vvR%%#O@vq~P%Jh`N2gQW_!-KqT zNdZsKYLCr{MB1eWT5Mq;N?fQXvP2rNr3Cmk4)lu*$}b1_LB z)o=pteWu8uv0=7iU1zoM;C`^Zw*Phb)8X&sy+5_4{w;CjqnR2juhOlL8n=n=$EwFs zGb+kWqg+ucRZ4ERU8UGutLM9Gdu2>nA0X=qhmB_eYM@J`6aA5wMFYS;d=PnR?`#MW z41%&L3KvK=pO6FMMI{-H5|sm6la}Vm_>9zb33W}C=KT7h)yIS5e=3Jbsqj4I|1I#2 zCNi&uR5iuj>^v*e^Yy6>LA*c(3 zbA3jIRB)UhJ*7#1-sMon0qVyGKiwEh=aQdNIE?c6&G6-j9zk{cJK0d({=!gngX`@+ zsgwW+aM2~?Xk34m4#TT_RAmVoJQjJcW-6n2zi^+@oXKDvDem!+jf7>8p&H}tBpfiwDp*c7yPpxkee3xf-=P+HYKV>z36CRr@g|x{}ki~88VMXXafL}A|y5+wnDlx zh*2mw(+IwoQ@hBps?Wj3*F4UUlp3>h>VKU_Z#-4lJlQ{ZesB#(CvRYlH`Pe~Sw1S- z%EueHoCva!!gS3N)i7JWe`(47bSQS)!sf$;?q7z&&DFA7b}{=mt|u;Tz0YfQQf0rf zd{;jr6Ig5VV)QRP9GA7jWW3lNV;t4zmN92jYj{s7OOJA*@xW)3;q?7hu27D-KV|Qb zG*JOpBBLpS`YrvzwNp8#MyRyll<*)o&G|PshK!i1?H?i@0T~xZb7O0QrhD3q_c2BQ zfS(F%_I3eVxg4o#XN~f&RH4E(AH~%d5+X%IJ7TE$R86(pMB_Zir6jnnRtuTq$EpCoO2iUE$OP3_(WWzZ;qZfZg5W;xAVb9n~l` z$1iE(eOf=e?uhv>M36|f_u$6AJUmX{p61wqXTuNgQ)#(K)1zyt=z091Rn(Dh6W0?~9RO^oDx2rVY;F$@BA=ZJ8?bBN56 z$c1D&=Mw=WH!#Y1RYJmN7MoD{AI?9YnNMc+QIIu5#y8%pvBF`oj9*QT#osf{!WgHkYdyFO>=Jv+<_0N_f>IEm*v=FL1oNPl_JD^>;r?&dXVZx>sxny{1)^3Sq_Z@l z@k}}(?YuR_mrLj|_w(_+cGdFo!BLd8dWb6kf`oA%&5&~6@;aH2PL=~}O?4v}b-24<83+ROLKN zNQ5IZXiNftzM;{pXz-09;s<{XbYsoJmJMx-EQWVGI)^&Xg;*wpCd$e^EH41uq6{$f zCg4J9uAblBzwF-|k{$p%Zm4~l08M_XQ%p85bHl*WfN~(5kIm?rMXoa2gDWBH-c{%^ z2l!{5pHA4#!?xTQJi2;PN;dUzE>xoqG6J(isQDV_p}Zid?e{2*L4~C=2CQq@99Z== zijT*zZazWsj`N%Q(e3Q9OO9|*{izhX$k9eEB_@7A#cr9>rWrBu&ac#tKIX$iPoy~I zh0mIfi^cTVG~<9I^f||d?r!1q0>e$;whnsT;P)%M6acOi5aG3BM_xW{7gUU;h7=ik z?D=W`Ru}oJ^ErkQH06xHEdOLt!%&(AoIoleXnc60tf2I80RZsg;JSPZ!j5j{ zq9KpqnU!=hA%MBZ;=d7XoL5p$rx%M_J=+7FaCl|r>ex}(uMWW{`4QGC1+TsEpHR~A zSW)_@;8JL|QVXlz3+C0!;6*i}!70iL1Asg{aQ)k^NQr}oAd4!^Kk(C^Zo|O{ORa|( zH-j0zk8pFD|4J1*1l9}c`Ig7|Ze06u8w#{lhyRDaax2MYWfDzEBBi`tikV${9;@T@ z^EmcpVcNa6o~qOT)E*W}2s7LTK1u{ad^Ko25ynutc+wcSvB zJouEG%V%xH{+(hpH#5mIC+)+|Kyt>_MDTtd2Q|LEDH&)-G^>FW#2r|Obn7X74CK6q z8qEQK29GV9=9>{h9zt^o!(2j!F%K(`C=etBu8C@gYa{xD%|x-^nlv7fGPp0S0b{ZjCUBQI2Cjq8tLtAcHw$9Y04y8N+mLv!gTWV<^oXm|BcM-Y? zt)#MbM3hv%luZ=PlDCspO##*PQ1Bj22tX(WgyCencgh3nu}B$27dhoy&1N+gN$1Di zt<`lgJZrG__@77F1LKkI->NSgwB7XW-i%M1Q;UI5|+megiQuJ|9XYMH7 zT7%|3ltIr?yC|$IK2?7%hwCc%=>&Sl(dGbGy_40E*Lm$m#@G&(>_LIPt^sBuLN&t|A7`Rjjb+8U8*kY<#RKr=wM z)OtmWr8@answ~izpsO38Ok8d1=s0gp9iT2~r`8T_?8nw0KeTYbsr^nb=kw(8Vstcv zQ^wsrOjyj8!&e#vVC+X^y`P_jFgi1pCJ+8y`jCBq@ z#T|f)51ga_VBnquQpDtp34$dPAgxcb(55Pv+LcIAXT|;5Zy?<+jD!fb#ufyG>|>?$ z(8caf3Pv*DFw^rEoxcE61R^Ls`~+S!Q^BwefGg9a0hbabJ?!mk#0stKUnqajDjd~3BP+93Ir%SO828S|Qv$4pu2G z6`U!~e@#j35QJ-u|1~8mPMK6@Ie?%{I}K=4lB$yB__2rs?f@26EOmfG3X7U-=r`vu z5;q(N3%E7m@-K$HDOZ7h`=B5KWXrzB?o%k5OcWu z7_vdCGZEi`kzfb^pKi>0CR>fk3K?uTvqfMmBm_H$w`M&VPs)$9Im;O2de#qe(lJ(I z`bs#lb`A`ct;oKn>Z|FaH}&$KVckOCb$j&>B0Z*)&O5$OHam!Ami2sX?*L1i@w5xu zm$Fp!cy8i-c}vXoJ2lJwcO7z{;^q+o$Rq&wYg;^CvJxEiBhu>TxFf^dDcB+5I3}S9 zaOg&Lp;Ms;mw#1_Ibrrd+TEo)E-T5zUSYG?xg0Nq|7nfr|CcR@J!36Q+nc|t+#y6N zS&C0qBOkBN24Q%KCHz){^!*{ha5h%TzP99!_Yftnvx4r+$3MHSC7Eo4TFOMLDXP${xPmC%xRrOQxY4X`w@g1-1r`ZFv&thxlC!$ zo507HJc%C_=WPgEKxS_5L+8PGPe@DX11B9pM#>$&Qbd5bTY7z88JYWygGV;}EWivu zpH!S7nR+Fv+G#7@nZ#7ZM&#jA_3FE^q)N*s0IUoa-Rr}SfmMoj?<3J_*7hbe~! zNUF{Cl<|eEDrswKUoTp7$l~0ehPcL6xZlmrri{6IbOBbmM4m@3x$MkfurepK0!cwR z6`!X=Do5(h7fp39iNQuC5A+c*w`Ik>DJZ=m83J#9MUwFqGLsPA)@9oSK;Y&;f&MRz z>|L2roD@ z^HS%ys(2Fr+k~pZUM~p^%pLw;XnQ8LkPt2aNEOM_Van3hqclNq?8p8TS17nc)dV!_Jw zf9JR{D9!-dl(dd#N;V&n!)Vvhfwrehf@_)nUK5G_TsbymkS8KFd2-t46cv&+OvS>b zpKHdZ@rCy7%=c=TskHY86*gDcr*@b~67&w0*`{)Q}!*soK|;E6{sDu#!{>QlaoAU75KfYUjp)R+GY-Sha+%E?bRtPfz*fcNlnMJxk=fw3yhXVe)=jWd({@b3Yk zK`BnMYfcIzj(W6)&s!VB&-ivms$l#fw%C)e%Pgv z!;H3_Rc;@1_H9(P<|wpw)&8)1xVnb1rH@bJm3diPk}5nw45<9gEHehvqwlzEjNU6o$=l6pyAf&-Tom%xWq*!yh$IR z1qP@dT4Q_*9GXV31)@a(VWj<12F=~^a@ZKqG+Sv92OFsOFAKgzW#wVWy1V53@l$1Z zVcVLj9SjtVtF#(D7^yhv^%S!inR^=?nC+WnZ)lO?{-z57Jx|pjHAFcdb!-&;KbF2S ztj(tDHh6#lK^vUlUfgMMch}~NjOw_d_rVqC*3xb5cNvG)%0F5*b^6a9Bx2ZxdA z*p6Ez|NmP0tfTjk$K&i$n=s(RK%miY_&)?iBR#IA@!D&UwG;~e(6fU-+dx5@lOBtb zFpX;}NfDWsEG6PtLe$P;H`Sh(o#&lW9mfWE_(J_ z{Rk_&S6ZilI+K5T?YlvLpI8gUVN1>Y+oBIiIiU+%=NI6(zKo7ya&$_isqtkO>06_f zse<9X@f)E*(!C^{-M{^>h+T(DoAabpv54$ax=K1kR_s&ONEY!J%df0n_ZR;~-18#Z z(u=&!wrt5Vdh~-=N4tBPWfisWN;YiXzj=H_{6+jxQoJ3-21|`a>fqrlr;6~~`T8-J z=b|}+mA;MR3+oI5fYH`y4=xcE1b}1|I=ZjpZ!Bde26Ze!nD#icYwg`_6T%FMy2PpR zTx1I5H#q7BX%j*&@~`!S|2|My)A8R>n!dEtacEYNSUsTZl+!S-V_WyH^eHESRbOZd z`AEiL=WW&2`>gE21<3aF<-erSnuzSLh65P4XN`aFhGRCW1Aj%ZOVM-MInJadN8kX_ zkM(|O+zd@XNY{4x8{LT@QEt>zMbkHeMq|WuRhDH_!Hr+Aejix^n;QeeKg-Ad6;|mQn(6AB`h&|vkzA=LaL*YtTb6CRb&6p z8i@m`x4-jtJnWd0>u1J`zp&p~{Qlr{=k>LBm$@T&XKUS5e425ipQEtq!r{+O!`9<7 z@eZEohJWxxNSbrQB>;T`rj2mxB3tAsu^!~jF6;v6_%!j*5K~;6HNOo$v{8B<+g)#A zT3)XGax`>myvN>aTUgw(<#j|G!<^q(`nS`Mg3_})A#z&g?J%n}O3m=3(Kv~- zbD;zzN(d+NoazNK9oaL*W11fwKF-#`qY|-sh_v^nLAFJHeGVreN3q0s+78-vT;w)< z(4cJNk| zMx=ww3t9^;i` znPBFuDV=q9?$>bOUmu-qg9R0;A6M$8a>@(3UdaPx*$uh-Khr&`J_N%Y&R?Go+?-t8 z-|?Y$tSr>E;QoGmOe;`M!d7N6bGJz~QerEHjM{zGQ@xaqejoBEC5NqB(Uai{=O>3UsPacyC9dajDKwp+$Mb` z|FtE4ZgUp=cJPpsA@JqkacayaI7RuU4edUBc2MAlp_-}D>~EJIF$~$^vu>qeY%UyI~?ttBt@3` zp2HqrewX|?h5fzSFZsV^Kl%oL9}4PWc#f?5;dz+Wu3dQE#< z7@mK=Ri;WbEuKh~`>_(;!q+iR2eZDcPvk)cw7m+HDo@M(@vXW}-A|$KVeG%F?SD{Z z3P%ey(0#xp33R}jM(TdB@vj^dktRWVP4#C;H7(~JtMIIz{(%1TjqS(HTwoKFv@c#c z1=om(e90UwV+1^n`l%h_`kzi)t@*&cGk}DBqwU5ryt=iR=|R5JWIAisav$B`IsHFP zS!u@!nmkc2wohNQZa*u1E?de4%DUUg3VGRsIUMVgm?345&1EoeHPhrMR`J8%u%|o3 zuV<9~?b9+c_xy?lslmFxA~RCapPUZJe7|!q6-e^{EscM@yKK!0C6HRY6s1knfuX%5 zD*GW(pIOOTl~H@}eHZJM&9&=tPH|kh2k!Vwt&FFB6`ab3RuQa3$ zEp_%Kx?0?)pK3E*C|lcTXrYHN-?~0+nOKrE2;Cci5IS?TwGI?q?k1cJgg<`z2UR42 zCeKjkEi>gG$AohWt0+SPJfs|l(Y>at_PoCwFGIRA6@r6Kgj$G+{>gMhH|?tT+ou~S zei}2=9g+HXcIa`&o_~lBDsGNSp0+?=&xVJ%v6$sC`>&P{mdhLGrFc8*=&X}EM;W(V4BN@t+HkGN)lYmKJ966Amu5Y1Ka!h7xp_HElgl{qb@{s4 zZ{;v@oWQUM3*53E%it(A|8!sf}OvZ?Xi&~X9AEb&4)`v+rub{el% zdQmIpq|-F)Icj}ZFFq+n0j?G%GE%k2^Xc(-pz~|eLU_Thi|4CHo|AkI9PIEH+=MRr z-rL>`KT^?B?z>->+KCpt#m* zOE?6m#XY46`vW@2Bnykkk&V+Ju0wdkZs=&YJ>*Swc!hWfRpZkn8eF75S7l z4*>Al^+?M|4M|riQ}#?7Cs=j6kAjW{q8oOzaM#r31|(o5`^MX1Zfq3T{>6x=`<1yS znKfT*stv+1O#949dHN7PUOndNuNm1bx9t1)jukUI?9gTWjdAG~ISndDPYxj)L27oR zKplECl4bOdn)21GAtg3$;h34Xie6dUrbS}AHz_C4b3*78o1Y1q=9{!wSD}&u7%Ws{@JmJ$ zYvGSRpBL{`QHowkGtz=R{v>|HsKgm&uaxRRl85PJp{=hMLXkd_@lQZI)f(i}{V_~7A zFxr@&pT>&Ak*xLtVX{*9V!!rn2&Y5@Ul?=(Cx>Nlq~$1xW52Xxkn);f#+6Y~P%NEa zyO2c1@>d!0#W~6g@Ni90cBz@TQ6-Q?kuwnP%La*&*pk=uP;!`!`XyJh&g z$N#$ZS6e9|CGVVsO6ATso}s8u=XUI|v0hf?m>4F!Ep-CkJ6vaLKS7RSN{|oewv7JT zC3ZdgrMTM$Vg{||*K%QnMTEt%_@!eGv2UpE|EO$sSGnl3vxlaAxuYBZ(YuI4{jOF? zuCaz*EI+E~nyH!bL(b8K-%CELA6tA5a#PFysl?2xg%R;b=lx&Zqapz z@;S89s0p8%uEdgHZ#)x1_OOeXN?l5VXLb6DXUHh-_$=59>$;iiag3hPyW^WSHt6KPeYWWA zc7V!O2i4g9HR~=uc40{G)f`lKZ@j|DsOrWhKWI`8L{Yaz}NnlEh!3;Qpm>S2iSOq;?Jvy>Vd9e;9wG8q&9F(yfDfmF0a9^B z&fdsu_bSFf00jY013qlEx1$f&I>f0|N&3=P0nYsN{Q>8z7uJ04*P#QLenIQ%%a=jN z0WpUvo@K0MQkx+CKZnc;pK`F$^Vr&hhJr5JlWMMw0xvsv&wj+aFyNPF@Qza?v!aO@ zp{q%4gH6=6U;q_2zC!U9C`q$aI5F#p(yFyGM&KO7EEyn@4#OC;NtZj{Xt6NM-;-a zaR6xbY6fqEAi<93Jqs>qc=)Bp>PYLo(vmt>q~V%YzY2!4b%P0x?e`7na!`dgC0WUN ziRJJCwjr$S^sBT>CBJ2$XOi(^M07a!cycs^A$4eTx;h4EBEmX7S`_d>-Hn-X_FT4f zCm5JpEyo-QnG$rT|F7El5L*mXaKM=w1VQY;a{PBgZ@xYOR;ECw@M2*IEho_F2Q+{TuwZy3N1Zuqq{*&MT zC0jFD+a>A~<<&XEbs6a#afNWMs(15=wPd}g6JVAV9TOq}KlGWaywxztc&s9%53b67 zWVVlIT`15h)qA_ozO(h}Qcm0OZSXZ&<|k#Ml89CkH>W<$6~grpgq?T?=i1xS#Zdfy zdY;;{NTaHiWcH_5Bkmd^2G%M7DE*BCY5(Bm zfEYZ{?MR6tdNldJm;6GhSzMLqFS;-688{II#S0O)My6DWz4LYKlTe_rb#pGz2*0Z` zDrcvrvtv_=YUi7c?n=~}CDf@T69DhLW-B5**({oy$|HxHPy?M4?{ge%UEV#o= za1N=CCCZ^!F*1WNWpYuQn+4R(2r%SrCkNB*R%4197WqImz}!qX^x+jcA6a@W#eYym zx@4yOWA`9~HP0vpTt^Jm-PDyYn_Zyk%xsd;$?CFXeV>FBrNAu@*s2~rg5s3=fj`oH zD4uL;wK^KL$7KYZb*PW%9({ckx!d>8wF;L`%V~ritc(k5=;zKK5$p4 zblMj@m-qana!vRn7;YbgEeU6*U;X8OU>Bz(r#J^bGTzPY5M{nw_MX{mqckoTtD39n zv2)ei;}ST)r&_v5|CFbT)$F;7DMCM+Kt`hjAQxaLEdi$GJIVMr$`7F)6%z6E;NB+I zyvPg{BqS8|^6dDSZw+({SpD*p6NskQVN7ZUf9zi?_qKJEl7txW-gWT>%+A#OfoZcL zUKrUKjQ=OEgpABtR=as4CUE2c)f$8gBWG#i*mCoyin;cR==2%N%*rG{?AW1CM8GVp zEO4AcXn5aT`-=>zt~Vf*?U_W0_d131Y(1XO;EIwlHPP;m>in+)66ChTjf%d!uQ;-H zCl{y-%6`Zf*6A%$=JrbElBe_Dmy--8mIj6)uF11(Y|wbZ8ql0A=$e@qb-}=YP{jtI zMXE2|Ax+vG&+lK`$l~I68ya5nIF}aCp7*EYby$KiSTn46%pCfgU#Fs%r$ZfAHpN*X zsTvO*xrICb{qp#YI2pkA*6dEZ$_tKl<86V*>LGiafs3P<(+uol@c(o=Lk+O8Eex1B zw|$ZvvzLhmgGPy@S_+ARq%=Qj*b{+=0Fj^NWhI9=MQqiG_P|Sf7`1+{$o9M75-u^Q zX4lqg3W_(?yiR((ZM4~)Z!EDDsIrD8rH7Skz*w}*G%RgH%bDlpaReXDg zPCy%fw>*oCDWP;GWfo}x)=F!kj9_a2QQ$8W^Jgy8PtqX7gh2X~6QcFLGkYFk&k_)6 zYLS!UN}&wDGZqhuJ89q1S}im1lx>?Bi;Ia{874e6k_E2@{>D#RyZYIMdac&tIGV~x zN)M(Zl$iWvqkN3uSgMy%0V#wz<=8-Jnl4?y7Ljk=jFQs%LFT)$fSMuNNoz{ zg*T*3ud9~6(>LuQ_@|zRZXIOt%;JIXh^YiC^&oAQ>B5@b{_?gi{#*0-VAj$kurDQV z3s~ywT(5@X&rSYy|5Si9p;R@d#v6>(DDdpEf|SW^E(x4#>I}Eq|D6{#`?|W*Kd3?h zXi7+`x&oz&#o7IW6@#M2roJu`s(Trf=SW>O6GQ8S@akEA4K`L@sdAG^q-&x@7{eXH z&5rZhYrud<*%`aOBIU5(v_7?|Xn%J;NMVPWdC9^;voTBQ>;!-UXuK65f^u9nfjBoP$-Aj&Fv_(NS zvaKSI=e>uJ&)V$MuB}(0edNy2_yMiKi_@GnoDZ3w8`!+|Nz#^Y?ZgdN>IKRPmTe{Z zO{_PrJUUm_DUF_WgGNfE=aYz-!;<8!zgk8qhhYa~v{~%0`Zx>r;Da)9m^0jegO~0Z>Jt&|5Tg z^eh0k7~qpbd4@7XM8hzLHsK2@i4#AR9;LPhLnj-+N-QD5NJSaWG|xA2jQS6%Nbl)h zy(J%Js!11>S_>P77X(30ib#(Nt7b0Nr+N^loB8kKcFGI`U3*FYekW`azLqjJMs%}J z>J+)tV}H1<7{5mDKE+5a?X1?F;Igbt2IwiaMC5TdbC}l2RBmdpLQG!b6ZrKdK_ntr zRWPGDBdws_P^s{wt5aSzc_wimMqnIenlOY%J!6rv10w?nwhA%JPBOY5r9Y29;(J>R1Kd|D* z`^#g_j_RYufY4V_7v*RSQyBi{`=!VP+TpsL6uz9H0Hby=Yhnt`rXRAH{~acJMZRoLuw`UzM|eON!1OXlB#YrZjifk-;}IjqE_O60jM zt&K4+Ax(sPjichTVw!1uhak>R)X)VNqZ%mxGVkm=_HKB3#d}KrWyjlS&01T(??>pq zp*L=Pb)?q(Wx9&0oV82H^eL(n5%2}cFWHrP9)_hC1juBKl~fdtcf(07I|CuvTFm51 zY6-71hv4+C8qCH3dK9ckh4^mGPrqhMdz@_(B~G&yK{rY)AP=Xfj!|{GC1^v@m}RYz z{|GqFGb%M5Kiq56%;4LG+$As7l7PRl$+(s&7gtV<90nK;akg^ii%hcuw7o!urT%i; zsE`qZ@+Mk)n@Ot zkDL7bc0~Ee{$f`Cb#jOhMW$W>;_lZ|Zy4g?H+#9;1AiDiU-PE6NN1p7<70O9|GF08 z{{orbg7`%2YsChvqBt}f98|ABEv4B=H@MW~`mKDyWY>_Tl@EfP%83OY?ZSrcI!@5M z9pvoGa*OTKk?g2jw(RnI^D|BX7TM#ena1$FmcqK2+o^AZm|LdhItw&KBxl#QIu$50Z{QBCLzCwf^B1IO`g&cDGXkax{)zuQEJ)qb(l#BJ$HV+_|2%hqP7A)ox)dZbZOV9E}j>LnCAi= z!ULO1Gtpmbo2PXmVfGBc8u!8;-MzZ)W%D-tY1RdYy0a%x!bN2>$CkpBS_)cgo;n!0 z_VN1zg#+ylLK3NP%napEUl_9plX!rrQJabnK4kmNzW-&Vsx6^NB)7HIe&xJKeL9{X zBMhh_Sx@?0&~N8p`L)HMjN%iw$Z~@|mZGRt(xk15Z7)?qZC0kRch$|9tTC>Th(d$P zz%!6Gieg7wo@6_D+6^U!d3kTRZ;-j zI|CgTX39XjT6W2Q3-p&-|B7+$teD+JvI@`K$G%FRQ(Ku*pSN*ig9b{H_tNm5@u%##} ziEhTv>P*Gdez;>H5^UOk{PO*&yP>(PZEc?8%y^L<<^|UI zTyh_kl$|(kf2G&kh=rq5 z0*p0Y^0FE-RU1GrlNYQ%G8Y42V=cm-mm(+?wZv{U$!aHOG=^Wky?4YW{|~BM;b?i;dt2G}cpb=_cC&+3R<-j}IvzE@fy(TN149}`+>?iO ze44Kb7U1OQ!^_ZUzQ6-$i;~gx^-4ujQr4XOoDZbm{HFuFV%)n3DR!P8aWUD5)6={+ z-#qdWk=Jvx`0>PKEl&P&nSM%uU*53+nNTeHN-3nAM-a!B2O8<=1_`tj9AOW9O>S>b zt!-?V9U91%t&N}U z@1e!6=RScBbdiK1KsIHV2G&T`;Oporz^*8#%7$aQvb;~PRc0C z@#8do=DS>Ja{qiWEZx#3Ut(%m28bKCsQ(PYf2HzjPY!-{bUf`hOQEyQKp*u*eP_>P zQo$}$64Xuu=f|MviZl5k6v0ih^#oJA~_U-cYdq;4l+FiWwFiR$den?BR=1cbaY$eDt6&#A5awBnSr0<@U3lH!yebdk21B{$x9)YZTNysqqI>P1$mA% zg#Z^ESLJ;%8P^B6$tQuvG2=>d!)*BFn{&>?CILnBfphiII=(V-;jM44VCYn=YB`p4 zxxT7;U}ICV6^Y+%kZoagUSgE0jEXPR({t`W@{8QFbvd&`UDq3wHH4nV?$f-Y<2jT_ zxn;I>4F%LgP*%~Tt7e#SoqOQ{q)YpY6tsZ@E$uLr9H z5xcf1r%>9N6B+39w{LC;%tG2`m_&RAcD(JZP_Uz`)7$HEsm_fekgRj0R0x0;j*Qy< z-&<7-NsGeM*tFC984ZA@*H=F`^PnfyR{cA1pRZBgh8su3peM%RJKFVtFbiFYl5fH) zewPaVk$byks2GF!xoZCU_Kva_+Wq$G3B%fXH~Bx`ywHMCCyG|^ic%K-q4w+q-rs=V z_Fpmz{50p=No`~;KT{E^lozIGDfgW+r@~w*@-iBy)}5T4+@G)Uxh;9Yi(etsX|I9u zu5Olewja}VHYc#}(@}9^iJ&rOoVTVG$TG#LJhiPqn&fgbC}*C^%11 z@pmxY#2%O=I35Pyc&Vi8{u0h94DK=wOgLY2V)OfW_gquNl;?!ga-ybn^B5!`RuYdw z4|fuIV>{Qtt%efGT}hhR{P2SJ_}iBm^u%cyAdfx;f0rGRvnXaF8G zz>sxo52rcfv?4E1;!~{UAo);iN$OOJVk{+1PAa20FZ-$Xc#T5uLZsVO7x7atG6I15hbVJ|09gwaMBXLpDTb(lhgz+ zq>VOA)4rJ6iltl2B}pCHu+!g+XdZ}TBG?wiFo`Hz%9-h`Zam_4%?b)~?l-*DJpSeVIU7S|AX0yN&;T5Wku@{o(d_+LqXa8K$aDy_=o$ zviWgsl(G8}hdJ1M5CQ-Is>MiwNe3MI#?;XGqtZ&{nRS}ngo?HHCwO=(upqA0Exa+| zgP6j(9X67-C_ha$A0rpMQW47V+5GNMmcC?3hM~NujLN3-14a3mU0_gW*;7vG$mb8Y z?sNXDdaMS4KliCIx(k1RL`OVFv6eW9iKNU1Y&?1sPlOa7LVlmqw^DZMza5jPRlurm z4o)GN$EyWG>OwG8Y+n5Pfa_(Oe?Q85t^hm$fatTS+h?T%EJ!RBd8`Cl6`9C54hl2L zP;3c(wRbsbDtE%Kww<0b$B^Yopq|;<6JZch$tk2Nc^k3-z%Ink5D<4IO$8!`kc8O zINXizP&+H%_hf{=cH3;YA9!52aZdU2{X}?S_urRoc34P=hW-0@+qU{oc>y4g2nV!z z3^@6(DSc#Q20#IT5ymQ(@IithT~@{~QZm9>OJzhr5AN!pN!L91V=4_l(0Rr5I;4KT zo51e>MKkP`n|X&mzkB@61A2d}QW;M^{5f9S@rhQ&Cga%DT~k1EYCf(!Bk^Ng+sRE4 zFFR!&-^}`_b-W^Rn!T$Y#cQx}s6)c-JMr~}9HHC&qXU*@H~7UbBXu}^vxL4f7Wl*_ zcq-P>Tf{o|OW6DX0&)DFM*ST50l(q=>QgF@09C1|kY%ZV#T04&zo)88{>aYsU(Jf|e0}UIpM(X@ z`Q2B0MZF){w)z9Vg+1UPdf)B-=OVoDh|p{*4m;97W-#*th(9x8sYN?ZRQ+e>WJQ+! z$v7|pm96E^e|Hn+^kg^+Z63mp?*|svpV~g8+~)FDdCDva^nl0=M`}3T!%P<3f+L?Z zo*jf~XH!#8lz?yYgXP*oSu+O)tJL-(@Z^`Y`nEd8J-q@Nw0J{b6(jVA$|r zy4dwqqIIhw1iY5NzL$(k%HE=lJ!$<<$n%hw+X8{lufW6+69+$(^Ed?BsI7XlC)O)% zkzwn4Uo`)v?D>#84?lJ`@T^oS&JpOpl$lzb{SX|YC@K!Wu|N#z2h#xpwfSBn-l2}# zKnDl!q|jaLA#(nxP-~v)D(e|Wtsj+^+aK3xD#uaUjc?hM-RAz?d&mK3bI2aOJ(Z&+ zGRoDCK^|XO->?DVzGq$gFVt7UsaO2t)%<~qh;(ez{B?=zoe)VirGxp?Mcgjq{Y1^I zm8#43pQGsIh7;0&$X6e9KR-NK>BwKGasPWt?5`D+)U>Kj3pzmf9dbNKAxq2;3*( zEzLK$RJv-ixEab>rmVA9OES>!Ra+>jTTQ}8<12R7wtye(U+MeY+E626e=jv;`(#9+6CD3&pVMmRCm(Nf!CsAcbHd z3V1aN>hVq~(!(VQ(@Be|mrSw|IE*o$9hve007eF)FvK&#Kd52=(E9!@5ak~$N3mTW z1}wGN8{;dgSty*D1Ia98vi~}zq^1QMp57gFoLYpVX%CL$M+x>lI$!3Ab6b{OmjeF_ z@I-YPHSu~b_Ej#fj$nBk#wy88-L1Ni%g$;4YoIA3`8#hpmMuE^+ZMv@q_2@@ilfzj z;3ScZUXU)%{q7uqb;dA zThWR&SSZd-cJp0Tn5>=4AOLT3lMV4X9E>KxbD6?B+^w-#A{3KsvVO_RPXVc6HBQJP zG%CKEXB6t2dFgG#^Alu*U-F#iw+gxxVD%!+DIKDl)mGz(jBQ~ct#n>DSnSRt*YOvG zB<(~XCw2IhS?u~qmpNYsfsdcv(Qwo_l!&4yOu2cN+XoWug9`FT#kIq;seM?GU zTItxaeluxuMxml_Zq-2?7W11sy)hcN*33{=R427jA}03_s>sykPIdhUCyILuCH4V3 zWH+JzrQxNkO^r2@V;X^1f1C#W$mXZC{b_|SP0IL#vIzw(9ydd+%wk%tfQ=Jjq-`89 zfK;oD|K+t#ixetcEuq(~3r)#2+6dMyfQ}MA*#7SP8(11Uj0w+`pP<0m;qzwL-Q@KA zyDLg@Qth(W^O#R<^Yc(RXlvfo7+uarH^}OUf4j#EM-+S|cL@mbyp= zY~GKKq(I1oV+V@p7_N}8192hQybh7jkaAL{?Ej*&l~m|NqlwX`(hF&hwdg;IvX1sL z2E@feW#O+KLozNF^XUM`_9Pae+nkDr4_YaFM3s~VBck*E1XTzBjlI+SN*KY?xbP|r z_q`8GXphBnkh!|NAJl-9tEl%!`2Zix;5c??_NutY$)-ta$vs*jZ4z&)X*lf(jhQ)pmrL@8g@`!=<<|%>U8gDou6QzTQY$c5lXi* zqsr2}sj#XrhFbVfWws zP^%X$2f^K17UOW4ZOcDjI|_BCtVs4A)W7F)fBR3AnV?xDLt|+L)~dcL0poeTVRbua zQ>CujIgG(tU zs@?{S4bv5muxURrjg`m5eX1aM@8aN|uhuyI2$96tl-Ybd3dwf>U^>O=2Jwi>oK=gO zV&cNrYGQo~`rasbh4sq&*|<1&;UnK`rmbF=hd2o(zSr+k<$2CpbKqccvrUbwx%hXD z&tJ|T&6-YiloVXUdkVVs;sX8h7+SUWgZ{S}k!{oxTeNVrDx_nU0 zNoM>_le1f)F>BLthk@svSn>2&8cPTN`@ls!JLSC5qPnXSc5|%S;LrF*tVd$0`cSX5 zb$SF&E+5YXb{1pF0(|iW0KVXv?ml%9%lVU4R+ zN&c|-rLLjKUq_mOD@Oh8-OxDL!M)lAwai(zQ^O}FMxI!|UB%#^lbaVN>X{a0du=)X z^r!Lxfg`@YIH-|X6#RIxDCu@gEaJTnJpdFNYsSXiu<&&&sdz>R$_sZD&}1KVV?LGm zs`t;2t2;kw@q?z`y^;05dsFl_m*Uj6-aSU$dVtEgc?Kk>$5(M>{fUC zctVBv(&QXXj3KClw$^bzKasVCpMlTg`4U|5m?K0ejwIUUpn*;s>pfgdqsWZPH;Az+*+`c)zrMT z(Y_|fgE|ymgjRvvSS$Q>0+Z3f%qUvp%!v{!g=ZQHb_2Qs^0&Ya$c$pWKLsj}bc7a> z3M848k?fx8mlTStYsAg-HW^qiCZIZdk>7vJLU@pdMrlT9xw6AF?5B#5@eY|>t+hEk zf6Fm?C8ZZw8n7}lr1jnVlkGW^*aZvq#;l)A$`o9wl`_f*o6u7R7B>kB`3;+11fZq@ zA`g@2UBZD&qr=v+=$PAKS_9619LU4vWMp;2uvS+(5zYi)b;Lv zj&BchcrSjTQCt5`q_OT65w9Q_4v=Lle~aiGJvRh;TJmhM5G9UC_BF-$T<^@Ly{o<@ zr>gaocbLr{ny1-DKZO33KfZRq{`{s#m=B-Q#0PFn4f4t>d>|cBsp8n1mV~flt#?{r({@!VC z=wS+v$gr|qI8iHHT9C0D;nr}r#m}%|c^N>@L}UlfwPjG>?WM?)&vE7%+z3<)l&W&2 z@-_=_j|PyY!jN*LsNoO1B~scjuZKxYT#RlsjiCk5ZQA}0Tg|SCvAp2=^N%ztA2H+k zwQGOZFn8p{uN#5*6lu{Ktm0=@L8w&AvPG~=Kk?)IX8~vwE(#VBQ4#R!XRIa)@ME?U z2;k%;Ct+Y|!s($dA7UgBlzJEh5RC&ty*EP##LOHDkZ(W+g6x@`88he9i@~F4dwlWQi4OEIj-JZ?7AJ@F^xqV z&@(0x0J>cGQR^3h0Ve5k^cTuj#JM$c5W-0pYdo=fExy$PY!V?eZTZy@I^AJiOo7sA z5=Qp`@puCUcwduDkuk6{P);h=#?>2ZgJ@Cqf7c#l=8=h>ixb5k^E#Cbiyv zIUa05tQW{6K_=mTMiN|F>ld6d3}&_U3Euda(UH=q5)_0TygWPU`~Z@Z6j`~O)Z>Z- z4PnHe3*#HY&0yAK3B%aC#fG<(X4+7yInJpg|s^fTBrwT&7T>}qUG&a*XS1<7|Sl|;JDo0UZ z+fZR}c*kNF*mJ1@5_~A2>eze?_V%3q(LmZ$RHgiQR;M;IBXI(Mw%Qca0D%wE9oN#>Dy#jG;OK6M0y3kD!h)itCah=@IV-hgeno2H$E?p2Ov zX&U>5WJ&TicWiS?&5vK>m@*t#^@p;U&CUuAMs4#yKv57J=h3GnVpIJ6J6RFG3^o) zm^h#j2H!-#p1#a_zp1){<5&!RatoDTZ3&Y8oBA&m4!TmmS8>iJaCx!da^UHGv-~t3 z?a>^I7a~f3gasP!@)NN-W4JukRN?$KV#R4AzZV@j4Wp8nWv})J0N7EPO#K3KwPc2K zOH0G^nn$o+zW!u`<<2W*7{3qR4omWFIw6!7mLrowvM&YYLpYDr%ijQ zba4H&Eyg{NhMBxPzY!i%E~IBER9;xu5?6CImjms0X0Wv`IgSa1osajQf08Qie9M3; zJwn~#s1Jn&d)hyBtLR~`+WD0za%1Vqq~1$fJ_wk})%~T~(8ZFrU8 z1^rlG^Dq7t#AFTA6fUGIEeFSKGBxUbq_Wjq2xCR=HMfn|-dgH{gZlLFolW#4Ex9 zWpwevZo&ruuql)WrvTZMYP^})gElqC~5II>@VW=-99^%^5Nur*6m&d1^KBBGV}l~{ErFE zuHyv&r8yjUnW7=cf6fIKq_8$w9o~@3TNuS=rN$Br!rCp)oca0{jY5SojJx1FkNgS? z3yIn>9<4AFUqwl^98dIowES@wj|d|%Dj7cfWfvXqWF~0C>rr@;){RbWagw6A%Vxx} z&XbAGBKZ$HJJO>R45sOvnNE;whsQWVc7jdWPb?Qc$R+T+gc3E5Co?P8ZGgM+`A3Hi!4)!vAe7G$%=pb zYwD}2wl2WPKM?)}K+4XThU%CarAKWnkD22Jc_5e~f}XYchziz?2y@vH(^q2I`9GC^P-O(5br`MuGY@u{p(sQKq~pgZ8Y5ZSH6b-F zlE{Iy)=6+uF#`JhZ2z%5J0~JK5;E#pT9gMLnwDv(X{7(GIfGudQw}Gbd6}vj_DFMx zLX-62*GHeSjCyRI81he@^_oO6J71IzjrVK;@jCjhu}x-0Y14VolRKxO0<~GsZqz4@ zKWt;ku^i$u1a)N6WIhr;eG}{;Ek{VlVv1FaOic?N8)Hl)UR2;Y8)!CKSz=y$;M@Ok zbe3UFcx@OTJ!*`U7DlIZD?Pe9r8}fUknSEi5F|#2G}0~IAtfDxgo6A5sdv8nw2!;4 zo#&kUso$+d2SDPVUmzXMfT4g?p5u=q--lF_y?&$3M#JPCyz(DMIjx21!&wdl+Pz*V z)Hm6wHO%}JcoVKdeN5B!We~J+et+gBc6rkwHvB`v;Y+gw+r`eNp|VeREJX1E`ctDP zFxNEGD0f*J?SD4}5F%41m^ls#g3f{|uT}>;{$HqKhoVgw>HLhN^0&if53zx2fC6-o z0@%IM99iw@0#bvPIe0`9F+yojumR?GK0gvBPchz<34!ovqVM_GMA=MB8xK4+?C<7r za(62~yX@^4{YlR6E!!{qIePA)bx%+ZixlF;Ns=F?X&wDPts4J zd)K=!>s!6O?c$BgOLbRit9KglWBja0A#yq@c+vK~=i6#$z?N{<<_it(5%nuC_qQZ> zuZa2OpA6WL9wC?&a!FfT{pmxLVnyME7>VnEC`x&gl`lRb#v>u*B(T#FL%D&*$O~y&>x3)}vv185An@rD7}F z{qT|^R>U@NBBN0LTkd|Y^34_ajp~?b4t40doIAo@b6u_3qOZy*2n%wo+ z4|x(&lMvv-%p_^=GK)x9>f#^FugA4b@8VsZhN`P_4+h_qifrL^Z~O|`{$&XO;Dm?E zz?5DqERq0Tcwg7p03&e3Z4*{(I|n%NfEE~xcXrTM&zwCkE?ia!huCvV?BoL#G7@kz z*$KV1u>J5>*sVM%iNInn`bX6etj0YPL@artlGi#4)+s;a&nv^9F~Sv*wQF9Y&+9HI zmV~E+F9>V2E^DPHcR8Wk9Fwz~|2@GfJe;&5a6U7p^S{Y=;L?7yZDH)&_k7c6%NS`O z`!-8kti*{fl>W0^c6tPcYP#YBm637^HnZIHyY#@nys|nzUQi)V2qOwEuLCn1Ykjk3 z8N@F%B8kuifT0DH+q$?FMNdv*{`gX_P>M^I+m>qvQyj%drgy1M#9mCw_G`bW!zPMS2|Mg{;6PMDlv=(Y!Gp5B5Um&so zs3YL_tcr)B+CLnV4N$qXAs}$$A)yQX7pnN7XfayL?_DU@%#=H39QUa3{Tq;Xsk>t^ za?GnSNg}ZNDFee-L|%(xe0Y*u@mZSYbHZ|SEBWC&!W`hEiW^WWRV{{B)8_k7Os78N z^>-))Z!ND~T&&fop+1~t%CCvD{mJh!WMZ_5$1@x~!OnWJ{~I*+KLGkKXdJTSKwiN? z3PeZgxXhOG=`K)X@N6-1pVqlz^);(}?)~jrz%4&|wj~$klMJ<(Baflqk}+Vi2UZ{W zK=|&oY&lqr4EEQx6P9-fegE<_h?we|(dnI7cZUKfbKv67Z6l>~$=rFEplV-gPy1q` z3S#CBgR2A3YJM$qX%E4jExYMog_9TJSZyQIY-;ar*Xq^I1S7h=`>#rt%hTLv)nKbi z_4YE;I2FbeWyzXho@u`blJObzWs;}?12#{9#Nw_0ZULyjhLqf&o&hsM(TTuGO0-Ao z-(SVkR?wm`{g1U*Q*Sg3u0-`5-aSgYqac>~Z^fqt=##Ghz($W%lg9s2QacX(qz1C> zw_H<_3+NK74im~RDH$s;avS>)|B}7Y69c10@iBfzl0gy75z&daC|dw@>OQsr?3!<* zIM7C!#@}4GE~W@t#o7hViq1D$*wr0u2SyJ)AFkeuQ*FMD`eeGPBPSr-Vk0>8%1=cp zft?6}Rb}i=kgZ@JEHu(zOB#z6d#A+A=xWW)eRu=CV6R6_7633+E=Dw`<*GIET@43gnBG z;QbJ&6o61yF7IY5!ju$7E^CKE>;di(yX-3+y5M>-R@4%hSts?U;r*Y*6cy99HUZlL z56U}!ov#`e{Vm9|U@OwP;N$kXc$uUzyVgotatRVIEF z{=L|hzv4oT>x~RLNwE}TAzmyOlGuP5J*!VDc#acL4`{o&x%vE#Km45Ypa5Z8X=LXN zgqgr;gBs`G=*s=k)zKFCoL<$GprWOiawF=cwTf*nUy7&WE6wP^v`n$gv{wVEjlmNE zP+0)Z8qX!Xtaicy;9x6q2~ST?BOsI0@`OiX2ta|* zmxtU31;mf{>!A$W#L^D;3#A;y=;szuyt*k+0fk(t9XA&scYL!rNY-4FW2w0+gPJ?) zqU2&{DfGSUX+E@m>OdO%sb6KLrWwF#v_0tBFGW_`b>lX~6QTeB6d(XsoN2p?>A~u! zl*~0hkD*TMJ6@~>5<26)A~{Tn+%`ycL_*}imxW=syf6%cI565E{p$H7&5&#mZ8KlC z44rM@w=J^|sZ%dHo|dn5mUvAGauePdQDh6k$-cF}jqf$?2>AMj$89jv-Ql8zQX)t? zX|MNpe;)ud9Duq!_MRa`jg++0mrCj=jQrH(;G@g>INbT?&m;JUKv`CU%d?Qe90$iZ zi=0M`NtN?5|K_TCW`qb>vdj;^J-ka;^hJ$D94UDjj79N9dic0S`lB<2GtVHARRCRB zL+@{-FAu977zHA_xp3Ez$ip*1PJ#+xeNKd=t@OKKPGZJ_C+Un-MNzyr`f2dOhlBcZ z@FwZ|{qb;;fc>XfiD;pCeO)L>q(i+8BovFwNXzX#Ahc2`7k^3u3?6=~(TzL_T^z?Z zjTY!>f9%5PsS7U<9BO_L*%+=4DUrJUa65h?JB@)t2l?k}&eE!0TVK~7F7M=_f?Acn zW)!z5J0IA#4EH)|{Z~IqKg68O9Z}}DDDj4fw+}8XoJV>Qi+Q;~`>Cd?GW!7KZ|O(MQ$x$1YI8pJc%-lk!eRX4A zx(1l2RV0=cHdTf4(Wy7^>tto!O4SjkyqC-AZ$HXE2xp})w*@PUd3Qq&X5DRCj0>a0 zQ;y~mNt8^C(^mghOh1jsN%DaK1^}!xy9fW<+za3u{$O) zLt}O~)Cz|1U(QPT?)(m&022*g1TRp&ED$jvYkL|gPL*@T6Pwgk<5{4pR%#{?;5umc z2Zf-2spgEX$VxE{!%4j&fRL+sppjN}`0!pVZ~Vd~ad_N1nTVW;cjS-~AZ6vrh`F{3 z9}{Qz-n(wWj1fd`(YGD!!hzbAfhuz{?(rNp?FTz}n)rDWm_67;HCY<)9#hg^z*;)^ z(|;$t^+~wD@!ib+%t*e*=7zWG4eGuw6<2ndqu5)!Ac=)Z*SBcBBvYY*$POfv;LlE_ zjoD`P`wuIP=E54%5vKLjlqps0(!SeZ?9#*zTChBcOjm9nC?SXpe}cu<(kU?j^||HtQJ zi5%W4xG+`HBKBAgOJJbtnw598B zj-M7V<<}{21TmPNg1?#XH0=CS=L|?Pf&Sc2W~v`%IBRB>;R+ymOC;~d)$(Hv>veV_ zQk6@`GOCLkhEd!1-LDF@a<1LC#tl3Q}(0$!G$Aka%( zIm-y@Rg8_hX@%YfIs1xh*6AD+B~%xeK+=Xa>x%WX(oeCyV;UuCL%9)<6hx6sTv$QkK$9K{S1so^zBF50X> zY^gKL;2H+KQ>6}QC07yBpN%Y<_Q8DxThOrkPb(wtD5qS-h4!$NH{RXCd9IlYJ z42Ovsh2kua8G1=tQ3O6n#yEDg#>051_V~j`Fkgl&8;oN?Sl&2sJNkFnM*M$5I-qqw zp%xT&AIypj#(vr&p~CcGaU$Aflt>Q;z<^FW;R-jE{PBF$^K+(5`@RPC?Dj*Ev+U$N zKebhzJHG&Avlz~5nM@je$-z&rU^Ez_;;%WpKv$>u<2-f`(!OX=;=i3~n69S{CPKKj z#Y@unSw=Eaphc-*0QSqT{>cQqP&7ZJWh@)z7sq2U4c9#iwJ}SjI;6+IXHK@)lZl*x zCw)bscNTi#`s7(XN_?PKMz!>;Hauk|A?@q zNa4k-S(Pw+ORvpW#k9NBs;lfXRse2tQ4AqY&{T+Z*m|D%j&zsNPGUUnleXC7SeuQO zo}45>(RErtm3BkyN98qG3^O4O_JG|gw;Wz)K|`hYC*Y?*I5zB0l} zMck!R>k>PRRyGb@Q7Q(3BiyqkP&=nmgExoM4evo+uTQG!4SaedM&4URd&mG5UD?!q zP53Iho`M$CZmsy%pdSmo>cqPhR1-qOcx)xFy19bdWQ2wOuzXI-id~GV5%#oIj0_xt zI=W4MzBBZWgA?T)j+AtEtp8m{FGhG)*i7Ux0Hudz;#oxmlH-qzOf@$NXw=4|WAz<{ zk=5y_+#eXB6L>9&G82jb>?VFi6VvP&88*b{<`hHA!g6XCh8H%I&~m@8l8Y^p6ecHg zoRN(O#%^#A3_g9|Y^pYAjentv9H6N{HY%8@9UPoGKHD;gf>z&ZoEEz7WL6&|tvytQ z?;wc|w#MQR`evby*66zyv4GrW`vQkACt*5tI~A4_c%iFST2_ioxvO^B2nFPP3kZ%T z3L+0}?Fy?*L1|-B7#&lZ@gyR%7wizyH^>)f0sT1~iu#gOw-`lHR3)CsC`+z3%D5&w zSV-raV7ij+ySg;z@UZizQD?GOIHcf?W)<5DsM{xt%DgmGODY>vzdl_m<*#T{8^X6~{p#Mun)5YiEhBIasIYd#Lt*l@Mz#W@rT^%$4lq zdb)_~*H$1D?Z#vE#Vh+U8;tfks4xW}V|b})@G)?JtB&}H_idPhCYVQiQpJm8E{aEzk$(^-)vak1PA{@Nts!cy@Xrq9Q;p;6$izu7#C;b9 z6}{h>i6il3oYv2ZB92j!pE+PeXjAQDJf@a7JV}ux%{dFvN-T$~KKSnV1d8L!kY;F# zFt8Tz(NTX;w93l#eqn|_SmS2fuBTECg|&!9u73-U=J9?v&1+W#P7JSqFWcw7-o(W| ze*SDGrMSF8jj%NY6vkG$q5_D8!T0AgHxw0}4zZ@4A11XJT@Sn> zhKP{cnuN-%nXKH7mO=8vEGck3(`k67nmBB<>IP^31gnO&H(=M?Mu{c27)X_nlXDlw zF2V|jaFhG<*J&G2}rj3;dx@7GUo>P<4mmOLH0 zt;GiVW=-%HaI}nVXk3Z%8pQ%@(4Iep*_?DTOTbQT<2WEvhKq*C>QHJRMw`wNi|WA# z+uv3|0J7mxnV7o%J3kVXqnMRMFrj@$k7i=PpU#6%tt~)km59acC@;hw54CHscdS&N z{@%FN4W%2AT7UKL(ta6=)?uuDk9<{;H7^az2quuUnaUZRd*uj6#bY-f*Mu%EzyFUB z#mmq>A0=L8#r?m8xv!`oFJ|i~Bpyl)-)>!%KgScc<6wTVh!Dl+lPO{plJ3IA+8$t2 zQq+`cp^~5HA5BIhkeZiX!^5P~0wl`q;}3OW!EKKQCZ@1_z$&kWWgE4n-OstwyaIRf z8yV=){(k9PVzRhESSJ~^@vCuY2R&chh0HwP`LXpt@qy%t92n)6O)1;{l9*u?tw@(Hi?Fo|=`?HFoc^ZbRib9*h zr2i`;Z|6Lh!DM5GdD4zHL=aHqFw?VYI@lyE)?mCkSZvbcn@Zt|MH%f$72#1y5e)uYpff(FZ_1nJevZNZA&VroR@67SiUrK$7Zt>O47uG%WCL z31uQXJEW))6I)?5!&vRKGeJY2pHTXV1_BF8J)rZ6; z4{86_s2djw1|ve51nZ<@BY3|Kurk1z+rNxA0V~$zD{>F>GO=NqFQ1L=;%&^E1X&n3 z4E*`mhT<|dH%yC%H*4=zlU_*9PnUiCI~Wh7c(>hqWMc~0ey3_3#J){_4JP*H`4 zC@V<5&}knE3cc$>L17EqCH4eF_Gi~&Dreyb`x_$lzn&qlD2hADCOIEP&KXlCzMGQD zG0(@@;-j0ioWz+ZPODFFk!Y?dJr<*ldgZ9*{T5-DnwzuaYW|6rsQpV4Uvgr6f?L;~ z&{SioqEYkEK#j<-lS4KgIMkMqVW;fiEJkA)p^xw5sC1g+R+L-w!zTj&BQgBMa}hbq z(Tge-1|(!)E&H!I6y{&^=68JYh*jvZF5fgqWvy$+X4~miLE!XDl82p_XYE8tb$7RK zV`FnSEY8T6LDPQf$2-1{mD@jo{;O2|aGfz+NlR1>!HGW4I%iP@33r72wm@QB*z3sT zbnP1Sg4ct8ef_~2(V&E}kmmE#@l&>0t21Afg5t*ID_y35Ci`~y>o=yS%=>~u>REL* zK}#Qwo?Q@HuxRbzTHCTnTjis0bdguoO!;uIT!LHkcdWnV%w>n$;o*Fi;f1X6OChEX zh~Rc`g`9=YP`CxZ!(2PI6Gun5 zmij#|I8FqG#!VJgHK8X}5cdrgo@EWk?|1TU!FLJ}Vfe*b+4+>!r&w7HZ%4oQFHNyI*OAidF98{dgjJJn44x>!f|4rB z9aJWZZbT=Ki&e6KKClUg7JGOPX-$3f50*#Gf7nQIHd}Cnr_nbvc$e*~C))(2{+1;d z?=<|Xn%2WXD`$sXxBI`zi@VX>PfRL*hY7J)_Gq{SW{zrS+x?bR?5w=aNop162KpjX zrsHhc*;g~q4_D6*Ay|przfQs}lo;n_D6ccag~MP1p}vWwat9})CJev1vklbVrE-W< zCxBjOL$A0J;niwm%VpKT*kfE#yqoA~mm2Am@FKL(K{WrdLenbG@0zKxOW)JLhg#p4 zD_i*P=kEufCC3u3em{_j$;v^(;MrVtrtgYEOX@`@W$0MLP&K(>8Ehq(al_mGF7Air zB0|3psn*aAF^C|(DQ8-7yk0uBrXYO$;VEa_m%(%O_OGBmOp2dQMX_1~r>~xF z;CQzgbvLz!E>N)L-wTNZf+QKv-76uhpSYB-oYAoGkCDhiBxWm_(-FB@f^b%o|DGUh zX*MZT2$T_lm07J5q_qEcOhc9YOg-(#7meTu@RHQex?***8B)Ha&3cJ#O-bv)CY#*j zftwtEW^dKP)84{JqFPo#ZaBTaW4f}D`canUiBFax6hIgWlNWg}1xx_^GQ>@;{9)Xf z*W)l#`Qf_co7LTx`dLxg{GoDR$koARi5Sk08_=7u*qv7#xxS$LA%2*(LB3C1PfMJ-$^n9IelF?~`JpA#; z0Slwh?^aRDQP*y89K;o0QDU6aRxQ5s|AMjq+yDaG!j;jOpE4&{wsJ zuG#s)L#O5P+iel4rHpI=JMrfLm6Ears%A{&?!~1*u_#1`SPpWPfT$;CBSA}Q3 zISVWQ_c6UO%Mmp5FI2fh8Q72*Q)Xg+ClxUb2WTxpk*)k$*9iT1r9*pH4yTn<%sYkt zul*EqRXnj3TxB%Fti}m&(pP(_`3b$0&1VM+l#&n2ou8g>np&LPh0mUf&Jog-EKqt0bdzd)GKMpUgtGzT2m8UnbOkuj}eP(kkHSA64?ygTn44(pj(s zpNPNRDG4CQ(yY=P5Ia9XZ1vbf$X@1KjLS&iwi8jJv+vE>DYajnxP7hgremr}A{uJnBBTnzydTC}$wz=@}#5Vu* zS6R*>Lv&NNuF1}Ne3$pbYaMqk0d~#j>^mi4LUvU2<(RVOy|`2|3zQ_85menj)XUf47qoTb@E913qoYE6hg3e?aO5^sRZD&T$eosAbpw1s6 zWNo_w$B(lK{0)MK%?XuV16P~~4##*`;rm~$WC=TiwIg?wYmFB(PKWHWOZ|wZ9{aEg2cQjlzo1ZFY7M8~uRmij^N$o$)-I2rf zmHbBgT!J#hS^gH$Xw7S*ElHP@r)Rp%#4U#TQfM zN&TAS7XcjeNqo7e;`Fb&6%5{}wEaIeKl3?i863B^MI;h}xVRJ0I4ivR!+F#6GOy%( z*8Z4_EFn9~xpZk()NH9G4!<6rd!=S~_w(E}Alp%VcrG=ju>oRrnU288{3NZdpx)-H zEJaWwTt^nd{=;f5jzDvOC}wRi=^#Tj$C{sxG2OKiv^RHL7>uVo%O9GqmCm$h82%~S z3AT65mZ@0D(u6mzY3QHBNL~yS)fyWrGx$Ry$M$zi?Gfiqq`H<5k54WeTy+)uFw!m% zYQ4yJ^5vlHEd9(H$`e`1y4{x=$`Pyk5k@jDONmNU+L{rGjT@SZNrKB;yEMF~UzVr3 z?u*ksl!p$iR$T1V2$h*HiIqxJr!_d>uPtI=ZSa znszw*$lVPU1XCnLK&!wScwz9g#Qeug z$J#ZH&_{2nl7IOBp$b4l7-%9d?OV(7lUMF&K!R>Fm2*Z{`by@C_O3)G)^c?*-XD{Q zB;BdW(Jl_Z$?=fqKNmq@NT4t;F9bu0(SKY1M?a0?qM?y;(WQo7*m!<2KUFP8zT2q#k0&=zLFESY)Vvb?lSnC{&ONHxD=O*UK5 z^`V0Ffq5LrYVQAE)fC--p6bfh<6<+e3|#6M_9``n?(`W*0UVR)u=+~&0=-w@iVdgl z|5cw$db0&IRADh2c--et_~##)4a&$!GZiQrG0oQlC!^^NW;hjJ4J+P9c! z(Hs1W8$`MjyvkfBU^+CW^DwQab#7fSp4XrxAH<0&TpI@{pE?H>dnVyd_MQf z{sXa)mR`}*p_x8m!_*Yb=z;CS4h=#%lYl*7>-)p>M@2VV^ZQC!cDf-zQLB8;BcZb2T#nXYG2PkCML)C~ZLh3c!?+OV-E=Xr^SprdnX@YNwy zG9>ktwIfAbTY>(j&13r7rR_9F-fXzN*s3R_G5oRerS#G}geeL}df!ZoML)HzNicWT zECqDu#Q3WH%RA8kXe`=G_nZ{tHWx>i;`+7xAs`TJ8c{ifrOd$Ay(e<~06d6i=~d-(drU4iNv+~rSo@s^O= zyY3y~>~VJQf)E-`jdj`mH?xI5AALfgd{efb95Sid`@(ZDBkt<$5ps|%>ziVp*y#x6 zBuje}J55R~o`KStp)9UfFW9*e9151(769q&WX6wj+Mek4Yu?k8W+@>%NHop{1`7>Y z_tmu^Y~ShjP@H3r`a|KmB&`_<$b9*w6SFzztd5VA+f#9rN{m3WG?K65wArTY=*<031KaLRbMcgU8lMTD*Z#zG9 z^gr(_tjhL{#yZ5Wzw8Cqxu044@*w?H_S0dSXU?|KuT)T2L0>$hQcnsK`!L=zJJvG4 z`DRw8iZ`8|_QvGME|oHAWJ1AiLOGpQ*0eW5JxH%ZKN6~ZZ-f`EY+|_e+GP~kS6kex z{#Dtml+V_tHp~b4I=(TcY~r}{aleKAMWnY31XYPOoxrLuj5<8Lf@c6Tx^NzF=TZ&wpXZ8H##uGanUK=$+T z1(k;sZY<0(n!yX`%#VuhuZTj6d)vwDa?$F^T>AAF`R=cP3@6RjjAkx5%9;uu43bzN zsiv||<#tgL%j_weIHfUp&kH zM7$uc+OSmm^;CxL4+Yn=>DzCjIz_kwhbK8bJXFGEzBbJ_D*^2lP? z{45=q=3)jp=v@@u=CKsz5AJN{nM-|V!x)n1e`x=3ifdyvaLWLP$Jo3EI31kSXklTyrkTQ4r6;$NIB(kI1N0@u{eX92p2JUZjcGuBu8D ztA$$ZlQL_XOdTD>qm=TmvRAtf$vKk}VmV%y! zYf(VOqRm6{f6lO^dwvRkc;}rZ$0yAQiQ`@H1SlXk!^X%QM5mOwSE=XYfhJm+CAEJ* zGSPNA5Nk%wvuHA98zUmk7vrru~`+TEb_I$>nVw)dJ z+pTUrhO;ax5#Pjun#pc{g(M4RHS_X7bX>}nccjtmn3)BV@kn@NE#6rgvhft^qNFYK z{;9`HW_pE#`+VQ`k5u=cq1K}-0O(UqsZmRgLem|m(0+<4^B*ruo6s*nWO3F=GGWxr zWt3KfKaHTX$RA##5^2BrzpVvLhPIDiBS((f9M6 zwF^iB=%T~&9+|jZ1diCI0zU^Qe~*ZrASJTifdclB+k8Vu*gO^{?WvhEXaK^olBwu4 z46D{{2+Z2cv6QfH$DVjycUB^~wSNA6z58wD;QH%T>K}Gn@6UElNZ=Y8CZMFH9`n1n zu8_PAZHgKfxO;ngfyA4tSFI8@{9hk~T|eGm(KM>IpS8TUy@cC%L4E*Ia;3(sK=VOyRJA@~owrum+NiQbOO_VHOXZB^Ub z@w_fONqL>dhc}4gco=#3@k@?8L|%qo=jgP)t4WKvsKD}04g;*xl)vg5r)4r#mR|n! zP{-+Ghq8m5(@^{*p)lu!cXm&~Qf@QSe&P5lBj;pKl?9vCcJTdM)O+-;cQi9JAPRGV%Cy(WcdRofvaSiA*0?G+{ zD9S83s%hS5O9!}3m%YuBq?ywhmPr z7Jf%7BbXS`>!sV6u9_dr02G!gjZj3o!N~vyoTLKG_5F#;f%v8Dvg!{0@9s?NCCj9 zauol40~E-sVxmzxgiQj#1Tq#%2Q~FbcPA4JZ{zepUV@CVydJg@ZF`x;%}|^IK}BjO z5qpNuhn8};p+_9NB*>wMU+Gjw2@=*MPVeAnqDP3inKsWuBe{u;Fz8rN5L6C*ch?mF zz(bZL@7Ke`M(&h`YSuz%hJ)AfNO&`2mhvKx^2ML8xJ@3EgQ#=jzi>um{IwkZYK5;U z`vV(;uy^azx=a*rfP0IZ*6nVvOJ?Ao4x#C%T~l8THYMbyuO>-buNxj=4%cT4un?hP z%$oo-&BcFz5OP4H5}9*BsR9j0{>~uHgVpv2jm{cyDy`W5+3>u^9aC(d>ayY?BWaoe z;!G4NP`=%HjbtGHU_i6pP(OH^z8#*8(RpR_>(kNJUv)bCl4u~v>eo1~YkuW!*o%1? zOhC)FQe~W*hJz)7Mr^+%PySzNa*v9N zO0Aw!ugpqtmHPI09-j+YH*q={xkur&9=t@N`E}$+;;v!Aa-TW`bM{hjCDFjDj<@N45iEbTjRdh)Vln z=kVtFS^eqfP2-zXiFk`ctNY)W$ag87kWpB#x%8e&YVEDV+Kf$!yE*mSyq1hGXw zes!;>D^}`fCkGe$JH@;{L-`(DnkH%n&hX3!I}~1-DUrWm!W42bEh`g%AUO2o6a=5G zzuUP9d9*BPb1Ca3CB$kp(MjuYE`)XlhlegdT~X!;A8jGr1cEZ%?r+*2~Jd2Gsx5^6^5TPSHAEdZDZ`uCBD`|VVU@=m&pZunOeh39LAU3xFhkz?rSo9Z=_BX3`r;*F)6Iu{X7G@>bNivL&@6X4nWT|8n zaUHA?0Dw2I5_uc}X^}Pj`ke18IZ|(%R5J(A!tHFHLl2^jhJqHnk{SH8*^$cZ5 z78O8V2IAiP0hk7qEj!Mn`w^x4^1mV@>~!_n-Ltn%KcYF6!knz@yUlZec+KZSUPCzi zJpcVW2~+?y9r5M&P%3uoc*+?I=3vch1Em(Ev;0SPN1=Ki46INq7J`1MA6bN6rpX=` z4V7+RQ&@)(6leyz&F90joK-{Nh6!+200?1Gl#jV2QdE6ZJJFRHIxa6f1$uc(t1rp_L=fzhUwb9)691CJ z*Jsytb8Ff6DerGe?GYqM==S@n&oq3qN#N8kihL^@YzUwR0F<?nZ#IS@*?wH`8f2aUn4-h4wL)p zi1b^R6H!U9e0cX4003KP(gFV-#J7wU@?*?0^El}9qo+c6hNN0RJP?d@!Kh}w-x&P5 z)*3vqgoAu2qC6XzUMK(p9zGOej6y+rKyf{ZDlvO1iK9RZrsejR{tA-BFW3f2N_2A; z3H1L)u3v>>z0p99T%(6hI*YxWvH*ki?EgP1kA*WzTY;sjJQMnlj7MHDLGR_*^<$N}q&#vy3R`(RhE}?dxXh~d{~imL@D2QC4*VWhrG#h9cR5rFQ+Udi zlxAl_F_jv?L)4(uctu=o)vYsz8}HT&zEkGufEN2KKqBf@4Pg1w)M(ce7pet ze7Plw6h+y+$BOsn-%bEbU_i%un^*)rwg|a530)zSN+dvs*#Q<<2t}vN$H)JyQff3@ zi>SEdRiu`R5rB{dfFjn4QiQ^0A7@-FukCbatCN|rHl+1cS7)@2uS5L*s{BzV|4+1t zBcX&80*U^zMUmH}EwwR^(55~tEla{wocW}qEZr#7=q6c$_t-#2gIr38jLTa8ECT*D zgnEM)3tW*7)xKrCVv8wVVi3C7+HaXjeyV19@7ntG?bEl9Tm0QEJ>S0loq;EPS<~;p z(a(NLy{CLC`Os5fY_#?6ipbB!(d}~VUcSt_AG6V3&Sk%8y(s^vxyJx;bJ%;bxbQqf z69D*k_n;tsg>?Rb&`!)N|IQ#e5b9h;2DOaNMFG|9k7@W40C@(;#g-djmr)9Bh?x?2 z9xj`V(Ct^1p40f5_sX+orEKcj5!F0hk!ZZriETIUoUAc6lm4jl_AmCE?7!stjq%mC zJT^vuvBjVu2nJ`8$?>dVpXKVt=7KDp3B%Uo4Z9@m@KYFxTum6EVkw8d`DHv@=#^C|Wrt!+n*9a- zcd-gbINv=aB%Phu(5{`!d4(pL^{`NbV5!>J;m<@Y=SiqZ>4+-w1&ZrD>jlJ=5CXt) zmZ&23+@a=UQRpYUD~d8+AaoMw0C2au$gx)1VLv{f%n?fW{YNSWMyj0vk#-2j7I@Lf zA~wnRIg&S`6lbK1oi!)jU6AWA`CQKKpm&6y$pJ5EG~Hp^?LS7ThZCDDdOFrn0G1V@ z@^kCM1-@anDouOoCn8h-E^-O-n-a*v>-of>D`B&)F|`pK$;WsOavj4DHma%MCwbVs z?242;(ae;agX$F%gIILT=PqxkGB7cA-7wL`H(%=8*!7(sx-kG?Aj4z7^)_OrQYl-` zF05Ayz+wZmz}mr1wR*_Sn3PUy{ck6Ww4vS%PulP8?+7>lw1N*!Qz4cc{HgwnG1QYu zuKxrh1b}ACt^eLcT3Hubg**uwpayF#BoU-24N0#oPU7S#X32mNl*ugX@U5c#Q|OhNz|2_RPHC*~p{ zafUD37l2Sds)_5ynr7m@2L=>7{lQ~&0wRTF|N@}ue>=LqHXJLPBTo8ZKH4%)_5Dc`2!p-sS<92ab=tk_u zXvS2tYWu+JfbP+-lF{wnUuvEJoKPvyy2%$O3$VZ%ABu_q2^n&W+rW&GQ-H^c{mT0x z{;k(Rj%t68cL}V$h@X+=Yv??$Ck-c{n7&F|;#NV$YGq~XkDd4c0}BbMhRf8I5Zy(m z_=unvvylJQP*^Ve0qse_nuVTMe(6zB-K0s4@9ID0HBkidVkT2(p=8lDB`iT^mS!7*TMUu@7;D#((2m~ z5nI{fmql>7pr4qP24;natqt^Ks|dBgNG`(Bx8$ugr6o|CUDjsm-4Ch!U5MV8GMNMa3tLrjyMftc;x(fmszxT+?NO>O|3Z}$ zfVN?{=p0RL=!8p|V!MYAdX0qlAa|F;D^DI{5(O_6_o){t(6Kb)j!jF3-lsb%eJo{+ zN^cJ$NuF#ONW=W)4-~LKV)Y1ROl&sX^urw)TZ1y?ffDp;*}z}Km~l@1UkN^tJWO)q%D6*Vz@5Z0c9ZQEz+~#-~r?h2e)2#P|jMnLQz6B79|KAl+IR^(c2YJ%RlyJge*wu=co|hrPD_MV zg*F2B*^a#Mj#Ix`dn_r>FU?y?P8)3c(38a;z3PQE%V6O%AzRc;SV>4!%BmcBorPB_ z5CX}L_pWl5Z}xbQm5@sGR#>6F>;dPHf%^c25IOzMv;Nvb7%kTIA9g!Zp?kSGwBLU5DQK*5ni*O-q858R_;#GsQf>g&cd(h z_j~`NN5kk8Mwh_oj?vwv^yrW-LAnPrI;Fc?K)SnA1d&icN)$xuyZ7h!`0ZcV>)iJ_ z=f1AzHJjw$RsN85rmM?xnn%oj#eo1MYm);|@a$ppz1z4k=m?0_&{t`ku&))7nSOcx zORl(fvp>2DcweoyTULBC-76MQXH4S}B1BKIHH=6~rlHZ(qSk6nMh-q94g_*X_P_@z z3bJ{@UIRmo=scz`aa?&NRnTCROMod6{`#u`c3Kk#bUbmbzVJjg?nQawGE1Yj6}bWr zb}o&$UpsDnJd?R2He>p-vPzxxs?`|e^hQQB8p_9Jv zbQ@G;scVs7QegwzY)Q{Z#smRomN5=$nk_VrQqedYc*YC7PMQ}F_DDRV9RuoP{U}s|))_QEN0G*(iuR~1>%zvzqxXT_kNpoCJL+%9+Q;ubhiUnDGC|f?hs8@v=J5%B& zJ1OYDQe_MyNkvZijx=i4oVoL^tf5-r0{HCONLo#RyOucw;UH!crCu^J4rxwFxEHU$$cl1JJAd&xBkK#K0hnBZCL~J_LjB z4(Gdmzo3ye?wp)2oV)>fq@pO1Ac`rWgm}@Xzi;VgmwQeGuLOjj>}6vAUI`l$n%w7b zUwhRjb$aWJY!=Nvz3b_*ztg6}(rXR_0KdV=pDzZ}aga0{MOF*j6AFWOn~~;`m5L^H z1mFGqBIwG`(GAPUhiSlNBG=5$dAUD3w_+>ve3n=9FX(b)k)-~Pf*gG$wNzgxC@)ta z%}He;zYd}v>p(Phg z^>?lw=*3NO^x1@3!S)QcfR^u<)eDt=J=5A^FS2q_*FGA)Q%JTe7G;IN49$6SizSS} zf$}}rLT0~GAFA*L5afce3C`$NBinzN8ZDFnbOw}=7K?U@9}5h~&l!)*;L!|D{$Hu$ zhM^6cuDBqPE%xf-g)nF!p}DrNH2It9RQPNL^i6{!NN=Ktz&3rv+^)~}kA>zP# z9}Lxo@w+FrJyXo#^VXtPg9I*%m;&o#9XuUf74veTy+YzSj@aEgh=8YMdt68*Pg%K) zVpOoKx<-r_(3xtu(bs{1cHocPJPpR*@3H`bA=KGx^^aQ}*p()@O`x6(*4gd)5k@|Z zw;eY^nZIn6P}aVX&2Dm}w-Nuk!z=6MxZHoCh#r<0L)b!QBlYoLOZd8lA)je+ud^E` ztm%3)VnRV2k(QQ+MvA|w@%~H8*x)-!q^-Ir4k7*TtMx`CX$}O-tj7!>AQ`z2)W(7d z92GrC2-hzU2sVw5rNvbK9U0eTs?^eV_Yi+O9Wp2Vo^kPP{$)ddaJ*~9O^lOUYR%2q z;X{r#!;q6MiHqZits4-Q=$5#)wtDjJ-Elx?1!p12>e;8}&YGt))FPEj0HrOP(uLpG zxltS(2@Z{T9MxB>VQV*1+gdjEhwtl@in;jXjk#{rW<@h(Kl?jg$V#q^U~>C*y*{$k zm|h*qZW@YW5`Z(BYi{Vv>TcqsWqR9yr)z~P)tRf9H$=JZUWMp zMFwlak?vDf>MdJmXC%keQp=p?)z35Z<77_f$8`7?q&HmO7N9rynLqrITPqGP*Wk!z z=;aI>-glum#O|-7Q16Sv=MNEXikM?^x+PVHDxz2U0|1OzZ{Ej0U+-R{FvplMvh$U%*QJK$uSwrI*7MPrZAuNaNEJ9+y@4*Z<9` zNF&pK$)`xrtiz9g{Z`OGt!5F!GvuwBQRWiT3Ad94xrf#2zXwDYDN}4B(>3$^c+zhb z{(aVSglwE@YE4vRw9QG)&-{CCYgMxOI!rq>p8ogt+5H?#Y}; zMOl2NSVVF0%T@LO;gn5$-P!I;bBhc$HL^!q&!L!J78OmY3wH@x3^VzPZDwTxr?5qr@uZK; zd;ey^@lD>CJlM88V{KI`fV9{j!e%>&VF#3537z+7;9f}QE5r41YeLaaC=W&ANxVcI z0p(ckEnjIJ4a)6>O79>E`9((!o?OhWt6 zgbr(&rk!x@NjZ`HY7k;6HZWI{RVN;ye=Thmv0#zurkHo8@oPIO>8GNIoOP@Jva3Z)9+5lQ!u-W)F(VT24@C3cDxkXuW{2X`R_Ss=nT>m zx@ucHdeRb1;7V~C?qgkyX%LPQ2J&3JtYI~L!=OAtR=DNKWw%euzk*{m&SXuCD2>e}4!bBxb-C9i5 z-D$2Z`FGn`S&2av6}f8w)9tzqSfwC522_C6z2xLCYWWty?m{JZ;}+bv?|8^iR8Tb~ zH*^m;i5cGR%oeO^jHC%34KGb2plG;mnyr}BTOyHRt)x0N=(=Vg6A+v=oBM~MACC}_ zqk74#QlG`3nsnblzrwL&hzs1GmMuK-71SHK7VY)1*P8`Z4NX5iIu ztCVS|IkCDTYL|%$g@GtY1Urf%n6L5K9na~j6=oeNcmt#{yPS=#9rLsD(0;4u4)W(%O_T z!F@+{WrMayOF}}yh(v8kyo-r_@~LVn#LVeW>|hor6RSU3n2>VetOm0EuKdg zX@(W9O#o^!BJ$n+CHyPMKwVIlbp&+{$nQ zk)`V0-Yg0CC{GCd9TJH8CA9fs9+@|O?R`Rw?{ty~ZICOtbf&5Ci5+tgN5S%2APRQF zTO}Tu7B^wTLUy{$43NfMSA520a+3iXbauy5zNGCd)1o31c6H;>V+mko=`fDP7up~P zHqwGhGGast3oLq#^XMhk=HDHh-7mSvW@jwKmUHL)#|fA)G%~7-p2#pq&xZftJv;z3 z=aK9*K5zv)?-+i_rxOGs1F0po8j4xOlG4^rG)5)3|#N_E00X#n~$ zhx*et)K8(H_tN%u`N=lvzyyoI$_a^z7ty}3K81|F~Y<1|JPNn~N?jFRB9gt$A(0v1&gc0)djDYUw?U|>=L zdtUSLd)I?73nQ{=N-;*a>sRDwzzXnNb46m#widB(E3p~f^M>don#TcC>Vn<#W}0Sy z(?6Ihe6n@ryE_Mhh+fgD1@rF&%&UAF&CZnneWhf}FDXY$KK=413@7k&(x|7nlnH=z zic#q4*@N25nkib2cn`@KeCzx$?3#d`y#J1tj3d3w($6TQKxR?*m|oo}>&+8Hi-S+# z*Yd{PDzuU@Pya?AvO=tAh>#|B)U-GyV-GEdsHAF?pE1D=ipO+_LWIvfHOO|#q3Qw5$4!6`pGw0%qP&*N$e1l$#-Ow z<>0}0X(S5Na#U1Qd`Bal09+41$kyk16$nEOUsHBqB=w8Iy99LflJei}X|gwsDpvm- zd!>YK1?`x~B)yqeT@fUXx%th9M0)(Xu@%47i}Buef5Uc?cs)NoGbQ`z*O;{X;ymB` z!4Y0{A$VrUl4~Qj^7q;FoWF+TO{HW3|Eu0#eZS()0(OsuGBW{ys3C>jr=9pDQFJ1h zMVBB>yD@PRW@UI3xp~&FEg3PD-D9>Zx_I)Qy&aY)9s`6b1tTlotUT$$3nk*2h}IeI zAotRflBm`OWtBZd#{%^ttGV=r2i0GO7lZH=01yCDV&DD& z^pc~-?2eMP!y>~W-$rl#p`fO^3qKCF^_hCC(w+CmSGz9vMSM_00&|L|y}i~0%IXqE zN6oanp@FKoZL_W+B2r97j4b2NhV$D6Kx;JrODoHWT;z*BBjio@_Ww#X44GZ|UpDv; z8>A*4w(p`DZLN+Zoa}XQv-cuZf+(vIFR6xOJPPQA$}E6$>;?FO#qc7{&L4J_To#0vhH%nPIlO^q8rJU__0kt+oPW}6v=V>M77 ztQ=fI1+OpJ^0u+@qi)h#3ip7MIrp(|$`++r0PJCcFOkk;?8$IqoiAu9E2sxyab9_! zjJ#%Tzhn=;KBE&2mV}OK2I^a!YlPsqwjJ|{V_jR+cY1ZX6+vpdI)IY=uc(2W;pF?gz;S|i423gRCz zSc$8Z^w!b^ZDtkDGJJ;*QWIbD%GauKEW&@f+gR8YWkPwh&d+t)#M?IzwI2{G|3(y! zDn@VnpRccZkF3vY0^lT|;zblZ;amS_bSD72lDz_R>dP1O@9hWL*M_Kg5zPQZY zMbLuX06he+<8eJX_a;t+UGjS*FyWLx(t4{xn8f;m6@8_zZDQ38)pJ00UHLvgST?iM z^SxtwC(Y(<+n>27OoU}ianT{5=J;?ltZg0PHbP5G3z0!Kib6am}5NDQJ#{9d5M9I=B zZ>i>T)qeb5ms9C|)=D+o6_@Wm>m~(>_uc0{XZzg~m(KLNmAK&aDfQt9Tw|6>t@KCl ziIV;^Y@{nNDhH8M%)p+Cn%LEq^Uc?L0E`PpFBY#$h@~v2msd(X*mdDag*UAQb`^8> z3QBI1`%cHH6ZGbldoj1&GD$9^r+wITNM&(uEPuz8^UlRfWvag1qCJ~q^~RebW_ zvikGiDPWns0aU5XPd!=3s7-OvmL4eBT-2B9kAaX$q4|$>qd*8iGc{d)YeMPh0L-0t z9Cp$u;4($V%sN%pI`zij3sNVVb^s&=0VqD!4BYH~Y&vYNB23^HIlXo5=B7bLyO|2S zu-6nl0wj=YyVkI>%HX>DJE_{6iX zYsXpngF=~0uVe$koEYixYVPM6uHU`V#2S6OyR8^{;4~w^g-#J>l&y`P;EpbfG6gx% znTEX0PI1S5n%|E=kyw<1WJfI@b;|b{$G!D>t-aAV*k9N2C+_T1FDlyjz<}~4pK_qe z9@sHi9a}~~S(Gc!>b@UnNK?^c<;UA=l+%tf+ka%CrSZpn7fsWS7|dtmk#wM5aE zIjg;V-sP)yOqh%5h;DtBgb_Q7T#6_~jT`Rogl);8(KR-g<$Wt5hsnh=xMa|7)huImx;^Vprb)DiwE*2% z`U??KI|h}B(4Vlb48GFz>B3@)IaRxjTF<>4I`@H=VSZduJGBD4SzPwIAfGc9xRL|; zx#O#90wyIk(ejLUM36ruYJ~E9>2edM5Tq~h0f~txnmVe1<)?Oiq+70GdTANPPu4b1 zE*`6fE9aujOkC}R?`5QJ)eR89KdNJw<`-6AC&sN%)3ri9{YNTqQ|7y$lOA(2Yp9iq z<^|#Ukw{zMt8rK{(f)XppANTk5NR4GjFD4hhoTGMaZCcW+uuB6Qgk;Rl32PMn?@+ z`L0y)*z?l7?}8-N)vq}F;6ShW!2Yt7n7$;6;iDdjOg*qN@V^@pB@As0`QC}t(YsBn z$yk>v60|#L{$zIDE37;2&4Rny$qTV|n2&Aw&-lg_Q@sXo*dbISt0}HJV-GJrvf@s3 zakzyX)y7?4)c$3O5*8zEzn*F2Wn@bbW-bxq5MdmtswB+Ok4rDR;W?Q(Z>`BA|XB!jsI+3-!LD`vl%qLG=)l@^dQQ7s~tzn zh%i3jIk#&yp>cvR(E{*SW>c3`rmXR&WYi*U9FP7@LD8Pa(SbI8QHp{{W&*6hDc(cY z>i5)0jK;`7=;x^f!Rh=>*#fT-BFxoK(>YvUenG$gW&RPyMq2X=R}YQOrf3|99^*|8 zRxB<=Wz#8@#^>@7E~SgnmFM7JmMePvM%q;Dw;pKQRu$(sqj4smfak8nKkj;-ErT|I zg5FYF7GIXBvVHJX9mORQ9~!?OZ8yZ1GaY+2mU{3TL4;9wcSGHk{f<=7&LOjSYbrj2 zdt5(rBr45Fzwx|!dD#@Ngalkt>Yvb-Yi9SBnBx2bwBY{3GhKm{c48*n=(P|Xp9M;I zq=I1y3Yb(+Q$$-0N)Y8_WtCbLR^-J97vf)3(|Z`=n);Tutoz@W0187JG||0RqE&Hl z!O?b$q$UV=Q2iq8cxA?p3*X|r76_$N)#HkGnpu~ zKt=Bbu^Jg4?V?c@C^pN>JJ+lS+xYnUjkDH_trRl{R6yc@rN2L5DH2nST?v#M^;!;PX#vZ)P zOrRhX4$&nabgTmuJ2j0+vq47XaxL?{8@v33yuzCg39%P6$$UpFF)EAEWP&=ddO4ER z0V{iSZl8H}TH884oj4-wQ39CG$V788*Dx7lV`Xl8OzsG?w+L7Q_!s80m|(JSs}^j; z2A-lS{z5T!8Niqm4@Cw0iP2<}i|ZDq>But46M4!Rp$w*&+}Y)}{T*#u0++IPGW;Z+ zIb6spCH~TMw=7U+jasDB5J?po7A5Cqax5fl%`IcazioY=Hoy!N%N29_XD`89lJ9WjZ}NL&asd34ug zFgGy78ih;gpyqEpsx=1;%AiWq!5)!0QJ(uJIJ!h2^yu75@V{P(IUKE8&k)%)Z&`B$ zH98LK6YwDYgJo0IF`1`Gz`hWJNb?*K0^M+9LJAVK$3!D+le^}i;6j;11j#QuU9dm#YWd083?p;uV46~fM-l~i2gQL0nLbrtpG~7ZB^8PFeA+I1}q-}k;qb;>99D-EM3;~ofuJHK!$FGL8xCzs>~4PPtZorwRfTozb?_j>SH`E!7|OtLHPz+Rt1MeZ3L_^fk@OrRFY`AKe(KPWtceR{67BFYmY5 z&SdN=6P;X#hqUBqt?73l0=19G^_N{l|5<J`ej=RD*MHkjbV;w`^F^rT?>I1 zRPmt@)?n998a=GHl&7{ybfA*J!hc!A$J|#ki}rpSO#r}o8A7S>0I;X9y`fY9Mq9uA;!`=_IBi0;~{a5uQ#@+>#UYPI@LZG#H>0vS53v&p^)QX#gbW0jr8@;$cYewrhVsBUdYSRrV!* zBS%Ia)8LB}i&t&&=f+wvf-Xk?1i@;1Upfbi(`qeb`RNCh3?}E>b3b=ZNZ5DlxYTo` zHclNRQko}{|Mk*do!HBoFLsN?D}QefO~V@}ihv){c1Yf=(>?d9Be2An$`o7N?b7u4 zgUj5=n=_`a4<>2mS|7@XgmyBIhvt0gJueXUn-||%V-W@2=gx`!`(I;ePnm#Cx_l9@ zb#e$7t4d2*Im^wH!qdMsmCWP|##7rKViAGW6enb_VEBCF^Y1xwIDDW~9?93J17jFZ zd|zJ#C>BsS%Z$i`_&Ww>B(WkZuVcp>jotw^T2laPuLve zWtR)DhMnx%(xOejL0Wpey2*{%*gs*vly-N0@~=&>VVVe}mNf;&3qaG8`VTSn&tVFw zCqtHMyt(qvUM$A38umv-uHeU8AyWN|c;oVT)shd=Ejz?JyU65FlV(bcgE@`K6cD*8 z>ZZXA4%!9-F9G{I20e_W?~M5}CgnNh;%c{z*@so(qD}W4rYh7@N+TS+OB26aR1H20 z&dC#1$Cow6!!+8sz>HIU`=mt9QsWh32Rb7g7?JG$!el5QYBvr76j=)@JD@Ilk|@zT z=I8e-^5x;dr#jVn_1tJfCnv|Qd_US@xgWrIA3+Um>2{nmP{CQAT$6@)(m%~t<_mw- z+vF}*+e%Ld3d}Mssty$cZxzmub8;*|@E&!lifX=phNC6z!`r{IC*llGsAf_CF)Pjj zjKg(0K8y=724pDOG7FhO1KZtiR_oo3EmDm$4kZH7p$Fi9@5dp`v&Ff0+)C^wClT@Q zjWxc_36{NqF!z{t8LRe4`xR7#o?l2jxb==gkKQ(VX-oW|uF6mss+r{n)mYNQ6}1dx zDWmiRh(NX+tbGOrVQx=bdKslAFTnij8mfCOltqh4GHpPSS|t;A%^8b{ zp6@Iox`*5au`?{_%9RobOjGo~Tr=Kf4`Q3!?48f8v43oIN_!qw`k=PWn%=T9uZLeH zGp;kE_C87H*TID`MZe^*^r4f-=9@?8-^Xun?<=-v$9p#7Zhvcv%$p>7U(C1EB*@pO`Mf5I?oAT8|<;nmx? z>rIJjdba4Eu=pnHQ_?fMpD#llDXI zpMy1OlKWnM+0>iu=!vjy_2hofurs2au^!Fs7crwFfN@M2540FtuEd||dS0#N>k3KI z=v=a`&Zt$d-AQWtshD1Y@ahU^1M>$aCfG4aj5U3GL&F{wb>L$n7(*cDnFJOe)Ub|Q zWzLWnWPp@8i2$}l@Wm%a@KLB&s99&ND4s|cN^j`yBB9K2I3)gV`(Q=G!1aR#LMsgxY7_Jj@YIOaF~E++k>GdP_(`YxZm)xr5iVn*_x^7d?& zxHf03;bT!AOew_8N4l6jKNRAY7Aw;osObDutg#f>(1(wT@fsP*_@k(PE-YHIvKc^U zcFXL8pC+Flj?49D%@B|=jIwJk7J>R$aKYbaN67=bc8#ruWMYy^Q~_A5u)%O`OZ(@G zP^>uo2?_?ENw6WmKtaaTtr*@2YTXspNLl8ZhxzrW0AB~htX(<0am8-{Q<M%9}M~(8)BV3(?I>Qr93~00<5>NEN0Cg!NyKeE7#db zfTKq+kGdIlq(!g9|MmUIq9YB_(|dw|q2D~IZucEAHkuu~?*Ek?I&w3s{hu-G#FDdn z-pVGFKn&T;GP@ubf5*r?C_EH9gN^MMfGFXJH=TSSbY8T^i^j^nlmQ0SsO$b5T>bp zXoyR8OH8;RazB}1lFkM2)OZW3xtq`0GKW6@y2Krpl9!cnR5DVIuO8o|%_!mw7*2mu zWd2jw$_M@W=eNW`(hSIb*8Az|M%d0z^3W2d%uPjX3Q~sQgg!pL`|;@X=)4=5m>(C zUXznfnuo)Iu43e!nK@ande{9KT;SztAKQdQ>+bSI@sjkUTX*$$<2wGPNufB>9UaecnwCgH#2!pSc`^ds8D%2{TQX#^Jw zY#-*3d|Nt!eJshTE@udgvepe>ghFu56!>q=Er?oKgW|V}mYVaU>C!v@*}q*$o&shh zl`%!DbU$sRih0P755_ZEA&#r9Q6d3#ZRASwoH$vn8Il-BfD*Z5MiQdxnfcXp zafC-Ts?HlJJ5rdFVgd!T9ZXJ^JD=wc4RAkI zLqZru2=LASTm0=c)l@YjwPBfwGj#fT)<~dOS>49NH$%dB!-!l`Fp49Zx-F(pD#iqe zii7xW$?$bDn1eQ>fQhhK&}v2+6OSC1mdH-LwY#|OFmu~V#7)j8kPRc@1F*10_EF$=!nu8FHg^6WY!jz}K|ND8e{NdKDsDT>mx2g4y;SeJ}c zQ!M&Pxd@7aTfQLLGFP$Xia^6?QrB-ZFrs$}NnN(l?~L+)kbb?y9GAkKl!*{pecCOc zv2Jym>0P&B_UfWJ|8DDd%)HO!dp_2blPhRwBkLvcW2_GLzg`ImW;NAAGL%i6PMq=I zpb=N}sFsjW)yJ-j5&^b!=DLT30Nm(l!rbBJm~oBQMWB600R| zE~H!!X|Qjwqb4Di5`k0UWl`OWN^k7)(6!7~bZe#Vp- zm1L^Qj+%DYcq@)+#>$T!b5~~)f|o+7!QHJj?ci4hyZ95zACzHGu0>HWc`Ead*Z|7= z{1g(C$00taEaJyhDOfhsB3A14)8|k!Jy#ItI~bxfYOok|WmC@*r;&Slp<$|V^Zbl1 z#1^H1&d7(WA|h{KrbK+{8KWZW6hkYnDuPVAis4}(&f@xCuLM9#Fw?!~rZ#a`n?iyc zu?fC7DgQ+B=v#T%d*SdELM`&v&9j06yQFJ^@vNbka=g|Q6(Io-UStK;uoND9h)kg) z3xX6b#~;Qs-@i2f+~3shW^(0%*Dn(Ap6Uw@3q&9)LJuTRr>hn4{Fgt|hDsI~A(+5X z^k?QuTUS2X9y3EarVGcXgrsElc$AbomtE9!AAmLh2!L^UxQ=z5iehU>p~JeWKXDwx zrT(&04|0y_NMuI!3b6>j)snd zFO;GndY)A^8v~{^dfE}w7kr;*j}KCNvGq@LSQ^iN_je+DUj6qaAZ;>L#*3#&5t_5}KT8)T zTtZ(5#d^t+y%O#pf6|i-1R#@b#0laNtGn5hLaahMBd+$^N2zKQ7pfo|b1N zE<+m|(_XsqOZms2(CuO0ygCtJL%OaTFwW+H+vey({Z8 zm7PXF7BNAcFt|}Rf_wRDQWrf1SCvifR#S%v%*3o*70d-ccRuApqZGyinRX<|6jRFI{h)Kl}*hb(f@Mxv?4G=KD2a==aIhbEZRRQscp7_Mcq8X63u5`aw$&8GQG1>;a zS0n0TxH)J>m7Y73$6LVHUF@P@1{*!Z58L3o;oRzQW%FLeot`+_UzUHcn^j!h$LG(^ zKM)?VudRQy$&;}2{Rva8KCu%D@y4k%_1CWX)KIWD_vgPuTL6GIW4v&0!e`}n@{iXE z3BEsmrGEBl*m{Y}Nood965rQ2qb7Nt4wAOrSYAlwR2&lAoLRyGMa5=8YVB+mxCv+p z*7eFs(4tHB0Rw`cqaC~H<=>BRG`M-KO^qSn00s1NTG>*idPP`sF86t$Xp+UFsslyE zm3evm&`;LG3wda$09~sgXX1g8kG2S-RtPPRa>^B8f!+UMJIB=XQWLLEDF$^3QSEt3 zmt#iB5c~=Hl@q_HJ!KSaD7zHiV=uk2=CyfEiI|%hUMo6c6cKDSxNTUp{N^|LrbnJ` z94c%Ul-b{xf1uc$yjxfErK3Z-*4XdHkb}HELo6fo@Z#-SZz1nlDXBNul?AV=t=_$i5lzhg&kskS4=`7Y;DQXd}8EL7=tr*=y~CNw?WSPditOm z)*J+a({&_h9UN|0^Sy@3@XgNYN0eCguh9Tq4cab^7J19ejvIj=Z;Tv7LhpAkZvDN4 zMOIeLfEd-CY}w+54PrSRp$A@}_xpZnZJ!Rk*nRPL&}JY^S>rmKjZt#JXl}@}?)Fzk-&+ffn&_tnz!D8AS2pOJgh{NZf;Iz+Pvn;M zC6jx=&K@h7CUTZO2&N!0NKFdf)=(bx$<5Cg`7E)r;yK=M`M5AbWwI4BX(`9v4W-d- z=9?9s=Bs`KnF?w-Y^XU_oh>#iW{B45bC{7}>MT6#NB7K#!u2#gEuSVPFr7x2T7K*v z$rniM=crxqdU5zftyBDgDW_l}PFhmHwuAx}RWV(#o3H6RF|P4&V!welTNC#>5(9Bs z{@$R%UE__FQCC_{h9C|MJSHYiZIQ@;s;ki8KgAg zd_EdIPCBKf5cv!`a19klw3&r)>yo#2+0fV1Fa16b=85!GD=;ESXi%LqM=KmJbxz*8 zA{0vVaH+e_FkCSEcM+j5z%{dQFIaN=q%1%7wm&fE``MjL1O!V-QvPus|+9)IYn{puO7F)ovB6MCpr^Lyyx}l6idMG^=?% zJ6iHtn=okXrkTHN;Gch$Ujz7KluN&1VE5Ww`iobiz8u)I?c2L=f5l&SnY~HXUlBBz zYJ1-&c0NW(lRO@Vnoe94krIKeaFOcdhKM0*C_EQ}`jtIj=nL4wzsjP4>P|#RFd(=T^YlfXgo4b_cl>__xb<->UZKx^fooDoUkj> z1FK3Mi%|Ac6jqbiflhk(tY9J>2o6A0l9PK9U^3H=yAe-hS2k(R;Vkq%`Y^&W0J})F zC|DoQB(wg-beS56~oHW+O=?d&T-~n{}wmul1VTO zVg!0X)is&ohe}r8*KFu=Gj=O0+TZ0GbC*?5lU2QC%6OfiXV}PAo`<-3{IF*!TA&s( z2&z}}>ftCT?|dWi=Z&sG`B{SNA0f?GhIO`oU-KR&RDK(z+OiT(O*L3#Pa(IMo#sF1kaUvIwXJmUxwio^%`iVRYtIced{w)KbOGEW!_7i}( zm|xj^48Sn zLxH|$jgLsU5q!dsQHCbW&zyBoT- zq{*y4ecHKlaCwXQ+JfcO?4*Bm{`vVLc>Olu`T22g=zSw`7ytl{@0`)0lV{WMV6aMA zOy5)TF3kZ12bN$bsl665T4jj!o^el5b8jQc(Xi>$JSCgVYvwHjQ-G31#o$oNwH z#_=7jB{%B(8@tLw_=Irc_p@Yr|HiHPbGJT!8Oy29xmk@Cp0$c^XJ0)QoN88Jxuqp* z3`*auJNOZ{ZPAr~)rt5+?Re_R9zVbbz*l7%JZEQCbR6a?V@X3bn1_#APSCHyYt5)* zmW$H@RxK;6@qAf!n;|VZkIr`^vnuhZ>&pW5Qln=AkAJVO7F;ANT?3q&4uY_ZNV-24 zB6pal0cS8%x6r3xp?<%6yYaXE3qj8^A#2<|>TywxeGC9QPsqjYn`HKs+06Kevq9kz}1Tk*Gz#?1JJ^B*X|uj19anZFC6z} z2(YD$&zN0`Jy>c(vf%k`*q$w-&IY3w+#bWzBkL@?r~KFkQ|lp*bgId5m9);?0!(;6 zPIrC%gB7{kx>GbVD+B4W)O*rKkExCe0f6i%ev#+rIUUki30ycrYCCnXoQLo*P_88B z&r@y|GCA}U7g84Y{^<{}7ApBkQ0N~+VO(1>y-wrzL*Vp<3B zZ@ZBg1m!4o0&GfwWK^-3ztQl>yvTTitP@0j~5*Qv=ta7IFN0z*VP zHLhbd-19k=8YYZ3@y&K0KZ=y%5@i%eXJ9JvNxht!;WEo0+{Kl(d`I8*$Hu|?&E2&P zwqD#VTZf_RQed8gWAg8osXXHj5HBxoG|7BiGC~u6&BD;1V>Q3WoVS;vk_l&5L*@W8 zF%a2*elrJPL}9=o0U-VP3RnXcR=8skI4!FQ=J;O!8&<>6t_%&|zTy{mta0v#*}vB| zchIeGa}TnQ%u>mIQj8=>l7Y^%A?u)4#AC zwp;?IDcrR}{{5Rzdx))I7N8k=cVm|SYWB;PJcnwt?SG}ph>f;kyz(|u=!faazxP)A z+5}X(`begOlO-uvNHjb|)T#$vS6-Nq+W1AWC_H;Op}s<=QRl{7qi1&R`kSO-eX*cj z$d5qE9*zh3+l*FUW08Af-QsM=_$%6g_Nu?B6`dLcT)$g4 zi=aECIB8H%5Ul_fj}Km6!OF*D`CrgfZP~)EbQ*~8e9(pz=?1kzNN1c3gTG981(4+f zr~nAA1`AXvH~=n;#SaK~%BI}#fNqAR+8bgB5dV+qKoo|y@c;7Y?bJDuDdchl$NI03 zMy6wT_EV`kI9*9Y(ikja4_j(ce;D+Fs;iyP38T=`R`f5eFwCH*$7|Cp@YHZce7mma z#q&M<8BZT97nWn!5c(J`)&Dq?QlS*&5IM6lQyf7MbAYc&x9~T){q_7arc9(W0D}kx zWS6NrYlQ(oeIDIY#S}CWFV>Z13h_-9DalHGq)je-q-Z`FTYs68>3I~0hA#_ksF2+t z64%{ySINwR+UNqqBka$jeIQ;1;Ek*g8gQ?i9>#OWszY#T4;PutApX}z0DxFyROt_Y+=Gc<(rcHQ89Xo{GYV0)i*{5*k0)DnVXn`-KRX}1>`MV1(2yzyAIZ+ zHw^tsGWw~!>OXE6y&XgF$Fm%4R>KXv1VQ|8QtMl0Jj=JfVkgdyQ8n|rV7>}^62F!R ziN8#V=-E@h0c5@coM%XY43d#0G9_9z`cd#p^@GZa=rEoT`<&U$Gw$7s>F9qxWzg$n zkGlh$u@JJ0Vz!sbmEGwEf-bm(_j!omix$%^CS~TJarauwn3$==j0YNpO3Z5H5iHM| z_1T>Of@bGo%^zvDjn1pBQJlrdPwF%pTrB))qa5`R?Wd`j(#)%Jjss5{Ro5Go{mjhJ z=cifz$&Pa}nPp}%PuZFe_BG>qhq}KU70#djD^(;JJH@~d39%0{uTkrVm0D_(A@k_7 zJ)L4$PNg#O>=qL;HT-q;C>{MFs~Nu;(WI7*uA0nn2pqrZ2@TY!Hf z$e5aml;7v||MJFmE?ja*sl4-9A$c7Ck@btcBGbyHWJG?qj#^-bkWBZG>y{X|2Eu|4 zs@!f?Y@qCuDpufw3_9}zeqcUf_cX%vAy)P2Q2`K_07!iQT@rm_CvezTB4WLuuP zfEK4RW>5qAsql_2lCADAUCdHZEWNFMcC{znmSzXx%xKcEu9VpGZj4-;(4UQ)zV~mG zr~&y<^nARNKyeD+T3W*TC5B_1yYeev&2x3Nx)_ZhMt?ch2u-4)!EjtF`HL_Bi%!fL zGXHvoq{CJ|a=V+2F531_P!%YGK*`K==9diC`^X0?%aep7e{N1m4DFdCw#{XTm*i!& zo-Wh3%prRl$#TJN!uxu!TGj0w8r8hv>LYhj5JzOQLK-^HyL_o0WD5vkVC_D~A7uXC zjE#VEQia5&CsDx%@WXaFbI>6~Ytmi@e=`5ORY?J8%?7K;Jo^7Z>>W#mv{4+8f6$xa z7&fHzC`nS(Rlk?hsJAL_`n+{_}U=&v?2QpYuJ}>zs4F zFMX-tDVG=7)!DI6rEYt%4(vnX#-?lNK@1tQ#<1OJ#OH`G3L-S57nasxFo3eTN9VB* z?Ra`3EgodeiRvgO==!g@V%5Y=d=Z{ij|ba$f&PmlE8NCQ7K#$F60R=N{k0iWW1&ra z{(7nVFNE#-OsosjAt`ITO={Ugk=!ok4LJ6my!ptm&AF~FU*;Pq0LcW~Zhx1;0Ld0` zDalUdT_nVPrRs*Jk0#M0|Iy>&Qa!+ZrJdV?ZL#umvULWgnXA+j^OT`Z_Q_OaH?kqt z>}fP)4M`($UNS>o#lm*oLi;xs*ydO*uKWJ+8D&DM-dDZu&qISA1XnXTZ;-k8>oPH4 zf|7IqfK-r))58wYIqn~swP@Zms3~oz;Z$ybWH{_oq-DjccW?6s>0j_l85mZCUNXmM zon^7BhCNkYRHr*Xav4>gv- zIMKzXBcWMYiv=t`RXbXNtbY!cjr){RwXzi3I+*#L&ajC2akuW`N|r=>7K5&zd}l_a zbm_uDZ{J_GePh`;_Tm|J{?VoA!iuP_w}^{Dq+a>O@}K{=v<0dzqui&buWf~joPgNM za!M#mo84YiZSCK*1pk^cVoAXvNuPZ|^a$7>Ha7$MAL&A6b^u^58IZc^Eug;S{Tzz9 zSt&0>W?Wf`^<{~D5hF=>>T;T7_`X-9+bX9a=zF-2^WHSU*J@eFROb6|{O2isI4Fl< zN@ha(d{QL!Ne(-W>0|{)b}61SaxsRdv(qTGxZC!fB#21{fl zh)GcYB{CGW8ft+Aw^xOG>gjkDp;miCBLY6ZFt|5$&PS_*d<}vO{4qvr$GSIjUc#^1idEcv0m7 zt$$YEJyR6*C^^C37G;0qk?44Iq%{E2l5-u~$jgQk37Epkw5iCrv;290CkFi6ptX`8V#lV055et&PPY=bK*p^TqpcOc8^^wi(Ux|UZD== z39N@D4HCOZa{-=^a5BYLQ z;DJ)L+v$}3N`on-vJ`2DHUKc498Ddx6MEfg0tJ#{dVs-X--p1w?mRx8oo~-d6fAy4 z?)S&A=rBW5PAfU}2`xO+lO`CHiDpWhtfW>(b&p7nDRk41096lF%AjC;rvh z5SCMRbxGqiy5!l=dBvw^!Bv5hFjEDfHjvX5=^*)$eBOae$S@X2|Iymv#(#o=|;g zpJhc+pZl6An#DlUR6hZ z!{nycR9*9)u%#jSk_ga=4UU@5w8tyc2%4~{|HN!C@C%C6VU4o?;|V2M0A&dR)KDK_ z2a|aEGgLO0gZp<|C_YPE;Nxk9w&v31@c!WXtr$RNwK+_izPxA1`)WLw?S1bd35_cB z$r@1@^joHq0$w5EfcM-b0G$;IT-fwxBgPkF%&)|S(PT5;H2A%IbJV{Wq4IJ)h(6zG z@%Q0t3m8<^Wpo+_HOx*XMnnHTH*Q7@x35T-0&0C^%&x9v1Ygc%^xW}!pXNXJ-&?(1 zB1crvvdC$-}Pk!xiWyYL<%r!9}ixTUD)4g__Oi&O7w&gw{)Z6x{64c zF-npDnJDq4l?ns^hV%~*_)K3vj=lfwpHaBn{VBDyJM~ToPsjaEe0-cH@+eGw0y|89 zl{6JSlC5I+r~zvDIvhQ$D0Zy&4}exG^J=)BtvnZAzA&v%$32n!c zS^J+W|8mt?Bp%=_0mhZNhUY-yDBMNUId0ElG)rsbLNc=YDTEtQWiG@3 ziV=CVEPwCmt{vFRkbVN!0&&eL?4c143hAX}i-h=eZxr}Z_{e42y44`_Pj&}ZI&JGk z?<|nTfG4gn)}Xuo%M_5*x;C{pmp+APBJDe+>!J_3uFqb56>rRLKT-M2qOEgk6rrfL zT1`V7w>isQsrjA2;^4kSw+zpuwBM`8PnM z2n5LL2BfCF<;F{lac4~v3^&6@i4FS0WxuHE*ft*GL$%@pL2@ z-x~lJ+eaaf|KJ=zn#Yjg3El7y-+DLM0U?b3_IMLCpAR2$I=}zS8`ITYsQ;>hxy zd$;ds%9}_NEzf1?frcI$?}7XLpu(El{~N3bOPX8?TDZg{<|=|l@|2axE`wb-%y`2d zfW`-fdhNU>qVEsmN)Qxn0D_f`cL#zo-|3QWeG(J2{r>R4q(F2uEf-mYQ(kDfS$5}2 zx2f3ce@mfuDo*DCZ%|P`{yhTIum0B|8H0i0T3V>UA5?rhihl(U!smFV(~#|W!V5Wj zpB}B1RWB8Cv|4{E6VX$4Fc^UQaq2_fYpciRYtprO|-B&4|~vMpL%1tVv6BA#e1L>N30JlIA{)AsTsA~4fs z2!B;#meeveHjU*E%TS?vs^a_E)e#OK#|8VN-|gH0K;*gB*T*dgm6+Xz#zY7ZUQlkn zRjnflyB#2Wlz;E!7PIG1sxZT zkADB?1&~`{e)#RocwK=TLX=Yfuk1h|V6)ot88zb6GB&r!8t$VMBaEWK*`Fk%)?88X z2_bR>Q&S}M#{FCtk1y}PI0m|&Ps=`Ri${Ne*4I`2FvH|e4}??@`xSglS6*q@c)9t0 z+GzEG`(XS{P#QneDgXA7X7@4OZ;RhkN1S9B~x-2 zDa{Ok6G2FgoUnt{Vrg*aIx8%KalV5qBJBP8?qAkuPzwW_o{LiD%xDAL+f=-j6ByQWd}ihYc8uS> zoP*&fP!Gs$pTmQU?;}!=lH2+))JI&Rp003U*B`U! zO9j$!7vN}V=Fqn#V~lVSANH6eqIC$ZBs~rXo6P;pO-Y~m^Ed#6K)#Y!<&{1?D=!P` zWn6#3ZF~&!DrXCoJ2`z7YBC)Q)@{x^vBz9!DTsMkiept2 zuo+Y%&>b)xkn1Z?h(!%=U<_euBeUhLV%e0TTKWLa@{qj0O_%}>SsPDzz9FMX;yP5; zrQgUj(lX%TXAn0DFm=U7vqRA^ZYsT@Fvh#^(ve7LxdIvaj-pz;s9>ln_ufH; zG*rTg;88f-wSdFqq-3Jf;`?xYkM{yKaxu7N*HlJ=B03anP(fD|Yrk^d2 zR~h@1#Tq{}tD}pQ*KiECW7c9;k7%?d)iToFJf#Px?Ib|(YZlPry*5Vm&)$+2JywtK z5kZl2jYaRt8B3j~lHP|=Of%|C!GjRzR2u-AHa&yW<7*%i9S4T|i^D!gn?EoizNmc0 z7rDNit2^ANF$K@Yj`jOjs;EE^8x+Ba8gi`5+OuO0e?x?3p>m0GN2IXV{B237#|N{P zfnZNSHw-jQT+t6Wy5F!cr0Ze3lI%S1l(3!;g{F*j(}D6kWd+|AY`qQY6K;R_^Ov4D zD#|UAXHfcl;MEB_O2yH`sCXXlfTrEm1xoJ{&*tIVZ;3C~6h9RwPc8>eX2p?Ig`{=I$>nV;E+mf62!vlLO3$l0sxp& zVmOScZpAOA1H};0#$vJz0C4G)d^tv~qAx@(T+fgL>*DAkmQERao<>0Vk~gk~v<2-# zoz=8#D5d$ME5d`Wv z$CcWu8hpHp1Gpo$>^Mmu2aId`0hCh1*o?Mrmq#xk=uK&|ga=$iLciF6b#@LulVM** zo9%%zV>Q1U>-;E}YxJ3s5n(T_u8{tsU5cz)$#V`v4(TK@_SeW3-(#I7im~x8p z_YoyM@2sQIFCV@f?Z3=@6V&JmrfyJ=_u{r}k!c!_#c{ZDlybO^Uq_G9$;z3E+Y>`~ ztKLAzuvTc9Fw`N`N1KzE}z*);Ecd?rThR)6u5!?u8;c2uUNU!2+fAR>qVlJ$D(@zOy!p9PP& zL3u$obGwyESi_h$dZ-w-kI{Wo=lLCn=Xr!IAL~h9Zf^J+ZkwVyUL&GI$)o4OyL|)f z!2nXCY@SU|EN~YyM2j-mdK76xnNJ@-e8cQoV!P|W03G9x#RtPrcpQx~%e*J#V7MJ% za8t3KCAfi%JVKj@Xb{ueas@z6OqnDD)})LjJC^HLljzC=4Y8c&W6c=pFiFxY6|lb7 zVphR>1{KDM(!nJj0M^3$dwDpFfsWR^V(;QclMRKfIhZTe*wO z?DCr25-T&M+y?e8-kH@F(Z(#Tu#{+Ni#mSHpI$Kt*A*=vb-INw!6F{c@QDbXe@%q`)i+o;BWtGgn=ouLVvixL z*yHa6Gfn8dZV^5W;F9l3IB$yQF0^%z@jpk9c)N7^YYgDc^L=XXWlYM|k>A(iMJmVX zm%_O#1rWO5gF%h;X39I$SMdHHQmfBr534q*mg=vk`#o(UEeAKXuZYA#fJHxoXGp{B zi_A=~ET@=htSfLs-z}&Hhc;Hejb1|SXdfp#zoo)5nU*wn%D;WLO~^{@dLoAXla($_ z^DP7b;1ef$o~wuzyxJ9RKY)?59zyVxSS|uVSuDKOTGO}{b4QWmY009HoNXQOnpn;= zPO{H45hRnt`R?dnhonQD1fhm_mNfcCtopMel1eHbTBy(srxWJ_Hqokh^Ln`v-hiY( z>@78fCpae9rKD;SGHxt%&J3RNzs4LYHx}{os9~B?rLs}V$Nj4#lvGD zq&}7X0`xp^vsZW(XkiC-f!J=Rv;C^%%!N-9M{0oIei>_B<#1QuAZ_QZpF17xOa6_t z`1z|Vt!p8f`Q45VQ?<3)jBR`@Qo{N3SY23Yl(;x3HtmAuE1U56Xm>796@*tjoPf-Z z;pJy$iQNT&%y{&_-nL!;5zAS!)Me4dP#l(S59jcE*XUWO(>pus6)YhEJ#2UN?_jY%nkab+ctdwPq13G;0HwRh~wD7g6J$nA+O+D01!^dTK$8OK2{R z77DeC9*i;6kJ-^+l8RNSekNKq^IyV&FNO75B)+GZ;X= z_c55YSIzmQ?43og6uEw|nmFjt$+2`Wd@D7VxjJp#g0Nyui_iey$HgO3FXs~LF>o+3 zuh!Yv!a&Q*MsXAM^jc8}we{%lWre$uQ$aPc6tby|!2s5n=;uaGSWrayC=fiaRe?@8 zGXNvN_%eRFn*XtpCT_W`!!+qAM<|M=>+tSWzcGQsGJE=W;MvPntN8KazZMgdoGu1W z?VV*KT$+Ua=k_YZF+RxB@-wBhp82HP1`!WY*%o&Rxz9cUqbE#X>kR>a6@yn zbLdy4XD&j>_&;COn^uL*9=?gWq1amF>AS-Oi=I;I$!{eVTBWW$V{-kjveat7xRM%V zg`bEO2t%PLdkB~_KsWH*6*}%#c`MzDN{}j?UINC!-9ywvD2{0aMVIE*zT2_Kn!zXM z5u0imwSc*TAjai+54<~(7wu%&QgZ-7--$=_k~^J8^0eziym zR(owgpMx@eqr@x#sIRo@r$XYbJkm336GlqNWu$RoY-ZBURvS=~^C!c)_H7CIQSP~h zk^Z!A`}sI4{tb+%xBg2SkjV1jHq7JRIpfFc>4D z2#Nt{64mI1;9&ruy)l_b!+m!gA(RfF_j)_llHxgEu$&vrPeMkP$OC6F(9N+(e8rbyRZ)PqBu z<;0cvuF5QxlO_~nW}?|rpgXo7?3F3iO~w{8+O%G@8!fejUZSzWqqqVyxfe?V9#8@0 zk1O9!ff)Eh0zmWHuQ`}KA2<%x3L)!<@9YF#yw@_F?&cWgAk~+?8Y}4t19#1feyBQW zr?D6$l;{&>ib>r3_U6^sZCAhAJzPQKBDe^-$28tT#Lc{Cq<4oJg>`e{fqX zd4nCdG;~~zFvPAU25yF?<>02iT+R^W^I4)LLwjF(`1t-7eTN?V>a15}eSFyKoYljI zO85If;&*AT!ReS`VOsIdd7Nx&acB!s3|W7asDz-2%%RbzOjV3feI&Dwwam4lEqyT& zqANtzSOy4gq+rpj9zA$F2qPfJ%$BsmAq=}#cbnxS>TtAgwMRvK%a+TUZYn&~3KUzv zCKt1l_*bfo0AQ)E8j7LpZHI?TYZE3*pl76T0dX)%sjK1Zj5lP06=pMD=)_WvYb9&iX|1M1K>X9)tig9wPz*I(Dgns>a1DUDTF6 z+1MWn%9jfrX&(mLN=~BRKUM}8ZC%Xn(rc5Avhxa_&vQn_tBk~H4YxVVjh%FJz@-YE z>UiL-ZPIXI;jN_a@O9?$($uiX^ej!+5E(_xu_wFJC7@{lrofitZkD;4BAVtYfR>8P z;0UK_#8{Vj$m=2$9gu3Tkm05vSh&N?Ub z-X;34s{()vD5n_{^_qj7{412rln{pMs>UWIg*CSC(-ZLLOh`ez-pU;f)OJgao2}4_ zx@rQs1Jd|vxux=Wk?t1)a#HueRN=tIPfK)iVBr89T`Q5WUlNHl1xS^(^77Az1EI0n zyUv!OP3fhj?zd7{;>eqNJ6%b1MIw5l-Q}?PZ_4j5Xl6UpBxHa_3 zlf{~C)Chddy~FRej$ggU>cm;q_cb6f%)G(dvLU6R37Ck2b;Yr#2@l*-Bk#CMyKmn) zy9Q?^4-k3CV7zulPa6qiRfunS4tz1!jX&TBO+bqUQetd~nCQnL%;NAX00gxJ325Zv z42;3AW1%1-wZ!vz33U&CuV)g2g&=a+j}=QjqKm}vahi4uf$}8X6f+XmL@t30CUHv> zW3$+}ZpfEb>8;jQmfxs>w@3fNXsJsqwknkCpwV(of2awBNC1H^56-OFApopKpT&ic zNG(j+oCn`)LFXFB3&ZtuI@-SRPwsCC!{1^?*Di;YzAmZiO-f ztY(BwbABoi6Q!jpXWpV9shCaAXpX1;<{D6I$;+oiN`q{Tmu)hiC{>HR z&eu13d@LpxI%X$3du~?Zm9&Fit0k@^zLS1~R9@{`TNyTA9vQdIY4GmVrV$TQNVjA9 zdL&N17s(JJF_{eosdwauJ2ZV?h|flHlOW!JAiqrm|9&;aLd5$zE(fJe_)pbE${1G@ zVU7TjY?3rVZ7Z-$%V_3o^W^L=g**w^1u>hppFLv#M!MvrkB{hUDWxukExm?JK}{Q= zO~`EiPMav}RI(`~Sd4{UlRx&!bQK1>(VT|(K&Z5?WjnC5t@%Yl1 z6T;+igkIRaWHmk3!7w9a=8gF*(^U$FL7S2@*pvlD&-N07;{DV_!y*^P3Nu<6fF!>| z)H}D*R%jE8E8XQmNS)XpAq@n}ef$m|3d2TbGGk13e&BSythAyzo~19el1$0H*0B>RCSk$^M$dH4cZXO?Yft}%C<8@Kn)YK9^UL*>`bWZC+v$83%Ok{O|4P&&l8h> zBto!~(cpy9Pbp>sIQEBF>*_imJJH^A55a>2#E;l9p!OlA^<#Xi$>OSWa=ZOCvD-?( zOYpm``-oobmv!q{wKYNRerVOR_l74zzluD^mF;L$pjEytUzN-~cYeP(8olvgqqF<@ z>&EhFQi5()b>5q#23~Gw1B?e12N0X6LA|Y~rJdJO0_~tPtrIs@=;UBY!IuDd$Y{B!`m_+w@a$-{!C8P z4Xnno}e zwG|wNH4uu<6L}yRiqvXv9G>`aVV_To=8sN?uIo$A-d>s6?Wx~4>9-Tac550s!=acA zf$AC)%d=@n$`F$4j+XNwO6RhV6vDOBY-8!o=j#$25>D_oee$ZU`aXTl%>aDnRjSY*>-xSMe6E|W5L<_*;Am)~@-1KT zu~*&B6*OS_iqFzaRx<^cm}HY1sqQi(GnbsU;e5J|tlfFWU&_lLuw3Cmca^};u#GbY z6-UQKK-NpgA0GxA)78i_CM{B(y>ItvAl1e*Rg?gBn`M8Bq`_hx3ZEY#LQl-ovy_qq z|9JeEL~*yHOH)F7p;ZX=9%9AmYg0|+w*`8VF&c-$L|yH~hz$q_fz;mIDz`z_OFufv zk1qwE^P~!vSZm2~XvfQ*#0zNi38k^h+NZvrD>FSHWOb4PGY*89P7w!n)2q{OlR|?j zp5`biWnfx8yso+60x1m%<`l-p>sQ1}jV6A)%;ILKAK*eH5~-WI8jBB!u&zL^^={_g z%i70Rygevs723#{$e;U2MLUyWA#LaN=@Mw^t@LHaTs##PY7s+=^Cqc`-i!IaxjqVa zUWD3rGNE9uwJ4^8qyj;Rp3?d5Kut@1O+Y-XsrIMaK1IY+6@*}<~;a=JOe&czqVk7dhe%_(iQ-}4QJ^&ZPQ7c3+ z#vMVtK-h1fO4Ye3V}hF&^sq$(UIu|c0-n=21b7_LQilQK6+Z3nxEwLtAM8@I`lf4ny=3H~rtf3bJ&}RZxuQD?JG5i6%9KYb0uH9rr1nd4A{gIG-1M9qi%cdMh{x2 zhrX{C4OlgnWo9qmxy_&O^TSD<64yStdWmLgDNDBp_8gisTr}V6oHU+4QqAiBb`o}9 zw6F`Q+E5z4PtpB;r?`6E>Z|FgO(Zu-oo^KSGCOicteG1vDp1Iq7Rk>c_w21JTtS86 zGw=O+8gEl&MG?#RBP&C^G8v${{>Wyb@p#7oLjpmPYr%rt14fr;OYhIIL3yzVEFG|D?C*At za@+WrlLv9ZCX3nDGbyP{l9qkPe$qtM-==10S#rMg2$7bxT1_3jD)Rl25TWp|RGET- zrn(D$Na`_b1*)eOZ~;OW8@UGKfxTJ|mfmlfe+KhPMxM31x7d5hXnFB{KhI1fA@1`M zwBWZm)ka;=HXj%8` zmdGs>pGIJfv3I)=L75EX zeEoj)Z~RGVI052gCpXi2q2yiemW^dt@`i!Kn{Q5O4xZCO>j{qYL44mfSn1f_y*zNZ zbt>}oHWkTg$k0?vx*nkpE|Ut#{%ErPh$9_@mZShrwT6A!cIj#64RqsZ#)w- zh^gKA#FR^ueHeQC^>A2J=Eu#?qS0r?pZMv@?j+7CJ4ym;G?4Sgm6=`izIPl$4%I$1~Wl)UzLZM1uXba2TKs^Psc?)Y*3FwTQMPVK^~* z^Z>&?h@;s0R>^s0jOB+XkZhV~0z*gD0Vr@~#K=B!m!PDSPyormx+9Q%kab0yNv_YJ zUEaRO??=eXW_)3_e6#I1wLE?a1dlG{d>8?L*xP`ES=jYnUAYmlz&P?p<)DN(`3SkZyp)~AsikAA<7iwkc-WGV8qxX6r_NT2 z)cZZmdU(5i;ZDOuu_Y#!$PVTaL65Gn6pPqUze#B`hNh>`OLgm&FazBsjOX2xiR<1v z%oDUzgi5;;O-Z1`7ijIhxv6BE=1zw43V**^@Xmpy(nw34=6N-es>dq-&g%lc3?=f2 zr8QC;lQK~NfBQQslo@8MG>1YXu+K~tneBUD$NP~|AMl@mZO!j~-x+R#K6vD|}(C#yuhG)|( zKnJ~?)oJG_%CzEvZ>a(4isW18nfIUFm~M2!{-W75987I_Da|_~5)Onn5B~nQ3t$2O z-|5Yx1n`>a>BLKuFdh663%PTM^IimdHR^66{_!ta_GteA7IzXC{S&aQ?b|H5jOE0( zz{ZLYyl~}9+?p>DuZmZuR7ISw%-y)q2ZN_COrd3yY9V<2__~G@&Tl*jB3E#W$|RQG zr!|Va72G11{<4_(>P5BevjyP>dNzhM@@7$zIYm6KQRci!X|mV3J}FXB@WV035vA5f zMf12mmG9S&lSkiq{v0w2qghm_4r5ftIV>(|8RwHqy+!kSqE^60WnLd;(WEA+d>(2! zB^cOTv$P^aD_llS-LVIyR{mS97MZ)o?B`d~>P__MlJX`DCm-xj_@x+0i<5WWF*x9R_2CV+ zpL|OuS{sTFGU1VX7MPV&gWjcSTJ#un*Y;3@fBRrzu4~OxhUgH~Q%(g>krd{tYvrVF zdCd*H!k5515rBu|^>E_-A%7+%cNqzuR7%bC2> zbEWE^KK>!mC~fJ2rNI*wUs4!DzYI$b*v5>>Y?K(AKf#68hAfB6r#}B-E9N;~WtF2>OJ?q5JKUD=algZ_yvww)z#ekzhFSMhPWjZ@Ew+jHk z{9v_nZsHiN6uPr2fI#nNHh0CtXMG>VDF!DaYY-$@`^t%sDko)%VHk8y(X$FT` z(0r=DSwGYMC422Y#)Ard43A{pDzlw+-cJOE#wu z28O6<_+gR$GMkuiKyjCqM=VeSMyI{Z`s(*3F@IKWj?ZKqhkv4hKa#>C(a7|Sa%)e% z26!oKDpD1X9U*Z>umKTZ7}eKjr%!IH{U=go+0jGC15fwtc^Fvji6t}c#(qM1d9TZe zm9H0t9}j+|#6no7Qe`EqLzGk4p2*dEYfe3dD~~1raP#ZgJ++rH3L_<%NFghzt#0>} z5D+a6k3?ctu!9m)G2jbH)!a9Skr*5=;2N}Ot6&5Vq8JhE>wtQpGWmyU=){WDHDMsS!+C zY;={8ytKbuYwv%#qT9dV>7993mPtnxr|bu2Y4f}(H=o;rh!4i+Ng~evzVhn^4>A&+ zPj7?V+T9-?s24O!h%9X~ue9$KIxouL=MjAvs9Wy9slHZbYikzz=2Xf#B!8!P~L zP6NR3x5vD)cwO`%z+;bc#V4iUP^+e_;v%?XUJI1JH8!@yz$$!YRhYC+j5IQURo(5ZgJBD{!w_2o_^$~;H<4F2IW^8vD?qie zc)ZZF=77>TTs}~ zIMjd6DQ$vxGV212AqQNoeQOC+S7K;gyu5%2IYeyB5=HIWx!7cID zQfUe0#qKt95aaOLoQ;lU9XDPZ)@GZlTNIF*`P->(yrQa~Z-EAcG|0>qzjDt_qSEG&YXQltPK0f#?n7=S(<5|a%7wT9X{T83G z2O7hH)3eNVvgURP8ORH%&DBRV%jan#P;WTL3iZI7$lm%tUm#V&xgW8H8M zkVU{_dkXd6&8!OQ>D$@Ej^8WA(e+wR0b7ln63blQPTyF^Fq3+ys=&2+ytS_o^JpzyLw#W_BaSd zS)TPh&rL~l4dv9Jak{l!4BT7zba|iG^MIEt_xNUd%zZ2~F^Akknwl^()aOj)-E7-I zPl{0Uy3pGs#vyyU3l-ccTefq`4}{Ov7Qss$LP394oT+IPNT=_W|a;+!6yPumiA|;IRbei}_a}#t?*+b|{pTqhrB)qa*$)5qSi6 z#(-JlkIb!r%AW~0aUwSLoDHZ^5n;0BhP$2r;ocZo$HwXJ z9K2n#@zLiPISg2q=BjbucF(gs3)+o11E=J^26mV51EW+)GvAJtu z9YjiUlNqLTi;pXv1gbCfm&D&@cgd$XLI;1VT_GK{_@7z*u5LW>2}$I2kp1@(o&bP} zy6ULY&n~Oj+UM4AZvvZtfHJ3$I*w<5Q(}ICe)u=Y%SY>pP)yRoTHJZrKVs_F)Ir3c zfa6=BimCHcKC5kTnia>%hWa9f%&kmR`dclk(stw_$~ZMR!JdEH`3J*{f+k%o)M4BW zjb2%u&6LTgEBMMH04F*K3=SOgXW_T!5+8f;sxJF|s$%<5W4>Pc@NxYNm#!%97MTMG_Ph$$7%Y7cxUuQVLHFIYQ^w9AMpj5_pm@~1;U zb-9rY+{VP1OsVOs!MGs-SW!?K{DZqon(6X^#_8Lx^FAevvMq~YMZt^P_gq5NJd26Z zn6AwC!;(0BuxD&cHr$0u8a9q@-S|FBtBPZ-72U11d|P)8J-F%-R+vN##nKdC-x0tS z$()UMGyHr!bhHdaduFgopX4bBa!H-CObAcrMze|#*hyWz^d6_pNcZ`PfE7n)oC~cn z>xW~p7&;Jr{^a?oZaSBh`oP}GBmcrp(5U8R{I-^?hsdg<6^XjdXh(04<;G=(Ym^kklu5PL?mk+O?Qz)l*X??bO%znjltH z8*49oZ0zNv39Hr9s9!|#*~A?imQwpA_3t#(Swyhq(LsFXPi0>i`zDa6DY=feJIk#} zTcJ#z5fRK-E`pI%yA0-z!CUwueslR=Us6gel6gWzS5>#m>U4oEq}eP!FZxT4T)ao` zD^h(g5AvPkKU7)%6AX&eceAwWQ(HeCg=Hpf>o1$W*$}EO_hgS{n0@ez`kdOJEIVqJ zX~0h^RsqjUN{19vmXhP)_VFM^!U<0^CelyVGu8DknK-dSI!I!J6`0IVz;PWoPC9EJ zi)V8^NyqFJ^QNc0kEI8tGnHQ23R=ws{gQ4Gss6urlPnx<-6Ty6r9|vJE!Vwsq?6h> zv3*xkuDQAL4-3izcfX(YW!5l5aXfUlx5{4i^x5tomOUxEmt$Wv@ndsuOV9o2rng|V zNqpk2-!{^-D(HInq-o}_?$iio!bA=05bx3wZ$T@0(ObdoFaO8*`DB*$-yfG>*Z}fq zfI$#|2|sebkv|Vvpo^&3I}J|>-`@)hjtN2Qcc!T~4n_OZLZf~=i1)uvsY@aArW=37Y?Ap>_h#4?~0P&QC?#JfYdP%fJ7jOy0M-B+ZNF> zRpf;cablz9uEDy-KEPsjry<1oE)!Y2T>0`?C2%6_T4p9)j?q)%iRgN_>7nO@+uO(@ z)s2JO6`s7e2d_)J`p!-T?7h6MGT&Tom8BojZOPW53QWkWt@VhavMMr3Z7rRTVwHUje(_vn;w;R?8 z-p%U7LCeETRRV)^Q@8g-AMKMR9lpN-vuvV9cJu^Z4T#`Zr2X;zi>r@+25MhDl77L8 zr+J};Lz(4JFI(+&C5M8EpmGEGb^_6mq&EXt7-{yB_)6xcno`yq3jF?|{N5BUzmInU zbY=CbD=aVXybj&#USp^ajR7x0dJ`Qe}VV+H^t{xfU- z)Oz-77R8%Tc6sv88CzosKMN?q53q&k#EHo!3=Tf)RBi4OVO~!(QUB?zxrN z11gLGT6rk>9DZN%9$65b7TE`@p9Qbh(p^JnW5f%Ox!(y&3Kduik7yG0ILy# zeW+bQSX!zFNzLp!3|4mxY)`ymYK^uw6%uEl+PfVi(f$*7E(Dp_a(tp{0PVNI0NUgj z6vOt5gCT^bK1L5;&Xkmh(`c97VPSatdCxLu!nI+>wIBu8U^#B~ZO6Wk6yKTt@Pef8 zT@=A&i$NX5xudX(SF1@>VE>Tn4B0BXlplBM?o8-O4C>#;_uqH~2mp4YK+a>9Er6r_L!E-x_m45Qv0=8c`Gi*f8=DRIbu8F+|kyuQE_ zTE1&S!de?^Ss03FL9Tdm4!_OQ7?b@&h)J`ZZaa&UAi>Gma$jI345l&T!_piP@WrK% z;h9HHhjflUOAgQId3sg%gCjgT+L{?ktLhT>0HEy{eCN=G9V@?Rz@(*x=bmw7r(@A4 zKr_mIIr6sKxUJT{@YN6e9AR%qg(td^KD6Zq`PFh9Wd!2P?DGrka3(RI+ZIt8b%jhR z%TDH{`4}%&4oNA7=+HVb04OqC>G$+NNp$%**GWuLU+_Fn3nPr7&v759-z*SecOgOqHC zjt^%!ofOl^9mTN{+k=v!O*5Ql0@Qtf_# zJpkCE=RJ4aK6#FScefQ5Y}s zQPu-aQV!6sq(W=P2D;PQKWwfHt~{lkOFV4m_2j_J-P%*DoKLF}j7>eM2j-=A z&>QvEOgTCc$@OB9AZmzbF#y|z2yL^x_eVO63CWI3_*lV4C5WhfZ40Ayg?cM`*1omV zXq{d6sIRZI;t}{COK0KNg!{ey0Rsk%4ryr^-QC^Y-Q6i5T>}QvFiJqWq>)hRE-67k z>4p!Xf)e}9@ALcx+iSbex$hI#`+`i+%g@XKecwg?igueU!m`K9Ow>vy4Nwte{L$3n zFzL1XdpU?DJpudCwKk}_TP4|wC_^E-wg0tWy#z8m708|c{-;2aK$%c*D@yn8b&dr< zo6<3Qkl<6dJW4@2muqP!AXgPcKMtp*XOz%`Mk&2Jffrg z4}a4$8T1^p^B&&l0yd3L`!(~XX7YxRCPJzfRmsyUeSO6S)31D}_FApG!4H^wc4<$V zgk!HyN)n4Lj0nebu1@1-Y%aaqRnGOe0=+a4FOfW!naMiDhOP}piX`35>+-O!7eymR zQ$RX@6X74f>TZ`9CO)nl@J-t3{#2^WD{*a59CTSN9AKsY@!^=@4LH#^B@Q?dm!CSi zVvEKWP(_5ufyzwT7$y)|A zNotSePkYHT&X9b&HZ8sOTXwPvQlQ? zW-;;tZIk$|I2{Asd$({qQdwaUk&DzRe%(V6cNP+iv%}6e7H- zwLso8k`I-voq!BlkzoR)mFQ`_+{N#H{DCDjXZn}fo)m4JxMugA%)(?pVIo~v&zTMI z)21Wc)t`NO^R|af_>C0t3I4Jy6I6u*&i4WAEfd~fnW@Ryn3Z5!cL*{3zR-^)AtEq; ze(M$T-;DSQ(EtWWM!3&s0GmBSGOfDaTPPjP6un16wD-5?1hg=8a!GkG-C|LovxLWh zcICD{3WPb*t;NiGv$4Nl#!Kv=uZYxHq0_+mE@4E#XY4Or`Oj?P>R9m-*SyTEq@yrb zgHYWZ-0s7Tp^*O~v}?oQkoZ3=kMc+{oic!O3zXymF}OWtMWe&z19v6L6Z8A#f2OWI zj7sx$8u1hwB@mS?ps!o4_VU7V>b$`@uSb01)%JB>%37KmlG{!LAr}m*T%N1TRrg#l zGfJj9t6#EZvwN1J4+=p=iVm3db)6r)ar+ft)i%BYcU%^L?2b|j#Ca8|{)H-DG+G-H z?%_&pZteDW-ci9rJIDr!S@SDTXgGOlB%vM}Kk(@+F_0BA(C^~zgVL$`*^$?_UU8Wa zl^C%4VSg%tG9;WRTrSR%#EskxWc<`~f+uL>?xixjPumqcHEI^KdmdFP8ra^~Xu1E< zUw^+ppN7L*_XAFiq@vI*OmW@)h!*#ZZ;Ux?&k}71hj5>o06nCYC6C(ge=XM?k*+H7 z3rNX3s{6~BAH>P8oY<-67x0wRE_;B#^#=srq{30H*->Q+@*?W){V*i@ubfY=#`K6= zxbg=8dH^iW^xLW|K3QK`+3j9$kd>A&`G6Ye+w^I%dKYm%i-^L@Aq~%V<;*FxRH7`> z7xNnDIISu&_xp&;gL9Fu2w2CTrH)`13lHl%{*1}Va-)tpO3FkQBjI}+#Qbz0LR@D^ zfWIQ<^yBdo_!W#`AP)c_kww95ObHmvY$L7g{m`>0c;KH0lx zl=RYJvk5H;$RK}dXY9euWMa|A(PNpaK&Wo$K!ec(b<^*xiR%6fC9oHe;AeZ~_D1zx z?U-Eyxu`wz6BqzUd5qZjJgRyw0|ry811`$ECpNEbu^*C!MBBFS|mdShE~gQjXUJPM~n3;92^x`8)7_E;@dZg+BJ%!7w_8 zY%kn)Fm9ac%F7cK2&BW(S9>Kh5D&>E$YdAfW51NG`7H>Hp@-j~w*H!aM9k-Z^Cu*V z`180!ltusHU3DbI*%0|X-=R-35ZRd@icJDI0yX^7aK79%UcSEF%Lf(y)WekhlR}oX zH0tWm`Zg9FCbQiIKnK}saOS5;#sLT)qwOFQ_86=~O-JfrDC0n~^TI(;!a_x`0iF(Z zG7ejjri==6(m}x84j2F^|H6w!qR9n=6IQyWioWSLDG6}QEc!WWguyva7k!*0AF;Cn zeGt-qM@(^fz_7=M0kYF(mtgp+xg|*;NsC(+4M+yq2Ai=;V8{=p3@{!D{MDqFA&nd; z(4ffzxh{8gV1Nne6W9BeUgV0|)lY>H@(Wa*&36S0Y6v>yn*S2Bh(DDRDP^PJ5VIulp>{TPEskkhwG??N{YWoH3d-C+()K>p@}Ci>5WS)){9mQjQiQZiH#ff8q`Oc2nD!N*^an=+70LsAN zz2S{H5KVuGrwqV|ngOugqRr63!sRjrL1ExHI7g@ae_K;%herV^wQEANEccTSK1Hj{ zm@aFH#Z-5PJ`k-eBg^MdK9S??x^-L3&+*s@V|(LJ8yGkLDvIo5s%&*tx^h{X$HP$%FBA)jxTRy42;c}JEU@H zbD8~Xo!tWY1W++=s84?ePNv{tt>Lk$>S6&~fpq`?#t;B+!|PgL-;ppCkl|e+iW%vy zj{D2Vb$S!MdIA=2M9INsRU`BJdt7jK?ZYRVtAuW-EaJV5_x3ioH!RGlRogqQAhQ zNwfF9C@DQ9`*vQY_@&o<9a}9^n6hK}%AUxonB(l^E#)jl9*zq`|nw4i>ra=1d7+)|J#7>xAIw|M>`-8}lg9@|d2V}R>@(j7l zYOjDhsbPjxh`8Az$pmF-{atNXjYpX&I2jiu^4irA1a?O0wn@O$C~j5DL7aPSYDM~N z^Z_cb{91V2g$ZzmJ{U6mK4~)Ov8S2|}&P%2LR?M^T14j60&?P_GCy+wt=3t3T zN}qW&ILsP{{vZ} z;9V=3oqM6VrEzc|-=!qJe#59_G<*~Ex|6aNg{%3(3qV(r)P!4;guRB+##N|=|>rAw2gA}_$vkSfdtz-&n0t*J!rh~Z2(kPM>U&I zKWK{JgDx8qbc>A1Jb+wGoo?gzXhRKag|KyIS<4Lvg;a%k+kMl+cpZaEXijQD*VS;I zeOga|V^r{>XhKYW^_|zOMyHlot=y>;otxR~1|uEO>7vdsCwJ34A^l`;=bWzvHI;SH zanr>RTyayNnE!r0U%9&IPx6;?a0w3>yo0gN$q$~p9$6O#Y&{NgW>K5pLj-sHe zJ1SL=tTp-RJ|Qmg9aX2lz-(=&W^H79LVJoxP;gy%$qjC9f}zZEL2`mWQwiFvj#wxd z*7u6C{B;xHzN@00`^AiZhu+EtdsuzP;&UD-x8th$pO^~*Kr7V#Czf}&O#U|StDvKk z>4s!fc!z}9m0ptsL@d-%FY$pt@1fYPU0eNaXEZEg`G3Xc= zly#YL$u7E_0G#S+RP<;L&~J&XkVjNKY9>7qHQu_oW)+6aG0MnqMr{Y|GwtFLS5?=X z7a|1&Eh@{I)-?wgh_|IHvTCklp~D*#lgz@uEkM+NGXI3DQ^FMf z-cFx!@C8{)5H#n&D=dm-mYPCpLfW_Mw@^mYEY=!Ls#$t3+*Nm`vWvuu_O7eIqw8I>dr6j@`>M74oyZ9r!=QAlq z6{s=(XE3+{Y=-$4DeQALqOlOnCktIN(Sp(~th38^!Z*{2M33SV`XA1lRUI85rxcGcye80O)qhTcvZ_}0{QuhR{LyGx|Br}g!6E*}kud__?jLZ? ztIn70^eGoM>coYG%beC<#V#AQXZuwafC7-keLR)dT+1Y$?}RW_h!`lFtCktq$1-sV z@kHc?PilFtuhi%On$_w$4_<=_s5mI(Y=EprFdho}BDt-Yi2CB|w=UoGZzklqPfF~V zl+XZ+EUgoUZ}Y>#zAu(gf(8@T@-+35sV7VrSJLC{yp>&*Jg)L@xAZgf)k66FjC6C! zdr;Y0-b2wUEPbzA3)Bd6GBm??MtVyl=%xo`Gi?EQiHdnYJO+~if(nwah+FRU6*8|1&|!-!0Zt0?e?m7K z|2%#_^2#wn9a^;?#x)WiWKTvGuml7&NOLIU4GH|`(Y73p#1DEjh`(`V3CD*x7&Ie& z8{-?@BQoIqbA*~-W5B*6LuA>HF$)kffUJZp39B=|i>Y;hYr zFXtny>jHEBa5C%fjd$Af>wZ66O;3s?>x$a~HPY#7fIj#ey)ng3-@owxPeu~)`1xUx zagsNg=6(t%GgB@5I_dh`00382wa%&AKMP5OUkiPQZqBcWBH}>PUSfT7NwTZxOp)~V z1s}hZ=@!EQqj>)F?a0wXkokp9lWO<1i-YiQ568%Z;USaFhROKp@cGm~Ifi#3-q+7( z(%u1yziU$TbyP{t`m)*)tM6At0W2tZUR9d|vi7!Xw!FOB>v5=d@7|%n!6PWnYOGw? zWYX6hS!aW)6%a49_@)S__5l6?R=EIu|o;sd+4raun^FG$(3{!2h{XH+fk_s8|% zUFBx$AAVTCF09C;nzqx16@isKi;^=Z>)$4q*skwAHC+6DL>>KdaQHhyH0H`bf6%IR zq`W-2+<3q7?C`0ojT?Z^FyOtxgNZ&7T~@K47&B8mI5_+kTazGA5JZDIy~6F&@Njt` z2g|Jg=}IF1%TUP)*3N%6-Q0m)RQ}sN+~QH_>vHyGDnW>2S7r+bYIpBxw~SmY`KMop&7o}|frRAJb)g7f$k^;QFMO_79|!Qg&^_q|a6 zTRfiATQU}|H_D<@X$33&0NeyLuWsuAR$C-~@PBJj%SgDAC*>=LsjzvsJzP8*lYf+# z9UEk|f$ZEcf=J29b#!}oK6V#~>4s4{rVrlFdZ=W)VJ_G3i{Ua>72b zuXHz(Ke>k)EG7@i=|29F$pygKl&VZl2S1I zPP4eEvDoZ%ssD2dN4#yMo?Q3ucHv@A?WO(6N?%z==y+Ox++}Pn@SU=$A)92>DCs!< zO~zs81*2DGRhF@p;ZNh4AkxA5hOdx<9gjI|FX8i5%L5;6zX-&kvL6OtS zKc+16A%#?$$+($ueNC~d!RaARQYEvc)D6N;+FA7D{PsOjI)4zGh)*tz@GPT3-#tg^ zq3owmnYfje3`t?L*BX_U^?sQIJ3G23*TMX2py;d%JfFZYno zPV8s^x8(j2fx0P*Y!YQBF(q}y)Qf_j!L==)`kM%#sgWa&@Vnnd)b`vjWGr2)XUT;4 zZxmBeG8apn?_KEMju~$ci9yhzrA(i0=(~u*{Ni= zmBOsp^DUI7+Ir3bWg{l+raGLm22L!3KAUL|q-WziQ88adJsQl2cr@Sa~q z&^}pOMz_y*KjvUDsTXn>YzM(3c=k7zc;Ef6?nK%tY9rh2NbEi2Uk?vQhf55}LGwC^u4n&P z45lIF_M|s(D3RjLxs^mUuI3<1mdQA{B% zh5t`rSvX+3VjH$%F)%v{&rs7~saA&G98tl!>D8B^b6Ym0m7nA2f!fL%a*Vrg=BwcB z0MrCV-S@~hh|8Y1&6XG%$~FL`o{^&A$5C zcI-z1eqtS=YS)?Ip*OLUGZw`SMsSs+H{0%N&$`K)eEE}Kl#Yh9J`n&2qCqd7yf9M- zqfuQCD22}zQOgcKkxRR!#H0#{PCtnN>H%uP3SpLr6e5p&~Zq#Q@oVd`6y3mPt-8dB#Lno7v&7Jn<&X&r>$#j=<6ftAq(0XEK~`sbnVIX5M5QXsC0^@mS+v16Rb>E!y|O;!DgVe-x^^Y z8X~1*gN63gOttnccv=@>{3Q!iJXO>dWh-u4BRC{>^P(WiRYnJ*8S5ZN-~QAj{w!Ic z``W>7S*16n{wdhncKv+al$D;dkHu=4DlP%<)k^zSL}=JGdoS`r5NOn~Rug&&_(pX>F%MB-+9u}N<03}e~FQb8#QKRv#E`!B$(}v(v)rhUMo)bD|JHbw|fwLq;Fux=2e-Kfhus zLk(0lHo6}NykFLFtdN>Ijc5NaRFS-&cgSatCZ)KW8|7yYGzx7eC$0Rid?pD66O zmowijw?~BeoA z_iZ~@&$WUemV$afuRQ&pqfd%bp;FoGy@(Zv_%!i-Pz3Ee)YoFZTEOmBLhf6fsLzko z_RYJzx-fNO?frMPgq~D*MDMKL>C@lAsI_P^KL0lXe=$TirwiP7kZoJg%+PkJ{q!hI zl$x2sCzNDjM5tht(r7Nk6$4n{-NR6roz!tt3-D7F=ZNVm>ZG7Dg#C3?Fh9jk%vIDE zJnGt-IQ3AGMH%B=03@P#DygT2(UQWzird~#RF1F8l!OOS4H%FcF#Mx_UdQA<(IVnXwrm=ZkhgJRu`bOFc71y0cl%-W+ z4`y4_WA~ z94Vyve1w|cXfsFm^&ncpqCNA6uw(K7Mgh&?eCAHQaCtUIC2-}{?CBSInK7TnJSStb zpbc+SWf%eVnmkOf6A(=>FtD>NT(++;lhB#w&cR+uufPUD^#Wk2q2KEJ2*Z>AZ0S|+ z`9C{;rrE!xSHQ#!G%_Hbs7|h+jSsiOqbpO6oxe_2AY%~E;&V~wAmognH1Xr!m1DDw zTLIM1xegUv#}b!TmE{boU1S4HJK53i`C=F|=&n>QdW9Yq&^{7>E+Pc7Ms-i5(3?n3 z{0E~Z4-O!UvHHmX4=@RmfKpFvsM~H}c6q-66`TE_&X~EVUWP(vb|^mT1_^)flgk*6 zO%YTMaD={;%~dN33pO!BhN>cRH4H=QpYtiZO{Mt{yEqlC5<4|yyPdwev9>b~cR!@h z8?VCM8B^FFpv#mkZlCl1=2Mp9el!8jn}`G=7E_2hk&>ibsijs4#}=oRHd7IH3MGetW6rYy`f9p(y7S`C+5QWA^^zqM{O-KCJG+RL%XD!4abcJ{# za#W>^?7WkH1H-`&Y+@Pl`fd@l>-OHfYC2!)3WhHkC?cjjvThld?CEK&aJkbrT0=DP znX7(%za;%!_a}p{wA1ek^H;Gq#JgP`Jlk1H<3-ehc+3|wv3RdH{A%GBr65nnBbxIVbU$fTLVN+Pq=q<AC`otykW@e&>*Y-08Rw`6hftCA9-{@pHfT&oR zw`^Z`-#*K_Gu5jjhWpcy$L2zAB1I4CFJBLO-k9wk;e*y-3&-<<0S39Dc`i0eoK(4# zks@cGa~JKPIwx%B#7R~z>3^Y$q`ageyB2Izu7Ur$7HEF>P7X>8mo^d1_O4`>bbCNnf>=b^$ZuWd%p#gdQWSw)2GPVIjR6hwuoUE(C0#D-r_VlIrul>Hr^`%oTYN+Sm) zH@+W&kF<-&lK|&XA%k!%c5G*57>juPF`bTjPgNLMJe21;W-m^nKASVqv29bwjtA%1 zafqIdnpyR|QH)(9bL!_=zfEcyS6BvI42=uM?nSityPAi_g(S9#6M`e%QzlYDje<3P z98)lQ4tb^M&KF&br~b1>b(mP*zYJbLmF|84tFAKcA#R}06;aB9C!Mom0u=IOW7#$v zHf0fUgUp-!b?hi=wVSd(4Z8#9g)uaxrooeY`RfLS`=6fXd2*F559!QP84#ZeUeTD= z?c;)4*1=iQBLgwmh14|fd|_`nnHd6;L>-xe;}5ah0V52cnA?e2s!AQgiJf;ABx#8q zYXmLI1in4IM#&^ijTZlIRX{XaiOFI(3yrDORIjw#-U~c21+8;BAGaQkY^N!DOPc+1%V69rEcdIh<1HZ@DOrwOcdedGfSI$Aj`V%m3~q+aahr z=+v(vO}&vJiW|l6`YnVn*`^fD=D(_^@~e(7Z6ts$XSfX8KF_y3aklw9#)F&x+Zf{% zz>X1#hnPo7d@HAiLb)E*ZWnRLdd5^maJx@yw2oObV8l2&l7u>p{c}h{ovd~f)DF6o z7Fw6#n2?w1MsZo5>#gc~dH((Trnrg-QDfCx#2>`LbYxU;u=bYSFvufu5O^)z#j^G?FfUPXy0p}l$KIZa_V7aPb=$<4um!@d|a!gRRC3LYF~ z0c)&FE4jBR{2;n-nyR;bC0Sp=2=bk$S%-b=t$d#s1HPWP zj7gLhCX1bBJQA|(W0<|`y9$H7Adp~)Q?D(MKW$3%lt9V!Hyk-KC)AP%N?*4z!og&y z{!V@do2$~QQ^b6WN|CA{feW=n93d93>i0(I;`I2w{cO~w`-vVWF@O;yfJ0h1K7$KR z#h@0XcthtGL(sDB464#+k|VM3>V~U!K(G9>TnUEZ*pzhvWrB{j0fvEzeS7e@MvT9qYWDJpDltT@b<_?RPMYdOT1tH88a z&tcUO<`vkM{J8ZWArA5v93b?*!+70-dg?n?MI+x(K!QMlF-E2HD8b@|YJJ=A8JC|Q z#aUk!yXJ|ZwtY-&xhgDzfZTp4L>9&piSrU8{JWbRk)~Uh_i!)6u1g$7B1*371%0Zb zRkwG0wCapng`jnT)8MWvL|y8j$uvzy;+F08=6`>o(L>If@(D0mHm3y1JB$RJ*J`@H zf3=qEbX&~DQR8Rz@CN@SZ7u+szoxzi7L~Xs4!aGKQGqXKr-l@B?M$)P{CbD`W#!A{ zCFg2u(nu|qBq~n4N}06J-zf%C!{wNYJ#B~Eyi-+Ff|h7UpqeQI$?TUA1#})a)i2nC z7TkxNV#)mS-m{@mIXlE3;+P5lDShi<^4!0W6~`apu)CIrbxW*MRn7sCQTW7&vz>6`;y6eTI395JX`vSDKCkype;s^U3CvWus?nMfkTr^3u0|p8o{Ow(M*pRzj`V(&QW;&4 zl56A1lEH7hOcDJcXK@{ZI#QF{5mdX)!WLQkXLgMy4lLUid1c=6!mI>g2PVF`*gmNlF%u- z{br)ruvK7M!BM!X^{emR%G#Yu zM;}&H6)ESM?klABCg;lfv0v*-=EWAsNWZlHZtZ2ysHU%UJyy>HEfkOlLpUDo1Q+sx)E0E2i=-EVeYv7+Nr~q_(^U5$Ft}PS)4pS z0cm)%IG9yXj?aUz(oqqrhl%B@YRc<1@A#FAO5F34!pH;G1Y*~h4OtFt; zI@8t!bjD;B$3!L&r@1X@c1~`RUPs(b+uzKgcz48B z?X`6Pt+A|{vDjKZVR8>sxcPnmHy~a^MX=j$%>V#uzGU$+-1?ER?HY36Dlc=gJK0Q0 zL~uxSYasixER9OQWh4n^Ki)wwGW6K}Gv}k>WW6Cjtno*I>%|n31}n_SOGFZtmX7%< zM?gXFFmrmYoW=cxAm{%N)f{t!2OWwD$1`;s$6+p9#aol}7Wb1}P6QmQb+n5umRlTwRAbEl1z*F$kiPaTVKL3`X746$ubK@M}#heTZNQ z#sqHj?W&;zv^to^V;P6Em4AVz{1L({?(j%Qi%dA_9U*Qxi!UjXmE!`>@?%VFe*36+Ecxq6?;M;#uSusPaonfGI#mf*9}__Mzt0JkmanHePW1 z7`3WESFzN1qI*3zGk1JE33(!O|Knr4@|?`qd^wMpF4fo0b3r7_EA{Xdz0?Nd6J6<3 zN(KIwQehTSRd(F5iSf9680BAQBu%tdR&-x9Y5rO3_I+C!J>H2?Uf3&Wav)$B6OfTe zq>{nrc;K0`Sk>5h2~J)>tzfVIiqAgmYhvV`6KLI-*g^X&TJD${EpDt#LRgTL3I{zS zwtfha$i^sUtTz7(RaWHt%FF=iB)De7g017k$e|5KI;`3bxX)gQbMB|{ z&R2KPa)NyOl@jB30&wT%QblKPH6?Z#CtFD}VX;}t48mmKZbBFB8J}PZ-;h0Un(2xS zUT3md?bUuUJa_pMapn*k@hl(mTaJAemwG&P^W^RO2orEe5+z-xoHbVzVgrFVEN3EI zG&U@(L?dR_Ms?1|0I@+;n3K1Txlc)ra4k37KH%fBf_gNPYQbiD^<&d-*Ktf^T%2R$ z=zC+PW&{6-&i8pPh-<~}CZO2=HTvM`SZX8R%J$H8bVk-6Rm1R6V8FTfD?H-;bL5Y+ zD8$oxuQYGN@X6sfe};_lymfbFNzKdHdYc4VD6NdBTqV?NZ^b%q{3IiAgJ3) zT2b6F%w)A^X89MY$OdDE*%ETDIuX<$ZsY3ggO{sfbg^%DGK?J7AZt~8WBcS{CnBzQ zealO6+>mc+`zBJwgbV1VZ(Id3i3z`*^Cp4(3VPLB7CVq=w8x7-ClW@ZnrYnX{=}ql zi3$)^U#UscIX<)(=&@G{v11UpQ2;MNapvUQ2gmQ&=qTv&^b7~xgsJaxSG_fV{i>fE z^h!FYlNownM{gGR3*~8&U`U(c&e+x~&PT##c`c-;;h7v%YGJ zEf$!e6@|-2QHML(Ymcg%ma*4Q#!l-6In-StJU7cWRwkR00+8uXi`#yaWAS151*^bI z-n6T;XaIli=@!+yK9nWd5QdzNwpXtRh++WP!Eg*pY<=^-(={ei_5`WLBu9XtVY7|ymKJ85oZc%EtLh(uz06&ZcKxR(`0F$TVk~s6JpM z?o4B|EMhXuUilZQv;eeE`imYIRDa#n&2`)uO|(LJOwM`TAgTXT$yjyHarW?+;|NUm zAWe?u?jckd!RVveio@nciU#_zqY`GtmC@#Pa(EuR>No~Y&NQ*)A4UrVQOKcp=O6~7 zWhFx`y^dlm=nY)=AcNN~Z$shDG#lf^tkl{ry6!CmS6dP)VY!!-4Y3QGtwOV@e zkGMqiZaiefmuYJ}b4$k|Wb5XB`>bN@f^c=OF@@IInlfHzvZ4%SJP+Mlk{EV+p}#=M z0;Doh#2ev_qN-*9ogp>#LqVxBp9?#uMqRp|A(5gf@WaYUGPdqlH`%w3PM~<%%7lTy zS$5Gq_RXeC!H^rnDBi;W`Ct4LSn-k*gsHvgaL8N-z238?`nZs+M)|Ajn@sAcY~hUS z?|9FXY0)>$ge8$TE#qNtj4;7P! zc!VaJYXbfGGrpd5IB#YXIQM~B5+SM?iQh4Ldn;EG3*2(Gs$x_}3|#;FqoO37!ep;n zlw+W%67tFWU#KFNVG<0D!dZ~Ds(Q*>TjwaP6%!R$iyM725K{$QK@TbT{C}z3_|f)su2M%ajW}eBj4}*a-Fd$9#N%r z8VHlK)c`;k06A-_r0GX>@U?Vpd_~lE)8xK~{jENNlBLq*gX#Q$3v}|8yEAMOf^;lK zTl}7)3(h&%5STZIjRq)FBngb(QH*~;>uVr<%ky()zGm}@WrPKeEk&0dEmzAUMT$b3 zp=64L%3&~JzmmE|MY|ml>Zl1rkN1;Npw3rs2{tx<>UB@z>GA85>Ap+kjN1@Mif`j) zY4VH9nlLZLPVRyu5YI%yX#w2EIC8TCBZ}RvOO=&QnYyAquVYn)D+OvxavVYsCs#(Xq?ye4{IDfrt z`caNS#i-=r-$|fCVx&#KoMBL_dQMYnIqki~OJg-a)~a>6?DbDMu;s@kdPpQo2S-sX zc92TB;j$+AC5g)7dnxE*_|#K58f`2Zb-Yt17U9=YZYqjaMxOMaXtO3s_w(Tl7(!h^~kzwTei? zt#s{1T{X2*wwO3uk4T<)c!R%HMnieI&5ka%cn2L#e}dZm(6P}yXOdp}@K=^&^Grbm z)vpE!?iFszK!8F;>}W}QHO0DK$%EI27nyeHPI3x`7vU0amC;|s=a*9@M!Mgdsv+`h z+^60#8U`vFB=_?-s;U0bLT)^j5*F{|9dv(jC71+5S~3)KHvCnZR96X0Y3q*GI`r_s zKqMcwTFSO3hy4#W=&P}zzLH><)fCBxo%rwVpt3Ve^-hpF9^i)FD|67D$J%=3W*8>l z8E&{Ps+_VWfGE4^`)w+})&x`mH{#L6ODQ>$96Jvh zJ<)%i&1(PkFKP1w(6W$IAx(;7PY!Gz2Za~7a2J&(a-E&r`je+b@bb2JG>lL`BXX9l z?7hr&sTZ@RBB&z^qWh~_^*QwbXF9QuJ3DEP=nj5s$q7!k9f)gB+pdHN%rW}KUt{o10m(S+ znubhOIbYn5{h@PW$X0zJ|EfHCxdxXD$DJH~(y1l!^ip#&VmjW4#$nEam_Y?k!{|wY zB&O4QvDEyC^gXKo)pl{g*Z%nK9>c&c=R75`h2O2SP7BW^XWzoZyeb6yY}g2D8Hzy; zttE}}AiK^rSSroYf&n8Oc%IRs}YSIFHvQm0riGX0siXmb* zbF!{^1SDxJ!q1zsqsZ%6l}a&bm%uVb3RTGptwa1cNANr`js-Lku60z2@^_>E8r@!F zq>crkw*`o)MCOtQ*$eUmH(F~GW;K^M^}Ud@Hga|}$HaV&%XLoa>ksZ>{Hiomxi7vU zZo)qesfYgyRip?5s*Pu6L^LFnRp|OsmIhTKj)`rm{F2r{0N~OYB|6c z=Nv1kCP6`XGp#S10ZrxrNk_CgaA9PGe6Uz@y!NBzU;vtRsSr;E7XHHVmw4+BDF~cX zvL6eSJT+I(?R(LceSrTh%ZsLPEx$1+@FT~{O^vYmSon+*1GNPa1?4ZEwMq@dGj7wu zOvXTpW<{lV6#>0obPiAu7l@rm&rEODaLvE;eJyRu_yTak>)V`;N{BqjtKMu2F0VVV ztwO4!s9czR3@h?~RtC0{!h)7Z3jeIBc{)8yqZ)D|wT8NkLM*L7yI?V$5j`xB^EE8s%n4@B6ZpcXG; zp_IpoJ3E_&=7(wJo{;AfQsCtU`d*or6N;?*=gjJUg=!c)JIP3;!e>djB8Z9~?`hoN z%Iy)~>%08+4U^R}V-U|+7`cXW3{1sp4cct_PX)S%m9Af|K-+|@^syq%w}9NftlBPd zl-TH<-C;a6QTsGOuSqoJhE}uun% zG}W#Le8^I$N#A1HB9ToYs@sYO!4|3Jx&%F8*D~5nf%}SW)JLQBj+&Btd`f)mSb&vGD`G`+_ zbUg%Nd)#wgwA(zYdYv9U*jh}`L(4#U*OaiOYWJ|5rFvu7Y~dKcLolz?u~C{f%-eCD z#KGAK@;=+BBmv`f>8*~~)$LBy_AwX;00B_^W`lY!UU%#fP7Lw(PwNu&(iaP05XT?!3>uG9C);hVU=y?q3TFOLM2}PJ}|K z`*1Z4W84>w{^mOG|K1Dsoc7c_pe`*c`~&m&J<}{qk%fS_5ru4J4a_cG?M`em&ZOGYgsuj(G00zsz;_|!Y?x{2 zWo76eTOQ0~F0t1lNy_#muo99<+ZCc8aV42W8AJ#7K!jzIqFuCh)t%S`{212!443YB z!aAZIc|mKILqUb9adzR|F%A@Vw9gp-$I@9aMA>~`dxoJIU`QEqNa^kzx`ytQj-gXT zk?xT0?v_SMQcAiz1nCe3QBZm3`Mv+oaNql!eb(OVT5G>)SA#)*tJlAi+8Sy-R^Cgr zdFAS{u;Ej@zmb224M4|6$D(%oQIOp_#(6rjQwa+{fT1&`{PynY5G&MQdxcae%syrl zoBnG~hVlL{Po{U=YuVrjz6Isind{B(3Fbvz0)uI83?CjYGU|kTi@Z}Lb~mqN@@ID+ ze>Mhl^!WVEHMe!V>DkT4Il)h_XDa$9XfX~5&54Qr`Do>BhZCV=A4czkzhqF-6Gb0Z zuuA6Q`07f>BQ>wZyjIQ&9upImRtQmPhE(UWTDw=%CNC0@QFsqvN5-$<=~Dk$cuCgu zbA>PttIa75Prcl!DhYm0*Zj{hPA2^4D;zHpIUKj}3@$yJ5L6~708JK0=-mrB zlQE7TfiVTk0arn92hHM#))1VlWEo z?~X7VwP&Mp7lu{AFLpZqYZJ%;pitcrQElU-yO$qv0mRLi6lUZ;uG z))uk7@K`>jk@?8e`}Rc$KoXiG;MRdzB9@iS?K<9;C~p?!=1lZKiGKII2iO1UU;mXx zotNQSr=@|K=?Vzxz9N#fD$%p}XuDE=>MURJU4@I6>cnyO8`SLO+m-N@WB{=}1KU&M zzydl@hdquTyrFG!dX)^2<4Lh6ECHm^(q&WVSmk$PLQO2+Z-gHjjwKxo=@zd3BY-fL zCQi{{{$o~Rsn!1~&=h>9OAm@F?Hx*IBzD3F>kyFzj?|tSrqJizR}8XHp za|;b@0$@clYChfX(J{u(X%0LLLVohUS6iBoQV5>?C$~>0IX%`&T~##8Xy^P8Nen@#~)^`!=YOE_Ijx8X=~5=+MrN;rqGBM zV}E?Hrv`}{YIqAkqxeyQB)>i7U2+&!==5jZ+ow=~du8mI^_?}vR~hX%+OaZEi_0;eg`<|Ik)K;nOKO$c6DLHED9iPoQ{Oee`ucuaD3JX& zwYMTQhu1btRw5R^6#W&3{$w+X>{ZHuv9Q?{8k5KvmVmI!NetvnEebAiWM#(z(Oy{sVnu{m%DorAKR6c1C%0l>>|mi zRYD{#a5SMa`&_z4Q&D^`FSlytk;e@`WE;QeMLT~M{sjF607eXiW~#mf@LGK#!p5tM zFvRpkR6g;U6^H6jHimZSkWl91 z-@lZhtnArO{%<~?qCt!fBqtrt@^#*%4-QI(qdu*#lmpY*q8q=jFatU?O9(`f@_JY z3iWF4e4;b%el>gB6e;JO)zCvW07wwSruA`43?IH78|v=7TY}s;o`fAIXcL(tISu}% zo~n0_7v7M^7=GAZNm9CSuov)Y%Tpr|7NB|&rpN5JXGNZzlKh#qT0ik!^8+%6Hzb`} z_#FHBM7?stmpK3$TQbPLNjv_rFIGlnVO<%M>x^AO)~(Nw)2FMqb-?h9V{WZ~JGn*I zusuCyUN%x%54VKaWHza84*;qGG75EkvqGm00Ir~3(=6yXQ=S=)oE<-&`hJr9jZOiT zclpMO$aiwMO(hliM(ou**oo21Y|}?deCKwowq|t-iUm1qy@mFuyV}D>Pg6i);H@Ru zSeA2&zyJU-3{W4ZN%3FVp(K@TQ$3Vv;l%t{ZPrO%OB3J7=)%?BI+)HyDhg~6V-Ft) zn-8m~qVeKMiSR}KdZz8alwvF*~GJN_?(gTSs4&=b4bCH;Bl z;;9Jblh0A<`-NPgkPG0sT{5TYf(VBHC?S_G%s4}irlLCdu>)m>P-23=Z`i2{2y+Mz zoB9*t8c_B{?!Zz#^9NRR{gY4HOB|(exDd6czOx zKQ|Bb*M~}Vinu|Lrq<(7+;L9a`h1Cu@BGF${+>~*PenjXS#3H{2Wx72k2TqN9oAz}!X0cD&)(z`UJ^je%*NKwN6Uo`+wfS0L%*CQQ=C;=Pb zvSP))wKs&>j8W>+#F#rLFl>>Ghb<<3_?a%g)0-`GioDWQw%IhQwxt_8>Xj7#zggeN zH5e|bMm^(|!z-5mF@jKoiY9%-*C>PMi#ptQsFff>)Su9h?NQ=c&HgtzF-Xt|I-H#X zc25EvXB&!WsC2s~8Qy?X(qm2Ib>V#kBax}pgPdm@(f|NGAB3qz;!{;?lPd`uE^ao% z2*p89&q9T&9Gh|S(^HGstEU3I=Kkl@?{ob|p4p#{Eg))|i~jNt)^$|smI!>@=NFBL z*cIRi^v^LrEfq>~vS9M(n!9A-A4H>|+XMbpDY)MRk)ZJxLp+J%0R2h;fZ`>!mAI^e*L*{u-_1%@OeQAGLEx_Al3Q?;CQt zysxUA0THuCiz^Zt?Adl8~jorAPLKN>Lg(bvfwwV1|StbZK>!l)8MSLybcKQ(@*KFDUCI$(+AXev z6}{a*<4+wFJXO#yKCLP0IdFYmc4(W01?H3UtE_(;&*jr~S2{0PDw8*E(kr2XePV;C z=yw_%KzY5_q13UwadwDPRZt&{w?Q`qFlDEx)xSkscO*II1FSk*pb1EvVmE|pS0Oqc zS)=!$+2SeEScEK$L`LYp7N!~sBY&!lbTn`GRIf3O%D8U3W|$HYUiOz}Z>*2L*Uj4y zR>z{NW=L)NxC;Z0cv~oU6}+NHb+i;{d>9D?h7q6BMV% zt2bJWjkfjbZ`jIx^8o=y$ezkeD+tlw^zqcb+wmSAXAxZAiBq=H^pF2SzUgO+PNcz3 z#1G4V#^XO%T~zk3JW<(P?oyei-UfR{E3ix~U$VZj_2X5013-fTITL3D9n9z8uz`nG zuw=Hc6-=-YW``!-56EnyRDNA7nDtyVG~b@i`Z@hs)g_k&latHFB?auuuPAC5&2!94 z6QVUT3p82Q4?{k_vf6R z79{Io?tx5-Z^<72_4D&g{I0u#UsES^D-oEP##b~~o>B=29LYZ*R$)OGX8hMRo?UeR zUJrhc1S$_I)tBLY z-CO#Bog8pn#sLKGIX->O;vfx{knF1`2^gOCwVC&|)MrXxFtZ#wOg^nk7j99Ou)M<_ z1+}ev>3idZ>N6!N`7h3?2?Y7@>xRlU^a5yNfd4lB=`o>P3x6=4+1Nnqjll*1+^f+sl+;aN0*!HLUhd$0JJ<_yRMbb|&RB~XbJ4>Wdym(w| zTxko%Es1-#LFQemc*#g3wMrM zy4JBhDPsL#fzxIW+Lij}`vdoj<)6jo;T5J9Y`(ZWDdyHfg*;s0mz?tC)dZ<0!fg9x zobuvoLAq@VJ+uhe7c3vUT~yePm^aG$79+s^biXeqcZ>8JDl9`jTQ5z>D07=}Nf&GM z{&4_xRhltuiSTD%oBi!*z7yZXtv9mBUy##6wW{xvfylWPvC9<%SNJl9O!cWCz#FXE zytA4$UA1m?{&&=BY5;(W4!_5fJ4mT%7>T{g;~@9^xK@YJYS}eXRU}Dcd|EP7Yt_zl zRpm3?ypSF^(1#@#GN{$>)|9QtWfi2B$c`L+msR$GwuVK3sF&07PTj~>2^x5#+c6T^0pPA zhJob8mUfp>01Qm4^!w?7LO%JKc8v%(4^{FjlS{7;FU3d2|LtxDUgq(ujDw@!)8_o_ z(!83_$#QDDj27U9-=J#ygb!0$G8Do~(0vQ~ZARZT?kd%OlZ^rhuaHx;ZjibVe? z3gCn?NqBSu8D*7cO!TFmIJvKcG4FSAlKDb1k{nM8xRij;rwBke~s4YjFB!aMG$feFN?U&j=jOOb02(9X ze`SXj0@^@9L{ure&DBg%F9#_t8Z*7iCKu~K4rEYg0yQ7clRHi-!+bF%Qb&~DPx)&$ zx>GMp>KrA7poLT7+6+V7H>DN;x-yogpq$kn3@|W+#mh|IuZV_;p+CG5lFm#ZM1T;& zss)R}lf_8+ZRM@XeaGQY2X|l96iI=$u?!-Gt`Nrf(g;syRC4ozRV8-j48u3}0RR+x zqU(M$oS*Li_gv@Wl0!p?r2k+Al{m@l;aZ2B?UODS2mlQZ!~EN@wKal}WEuv6 zw<^rQWYl*nV5#y|xo78OuNC9w2`dzPyslUhS6luG+}dyqQ=y%d!d}P|`Tog}FV0f% zRF83|sxcJEOSluUA&T-Wh z1j*|Pj{|{Cv;kA!AtCro#rkUVXzUS~``R9_V-|eZVY6#^e zg(K_~1G8H=wj`?%yHAPc{S&e@dq5P&+O5-vkl5XUmX@4Y-3QG^GMWVJ0`lf!fGH|m zPRa03Al#;?$Z2U|CU#B?Tk1?4pGbU>Pdwj3jdl8|%~}FkMjB1NyI&++4Yc_(DbK^h zhtZuX=o1u}D5Y$iHOTK&ZPhKF0m|N~!!(=m76qcWdY>KaH4b3I0X&>plo5uc-Qn4u zVJAA68I=vEpI84z?g;|q^BWYb! zF_b!-rR*1PZSdp;=TuHLH1<83uPp^&*nzxFrN5{Zs|5xp_LH`2$o8xGvOjxIvh!Eg zzp^ZMU0@LCTbs&HxDa#kEt4KmaBs?Dok6No&`SIQfSRFc?i*K5gR0;C*p;lX4E7%k z(wAfBzkiIo)5o7#czJxJ{_WrT`41NzfnKqp4qk(25o%QKhSj(1uZd3JZezNQojHdo+D?a`d4OzK<+6I8e(#bILBuAkQ#snBuDUH8P z*hXi~-}E_%&jFlL6itj&IOl$1`qS_(sFW=R}s9L;C#YYclG$UP9<#(>|ziA&)T#l}U418}%feR72OtcE$D z^xE&5KxUfd`k@Z3w%NoOR)t z@quXn7(r44+Jw@SDC@9l*t_%`<`7b7G!bM>OG$TTwsuzYQ|{tB$SFs@y8?r#&Wilg zH)@hK0I-2=J*t9wQLR`frWB4?@))mjt;?O0|=oS`%^1Da1poz#}5N6gX7R zw7Bss{j1Cu^4pr6;r><4#_)e1gBofSP z^7R+bSP%f$U0m77k||`rR+6<@pwZs7|JAh&h(IxpvXm1=GhGUYwKU0L#AcYcjc=LXb>C@ za4=w;v=lv^VZZ|WD_2A!EjPs^dUbOPNRAvoo1#exmarE{O2me&ou6)u@b02!Vm|q> zG=}vH9Vbk?s~ILyFaU&{sd_y9p%GJ->#7N`#URHc%R+uvx_=(>CCot^9jPRcblJN7 zy5XNQ9Z#isDk;OAHyeU@N8qD^er=zv+sxVDAzg+_))w38oKt&#>1F;B$cm)D?T^2c zXxga>;%M;@G+_m_#!Zi?`6x+Xwp|n_!fy%!z;Mw+e|1|@|09I*zfvVaEy$sO;<1z) zZt-ft9w0n|b_*R;Gh33+Q5)2Zf0#$Y6cd?>7Nm?qf*~W9ebZGTq6UyQiv5v#c`fX> z0g+tq+=%_?xVxl;l8>FKD91=z;)(5xj1%H2$2C>l)G!k6azAElnF=yP@FBdjq;vzv zFa4*T060~L(HB(((8R4q#f4YVg6#LJylaKS;TuEzNE2hrz003-usXx}n7)FhG*>vM#%oE*72lQuLfneQG$9Bn@qFhtMk5z)j zjXGxKD_+Dhkn=w&Ld*L+5{LTxKi76$regOOo_vWbH%0JUU*!@Qd44V>f9>keQ@eE7QFFdh@{t z3VQe#5=4=hCqO&#TO#)?kSJR)+S@Y4+q31M&n75~*d?uPnvG@jfwsIpFXB1cUj2M5 z5I%6p9D&BU#1u6(WdB~J!AHoVALA7Oz(fywfs*gr%J^AT_5|@hSSc48H;30`e*k@Z zZ{IlP6}xaoMD8a^Q^shP23h#9ejhXOa2vU#T(zL4zptsTrS& zsVA$8R5QMm%NtEWwmMzkw7X42eU!IXhkM5pejkf8L!*P4l#$zq z?3gMx$qiZW4bvV*4-vAd?@5=aPL`3<=&2t|KWA{j5W)=-W73pd`gwiJ<{eJ)+8>o& zQIY@ShnVW%!|faq9uWl%mXbjoc@3#esz!iaIYZ@9g#NFT*J#kz8fsqGKY|MkQsTC( zy)|3KF#y5e>ZAaGni6a_X-E$uIYc0&WtJR*xs3B*_=bqzNGgE}$P?*$y_vBZNzZs`Xi3}+-1PL{s@?xoK$HTkhTT3%Jc^zjp zqh4t9jaT7eeIf~VIKm1ZD8?%ekP6lFhhy>zq)`j;9_Dj3yQs&z-{~V;N6fyraIid4 zGCbimrH8L@_M}?MS1vNEAs<&gdJ-B_J$)F>j3<(a{wYPYGA1ticB;}~oW1*U!x9!H zMa!Ha1_1gB3Ra;U<>?~e*yeO2YJ7py;b?5f3sDyV3RkhFL{Sf=4)5_M-BE? z<1=sil=_4iZz>#3HjI`E#s~)ltm_lRdiYJRJv$=fZi`LxkQp-P(Y zvuDOZo0HLJ^`ruL4tF(IvvV6|VdAc(Wh&)?Mw-7gc!F4;k4;H*1e1CJG8;OV?)J`j zNR)mo4Mcf=hp2zdX=kr6u<5RxI;?lb`HZZ!&!y7MBinucCh1U3f83bw-7{HB6y~2i z@$=JT=*Nt}rdX2hM2u4s+xE&q^rgyedES!gS;h&qkB|3{Yj536Rz;$5xUQ1EG2bVPX*7$^{59 zb+IQ0clg=>Lvw$5BF8^4M>=VL%?dYkrBGmxJ225_>`ky0Cip(fq(owE^s9+i6~Gg;KJ9*$fQ++A`SgDZ#0Q<_*Upq@CUK^-PK<6^ zVkp^|$z$KX#-%R>|L)roErXgIcI>{e*jZgU6lUaUj}vmJY59?x= z@y}T%zm+?BZ$Pn2l9`vM<-$EhY>-?%wX z-y|OwmF(KwDB`rEWG$zQ8o86Sk__fyss#BS)xvJf5-}v-snd30GEv{y?SgU>(td{G711}eN zPD$l{-c6KX-rO3I>xkVl)FSeg34NS(;WYpv2!Q-ob|3(VRekx9h2+Gv=4{uF!5Kfh zPvfF@yr#UMCa4}Hw%Lb>&sDN1!z93{x}Ts}uGReU)Q|_?1&w z9WtV~BTsTca?9;uG=)oeq5RL=zuPp}c_N7KYc*w=ujXl1erF(c?UtwvAceb2D`6yD z7DO{|{S7l13}hZI&{VX155zTI%GP3%azVE>fX!8v`SGl6W}*CoLy4rC(H4F!2BMxE4`~elgeiHp`@EX~fcH z{E(Zv7&TI@ItSUSh9Cj5@aay&*Kcn0CwU7ci4QJJS4DyMzn3qTS0lNfcr|;v>yulU zNe|pct$6h-fPU63X!Q9dLQVF!#Oe)T7OOJs)iplvdL+k}26G_?!^Q>XcZC&KTnPg4 z>-WLWi6Q6|OViF#Y7EpgqsA_a6rSs9#J_t4iPpfEoRETg2VJ(%iS} zipGkoMv^-X1hY{>5%$lNtktUSwsL!L6^C@?^{D>%WCS?)+2;i{7K_vmd}Yo1#RwQE zWZ`bMK}d5beV6qZLFyi53!XBx2ftlsOkfOpVXq0dYP=`mdrCUXfErSKsvR|5jMWPLeiX2_l8_#iCx&|ch=XwX3tfLMT_ zFqeo z91OpjxZ~r|2)O*%Va8TMgX<`<<@k4Ba@6~Yvpj}H9{^+fu;7Pd14#ZuuJ)Q@AkC=w zHRtRhE`1VS5fk&?lS{|)=2j_RX$w8xHVzj0HBJ?1RbTGeJO@;cixAW4%dnje*7y{g zJPC@ZJ6%xuu7!=gWUPKEPTtsq7`4yU5{Yp`pAW$s1es-23(2XwJzNmNRS+`i7~Id$ zJ(obz?06~{EwV?^vO<)K2%dp@PB_zm;;|;Cft3RyN=0XFOEEB?lU#NX1V0l6h!Ce* zmkL!JCQ`@bBkO7V%fiyp*N~z4sA!5%t zRu%$~B+ev6AE1d|Nt|%{Jt95r)P8(iLhE)(*5|aKLcNyLY0~&(uZc;efCopZ!<26@ zWNVx217@-PNx5zACfZyVEF3qqBL=?oS>~u~?E8PE$_D^780w-JV8+h1lRVapMg%vG z>K9C|`-Pk*LQ!C+s&2mmSzlkwFa{zxKv9RcTJeIfzFa(%HCb1M73XZ3R^Et06tF}9 z^`Mf#@TX=65 z260?IMkE;WqfcUexFLvhBp5nT^ojM=S1CWbFhG>9@(oh%g0(UdzW4nYObj1`cCT@! z<7iCqSzke>l+U<#|7}Eba};j zUDW5L%85^z;E4)zX<|DHXOyKc3NvS{rMBi}1nf%|?u|1XvFgHd?P-wTa~r;oY9fnx98_Si>H-%9$Q61Fg;eS+>*8COH5VPN;?L6F|Hose1d(n zoq;y|XDo6j2FlJ~{m&)~0{S=+&VUisEr`8lPg_8Qw-Ce6;ZpBMB!P8|2^(_fg&hfM z{q&)1-ZT-H_&IRgU7RXA%PEgO>rY3!Jb0PZScl+`cnlREKk%jB7g}`-U*TGNiuB`u zqMz>P6GF(Ise5IG*A9md#aei$^;t~|;M=LeD0F;I(x=~&x>)JcEBi<<{?di6ZmDB@ z^NwTo7V!=lA1}K-XLm(-R`@uI6rJ^(2_-F^bF;EW>r$L_g^|r2r|?UxY@cRH`%y(nFgE7DMO}gKD4bJujey0p(-z>@4e@$tcCA29$0vo9o zdczjsjca#JxyJz0K{tlUTqM`Oyu`>IG0gd|R9Qkm3&!)W8HEB+z8^X#RJvX$%5j5= zdQ3s3<3uNzDdCZ^QPmh%?`x~_{qtMmDfrKP z#cizmh8&~UFtC94AKuLwqHE02K~?6-9kskyY=;2=1?#N5{?9{+E8cI+OcDP$O$UyRuKC6xZtlha_yYt z^h6HcL{Kk^@e$PSx^-+(TPY{IW%5nh9k^rUH!;og4{U3yyRjLowRB+^3Bb)U57(Gy zaMdEwvaI)%u2~>qrF?WYw7J!WoU&Mo0;Ek_BPW%NON){=q%ZXY7Fq0A_vM=^Z_s_o zw3rF-F^^TWZ?F}Z=p{OK!;PUOq$@EMaZ5m$HuHpL2sudY3vGO{vp+B{(M}N_4x@$BMy~&+piRt;u{8yX_odGM?Mrob1X^+yvFFNi6?%OkW z&R3CHvlz|yTf`!7Y{c`fwjE}6IsTP8#~51k!`I!`1+;@=;{m!ZwD{adPMbfI<{Kq-okAU6QFv+x%VShyAQLNv9t;;$-c!biaI+(;OZgd zbmQGO7YZ!Qg-jwOPUCvY@$U2=@Z{?#Ppx$28SC9~ zEvdW0r6YM8M^yw3@5(EFoo#R0!ArcJ%XNYvFY0SzV@44SyD~9@qXk$BwR<<{2vFpc|G#Jw6*}N%z4}y1#=|i`m-3FEN#U;_6HlOn4__k!vH-fq*#Lf)q zlS`S*TLm~2b8pBj7_e--a_uq_ky5@g)JSN`ID+h78~@TIiS{re!kS)rAQGLHCz@Vc z({7pmyQfu+i>kV3jUh7P>v$f{ctQ*mCrADV#fw~KQwN-C$mbY2(j%+PGQp_cskr{3 zP4}LY;37Ofq#q5AAxsX}ib>i9jbUEpsY0f@!`IgI=&VVZdn~M>#F)Ns1yVzZ>G>J|G>jTZJNnNA$IZ>Vl!VuDF;s= zO%6BL$Z~(e9(qbP;L3nrJ6 zOipLi?tQ%t7AaR)L^2EHYR=y5xGP=Zh5OT6l4VTzZJD!`mDRTk-i1w0IxSqbY4ML0 zo3?1UV%NPfNd<}DsoF<1{}5;X-ZegdpZitMR%__?!}gl@Zp(K|BCcF}vECcLL}%L_ z%N{{V4embiFWPFJW7LK?5d+wET)abaXsRq5%AhYOg??<1$%(#N(uO#?62s6O9Zmt^ zC6Sv%#zZ)6DhsWSA4!3b!e@Gy3C!70w0k0XV958WMD(b}*|Uf$i@HJ`51<$xC2 z=+)Ox0yw(Zr@954vlL(iV|<~(CeH#r9pwU(0d_@4l7}jp35lp0cIY@?e{6FFv9tTS zB~d*CpR1!3y}g9H<2ms?n<0I0vnK^CV3=JfLyr#t(?e9C)nLWoYxh?M416X1?H%v* zo(hF3{23{7!(qgrLBm5|=9_YpEV62of3(2 zOs&M&Z}HpEib!afV`<^BF&9sR2}zB)eqNtHnDKp>^s89Y;~g~SipRp8i5p9COu$iu zXN5<>vFojCgh$57b825L{r-LZ_eti7)3l3-3>tRUq^i<=ex`LEFDIIu4B#qHt>noa zSB^B`$H&S$x|Dk-mp}?ph#TFkSZK-Ir9WH{6nUpx0w)in$rU%%yr^EMeYrBw8lHDI zH`E)DQDEG`Bl;Mw*yUu`VDOMw_s9XIQ(v(Ku~?U(Jp}ODhWt7(aEx}~hY{OokZBn~ zCRA{LMwFaUPRTjsm#Iz)zI1psSNtA(*pJ1QmqgzbPYwfY1Ji-lp<%;F5B)&|<@_^M z5-P6^V#-zV*Tj}WX=^?@w6Z6+3S`#)PqhdE@fzyB4uAx>O(Laz81c3EEadA^46vHo z>fb3jLB78OgZ=IvUbQ>)SXlH$8^=Wp31QYXh^oTt{rxNl!lQXd0obL^KM%10#Y^q? zEd5`Uu+h=5tJ zgxE&}QQKq#+6k21_kKaf68tgHnpAbzBApQ%@MItxY00IkETa$+DLrpJhOi4#aM09T z2sf0@$s*3%CWc65$lC7QZPu=lWNK9@c97EMBEiR+U%T@iX5FcjPPHq-;h;s)Q<1Uc z*PJNl8QKRs&godRt~6F*Z&UYLdy*t&_U35FL7w{*0V6N`2RNGu=(0Ca3K;=^ZhRl{ zsE{0%!TM`>F3ZjtPGe&~(Vk zdAQS)x!;|2@@Mp`ovtr2=rMkHm`zyE*=+kazT^B((Tz+bsF6d6r}Llp)R%d5Z+`xOm?o96oY~_CcFUkL@Ly!FT@~+nkTmt+Fx6j4n0K@&q+Z`!lo?&r&1g zu#KK7SkhRg&8Ld^hBkniiJ^kYlLXX1n<$fwHV%XN-GYUW2`+_KF9*2L`K93PrEvgp z=4Y;#>DY$1D8|B)CO6HExX@Eq<4Ejog^$G8{Q06x=WJmPHUG}!l)c$Rs?Z-mrx?pJjH5Akze)7omf9K|Z>7WiG}}_aq)-*8TuOwR{-sW13*3%^ zBrz>+I+ECAg5`_NTiTnPWt+OxKd~LeT!Dy>~3jgMGO5+*|z#=m+=~- zHDQTqos-r*6!sU6A1~lzyy3A;g=XZ`&%7#Zo*ZRNtXMuz7kQe=Nx6-Y&_SDi#p{_% ze$6Jt*qFlJp*w7Ee%|wB08JreU3&`q<|kpE0wc|36ZdP%UrvxL3o}HISI*dp$W2-Z zX1smW126z}=E`xqSM0QqB9@g_XM}1mPp&hSaJRqmS3aYGDi*!)5cquV_|z)$c>e{Q zZWEWg?Kt)oA(BQuT%w4ss&2FBIIXv#lz)s1mWRp;GqOYH|G*& zLbhx~9P$Twjd_QB54Tw=JPlx5DYA?>4>!Q(j9YiBda z?INF2xA}5aAkgy73~iX`HfSSk+ssJoi|!LP(Q#B24UG1@F28Yk9%1;`SniBc4{}@=3TJKxG z`}6%Ben`Y!-OQ3=^RGfBMfId#Bd*1V_wqSD=h*|T?CmQ1m`W@bh?<M;wl?IeaG zn~cY)e%;w0hm&uy@hHgo6%@K_N_zZOJ=L{UqFI}U0I(1h1dT+FOE^*BAu-&a@1_DwNo3&<1{LL+Z@1_i=tehR;8s1us=j%BiQxSmZ{4^MX#u54$mkLut?mCT zwbIg&{Qav$+b8OvOYR0gw5W#yC;oI@nEX|B=oefsOM|Tq_0FEy`wR01B0)HaJ~o#W zBiVHx@(UZEglm2?=IBJXXjZu3dyqCQkHx$CCZ4JP{7jEM_VQ&gy&R&-wFjfSqzC;#3 zmHX^tC%`h4ZGjjA&*gW{?H7V=wsJ*NE>ZNH^6`=C#h8P?qHSF5VUmK8v?;{B=cEM@ zw?je|z6>laUCX{kd!;c3F#nhft-Ak4a41GamG%M(jJ@HAH1!YwJ<|x{LeWaxYLINz zHz~30Kn%E=wJs4wh!B{ciP-P;EruQ>&Z(k{DO0-G?2~L(!I;I=az*19r$U8%vaDuf zXXyIHUwVSk(ao%)%H=vgRa)w>t<+rk_tgR@#vm`Qt-j5*Vx|MAP;@f>sOB z?XmcN-nbcH{E{SBHVfRyIMaUrA?A{`x#<_~Y4UT=InjB&hC*7F0`**~WnXp8NL3+UgV-D`ZMG%aL+XiTbCsGIC;nn^KsdX>yn3T@+*S4qL$S6sUMq{x9AxbA+ba4P&8l%tKwg}$-|85Esh z@mn7-A7^Nzty%_vl^S;bh=&M^NwM;7JWuCPS5kVJ$fTjru)%)yYRZ}T&0o82HrZ$0 z4d*`TCa&u=LiWzDvd&YsV(UlfdiC|5(p0E)%AZw=yge*A)?Q4IXueIbYp|D3OHBOb zxH5~nHvO6bu`#t3`dNQ}?&-IL!VAOsUq{|zI;-Nz5!RzdlA~j)PJHf) zw2R4M%m6a7jP1nA>onTjv-4b(3YdThr@`@t{2~TL*O_Nb79R~RPE6yrw?!fnnu*XT zqWCGem`ai}HWDNKKO-yT1b><}?8ys0KxBUt{rucdvUk81{>Llj=$}HA#x@q|l#`Cq zdJq|jh~Agg=Ztht*zB2s;KRLG)&Sn7Fk=R3S{h8sCaHn_PyyNLrIOS%^I93md<}c$ z4?>;kH5k;q!Sm-+?*B1|1-Vd}h$ODIhf2y{6d(nCYn%1i`=E}tulE;n^2 zzqg;YT-4Bm`eoK9ToliV!MOhW6Q}{8VHDX7LAmCrF79C;&PFI}fr0>dS&!6IqtGy4 zx8~6jX6k=Oq8r;NNWuI(&7z6v*Y?9rA;boI&47hYRMeRqCiihUi;|q$xSE%MhJnja zeX1lfnCoYz-00IcS>DQ-k)zGj8iCudMPZ4iVGWaNJlfA8Sk~WOpe>t_W1|yHdL^_} zI%zJ=pGc8rEG-jJSRL`5sYF%_>-X@6YNBa>F)FbM?*F>W{cOXlp+zc1V^*TKR;R}G zC!?aWWBxlRFpeLx@jeS2 z57&Sx+#SB3uF=C}P+F_BMWn6dw7uareu7rVl0MKZCW-6oiF5^n3Ru&LWrg@zLg_wXbqSp1t(cSv6$$n;66Xq zMjaKA3dmN}^}#WLnm<3nL|4={l9VeEI8THT50S;-W1VCgEY=M>JA0|g-I_#TlM++u zBR)CPE5fHkHn){OYk=-Xmf$Lu5tY#PLN<4U`NW-F789FtzViw%&p}a9m8-)7?XB^i zg<=PUlZ;g9m*^d4GFQB{oEJTxRe&4o#-yy^Lgz1s2qt?&s}izszEX+q*ny3Fe;YMX z@6;8>n@yHb4nf8k9YVgKKa>CUN^GdeLhYs3Y?PC>Y7g(MhE>7KJVuvI&dyOV{(9uj_MO$MHUTL)dxrUoz|Y${Ow(ri<3 zn-)J1Y0^o;;D6|xCX*K7?P*~tsOKo16HQ=M5E)xZBUB7yzZ;+VPq3=o)><$9ym^~| zngK!}gF&8&T{XYYyxw1hX@O0~f!)uWm!U>*^1K^DsAkzokY1ni#nj|^PwaE|`4>lu z_i40+?EBTm@3?75-_?DT_wV*jhv!IEQG)Anf#VSY*u-%4X#HuB<$~lIM3`_l?39w(0weO7a5-5hs$GEsDYa5 z<=DMqocBn!=tif*v^^OpVkWX=dO5=}Y^YzqRSEJDdcn*G2LVvEm}*%=+*#=Yd_X`6 zY~5r$1b1i$*Ksf5)G;ImR}ixE$xtVTs~{+bt+|Bg9~y=NfL5Y0du|QDHOJ`SuAxIQ zmQWM9>k33;;3$735C~Ax5-gXmgl#YzQlPP^abd;insCTWUeLi1oD8hWnRb1Q=r9j zJ#^iizI@)IoCH%g4$Q~44}$^EK|$Ekfe?B?BnaKbV9!N_h(RmgqDY*R2gF;&2oUG{ zDa-JJ7tr4eEyAT?lrgTkLWaWF^bIqCSj}eRhSbz=Rdtrw$3TGh%NLoq2V@;O@*h6+Z~4b|7xWJ;>z;m-s7>5e0x;6v=*F^ao{oo~LL&zokqim6Sn zw8k#M&uU$=o6e)eZy0Vqi_!GN*>|om>+oN7dWdEeug%Q8E?7c$F%@yS5ZMXi7U^;i zKV7eINj@v(?0}PTa%2~$4WCR}u0L;eT%Z^~bb^b*iUS2~1O%knYwp*5TH1z}(;_7F zaqDB>BLJ8nJ8L&E{m$XG`8Qt9#DSYA)}@HgK)(~MjPqj2nk@4QQ&I~`hDvVx05KPv z?9CrUUzUPisH9$Hvx-9ea*Zy$KJkKT=*Mlo(s+Ln^EDZDNxR8w#2P+4e^VUuh#_9& z?YZ*3N<+!9X=C{J)a>d+BeDB8yYGkBtuDzz+Sm-HY@NAPCp&TX4XUk`#;LiR0c_t| z|2##1x}2!^TWcBr;{Fnei&-0JhzQWa`9lr{I}N%9e~AXHh19<0QcdE;!l|Y z@TW|*y88p^EvqVS8|cta?k{}xiW_+-vGN14c(6%iQD)!pkV*}p<-1Zsi1Ex8d3Z4w z40!8!w}=k(c-R4&+Xz||l>Ad^rlwE_vk5%-ZaB&!tno3&VC&+Bj>W%jrpx)ZxW;c{TIM`VCZ%tcHT%_%Puo@d4h7!`{X0WUL5=F;;0Cm{cv%r-POFQx}MUSm++BYsts|W?JGQd z+qu-kvFrWU&z3FoVerY*i@E~Q&GBHdNzSQIaf0wjesIw_)eYt=%`YqNG0is*`}*&@ zKR-dtKlY%bFmNLg26SXCO5gfTP-Ma>a5#tp6>Xsah$Y*KQ3t^2dUU%?aAK%b~^xCp4PvlP(Q~?$}i?2Lh4Jh z|AHuw4a*%Q>f*ekbPlHwCQFN%#1Lyd=;OBc2QeYtmUi znD^!dEMpIFk~A6G*y5t*Ec(!rn37(p5>fS})>5RMgb}O9@|3ytZZVLD73T&3Y>-OX z(I}edzabmAJUkwC+FS-*`B>vTMBcFlI zLS=b=3VlFPPUvXiRkfg1!@CcLcb=aE7A8E8UOm|!6@z7$Soljc1yNW{*c?odSRxn+ z@-h^#CPSEAU-~7*9aJlRDRYIQwCntu3YXu zhgx7cc?^s@$md}utvi1`%;XB=B_#~)32;J*cWd7oS^?3gk72G!;cqhesu5DLc202XGIKq#FZp4H>YL=nUO`;y+RaLoSG!;xKv)%Jlc~!<*NQBEP-+>} zr#@2x8kh@twbU%Lu2=7K-3%^W2%iJnLR5oL(J_MyKfQ~}afX|;{KxqvQRYp-^gBbP zvWN-O6Z^yEnd5Y1`#7csb#~%89x|gFS6p34)>^+B(8dfWgpTRjQw6g}EIfG60$y4$ zD!A6%I62wTv8d@L2>%*pca)g$Ui*f(T~mS?Z0At(BMNFieB-}S^cR?Kh{kyf8fGbL z^-P_b&f&X!5!axY!Fp9XG}hXY^!=U8M)-6{Q|M@hkNb0_9{>OY1FTv$$7+E8o%P4W zLOJar;H-N-<{fIyNDNC}MKZ-3NjbxB1O@vQIfB7X#dAzXcHeNA{LS^>ch`N?XYS!M z`7zCEAu~F*ZO|TX7KSga;^i890FvS8U;J09NF?4dGAPN0>a8Q5Xo9OO7NNbZR!ye! zP5{S=Kw#WMRz>$7+eUw%J)MU@^srj311YK;YpIJD6$U=xb6;Q&5=wz&I2&7U*YOfT zOK|N29MI%SY;0PwdIP=B?ymPA;)O10`Tg8;I&pscTv#h-vzF*wE0So9i2p|r&#Ff8 zW>AdOAOR(`28A-YKUe1)rDHH({5X#;iY8Aq1wp<;v5SQwf{p6A4@Dh7001Ctxd~`^ zdCUS+KxDd}s78`DNhqV7nPwR5RL5j)Z-La^-jJUSmIN6b3gVVz@nm&+a95YV&k`8q zSkU?jcKj9Xw8+d`*)K+D{RP-qG>JqBSp>sYOWoLUd6i%bk9!~zJ61+Am0o`d4J(^f zY*>LkxXGH4ZKIU~sC`sMx-P4Zf^_k45JOf$e;v;(Cpk?7)=X8qO7AF?9ShnPvo368 zv<2s*f$nylzCep}y?`!}GX+vM|D>!+y}0^$5jZsI>|DiD(Rc=rpf@T;!oy1wHb57l7*~b*zf6n|@sz3nRwZ6_@F$(x=+>_5%jIQ`&NcA+twII0i zRH6u<#2;D~DqPWj-MgkP3%0Xjk7mNdh$12S8ja2zYn_!2#-n-hvm67|pBs{S!D?=& ze9`aelGvEKYWdZQjEGAhwD=M|-mW7A*9VJ29y15hWiK!5jtP<=Wz^zKMEuUA8y4;w zh48r20kGv5(9#Xtcckck4$9F5xEmNTG_SO79kiMxRA7mS3vp7&&_)P(N?u?qhwA9J z`RvFvEePW=2rCr*Ng)>Q%#l1)m_02p#h08=tDqQc-d7!c{2?9;Fo^}xsUhFp#wzyK zwKos2--`hV_?izvddY2HqWw^J0k-tm&k}cpjtk`b`F_r{$lAgq^v%Th(~bC zbt-WK17gOY@RpAd1x>wNvo~%apQ=$RGj_IOv=nep4Dt-3@cqm6^~GmKK3<6is_20K z1Szxr9;Mgl+^HVZp5Vk%(;=X8g>?UaSIyD<=cw?rh!J%~>gWm1?KS>2?PRtp2_zb& zh^bN4Ga0s^lWmou*a1E>5{J>QT8Q29^%wX$RptJCi}V%YGN7U+wTeu_X8Qnh2=mT@ zhQACn(4}mZuM9HPb>xCzYz#|hdTG=DehsOAnkb%W8Yg1ssu?(e4yN}Dk^5Y?FwCC1 zns-mtMy|Khgg>NyalasG(1|$QC451Jw98`s1h7bnMBURIJ)4CxQSz90z~I&LH& zebI~;2qoF|I66~oWrQ>7(Mf&sQt>61n{Ps+=ZtmAjCkm>6)xEcW4`B$du?6a{QyB~ zp@G8tFHhOqUBMztt|w&qmv|G`$F$GW10EK|wNDOq`#n2Zj%Gn|A;0zj0Q}jw&erwo zKdKy3CrRt?mjnOn+Jeae&@u0lCLXvqP8`~ODnH^XPR{g@lymq;iFQ--6iiIK!uue` znC=D-#&Zbi8^RTjK=sR7i3hLg8Xyg~{pH)&mVWAZy?LR=i+}Cgcu;(_8C;)V6B@C? z%HO`l7LIbrZ0p7E;-X_t{#U95foL^4%lD zY*W$0kNZtm_hS5bq+{->mKz!5FAq~*U(3J{^(m&ijGyuS2dW@L^q zrRM&_Ay*nW0>%i@BQ5mxKR)v(ZWVbwfYV}{IlO2qKxYPpF<{aloL*kM`w-q2!>$Cc z_+~n4!&mE&0ozUUQ@#>?15YPkVu>xgUW&5x+A1wwc<`?S7?{hjm^yDfnBfcWypTn6 zFVzr?AA;Etz4Y;~^T`_Nwa;j623MCdG>CbY3XIsuj8^FPi)-JBxSBn3iCGhVJHX*F@w0_TD@%+S37Sc%?3$Kj72x^hw3) zsPYgM#DEA}GErgN1CI9tKBZ-W+yT&vpH9Yn^DbQ1i&^Q`< ztrty8;1ydS9TcZ_m@J5*lQ2paF|L|C@>Y2;dnF!L5`%0Zvck8|PlU`0G2lR$gg{{l zV}WCB&AVCMp{MkIk8u3^=J6RWL`PNA9T#tcap3Z|ELZmSwCbD=S4|37b?Or~4viIy z)J^=L_RG=DcAd;{dLB!AbzJ5$>FSd>(02gg_QL1KHV8|2*dqG)zbz#FH9Q?p6rY@vrC(~m zKF;H?7_G<3C-fSRPqBUj4Su`3J2O`pE z^H2mn;<3qAxu->$#G(9>YWnK=@LiI5=Kz;MG3#p0BvJBG^7JoUWWo0f16)purt}1q1Y;3iNj#149 z4~T~%DJYCsqk~iJ@n5MT-D#TgdVlRHD=nidzu3vXCy3J3KNq*#$w3-P!+{$Z-~y?`UUwfwc$F-(Nw zHq!B8XDGUP;d8{}gh`^0@kb=&#SARju|cjC!f$Uyxnnp+D_B0{F;nl*!;(CdoTztC$(6r7@($3$sW+nuqE1Uf5^!df} z`5-z?%b%dqQJnH%^pUK9&%-!UL5Y%hvp(#Y;fIC0;z9b_@f9w*B?VR~S*8{}q1r_i zYUC~QY&d2=hADe3e)K5~u%{0y%TOpXLCXOs`BUh=_O2+>pur7%>y4x+g;?ho;HhRi zxwZNIG2;ny&uL&T8kZ2+mWtqYI4X-6ph%kdvNQ25s!da7xhT|vPLKsPI$j#Gn zdYbWO4m6;DQ;z(8HV)m6oy_H442IU_X872HuV*7DfSSzik;S;A-3`tHOwL6nYOHk& z44!lcCTf!d7Qf4dPPG9em~{Yt3RHco+|gd6A|)Q)FkK!Y*|i9sEKhn+Xaa66Pdud& znoYj_Zkwv1$&$r`$xU7TP#o_mqvBj|_fl&3)oOZgv@c$ChewFUnqB++7o4l#1#z?< z%QoS!%MfDA>q{B(oL3ea(aPXz%rHXO&>bcty6i>(Xdxber-eg{1RTflzr?2JJVvL) zgrdD~D$2?1wULlnnBp`!u>9*ud3bPW<=#}2>k6IhShZ{XFsRe$Q+>GZFLT^$5fOT2 z8#|1XlfH>NsUWLHo`;6*_FS zvwX}?|6QJO)7Ov{|4K`v{q?(tI#i(8<;GN8!$OviuNgnjL0w@N^wvSuoU!NbeNWHx z;3JD6i*g%nH;w@t2LNSiOl$r8E&B;86d-T@LHu&v#Q2D{)^mfvNy_VIctrLOD5WmijTv#+Or_uLMaAXi7DN72wv*dJ=^%Qc1 z+@N#qo32WYbuhEIz3(XWFrTz<{b!1tY5OKTx>!41=IGnJ=r^VM^IJ zKCn_5fTr`Wm!bmDW^|YRMUhr)WziWsW(~D45ySIX?c?Ywc8TKHeMX+v=MMxsG-f+f zM|pMDig%g9fQ@BcIz3h{;Cn0T9t^s?F&QBj?X~a-!*V_9GsFc0mwOyYIsW^FdBeV) zxY};Od3N}gvVR4e%^K1^RqC5p827Hp@A6bhD>v8PFP%PpOOj^xU_G@k#-;Nl(`JhM zV1N!eU$L{XcYW%j1{^#16hcbxQ=5hdxPI%nKtVj7|KW$RPVXx+RpcIuP)4$N6og?x z!ab%B3G!YmlayqJ$+Z)3=*l6bOkjoth*4{q)$^14YY9x{7Z7a*oLAC|HGzN>b|U@D zvSJNR(7>1+Jbsk_?ctalvvg_=G^6)2c&8!rj?PMXtnW+2nToIwUqgsZLFu{!-x zoKAQYd%yvz_l2?LtkU$$qA69waQn@c)TX|`o$~U%^HUxooEO^%y)PS;#7*;cQ6PYr zdaUZt`nKN!ozz?j8tS>i(hgWha&fj$BTweZ`ub$(0HJZUApWeWyK2G7_|+_ew6@wS zV_ZSD^jEg{OlfDtj;s?k^(W;yeDC)K6KWo*@YsxNq0QLg{<9h5dk~7P*4Zx%`c#e+iCmx#-0Vm{6v&4iihW=^ z?pxBp7)2eLj%f?wIc-SpV$a|(vZ@BQj8D^w(Xs9Nd1))x_MVup|N52>z`>To;mmN8 zR~~l0vrB_oD69Qa!Ck$*yV=_J?G{aXpL7mAqt@}P5Ra9$Amne7TxQ65{j*uuIJ3}X zbM|I)Q&{ay?bWisT4nCNykV1_Zh5Po?uT~b56DD#l>9)tnp$;kzB>P({wzvX*R1Ty%*?SqL}zptUFgfT|Cs|;dEbZzlS(;0-o9K zb-mjS1QehG&KtfX_<$Jc9=Oku)Ae<%oQ|K)wzcr=w?}$|^8QZVTmNm!n1V7E_}{dw zJUA2W;-K)!TV^Na)E)1AlKQAFlH83n3hlsu&~utZPQSLJOeU~ky0LFPk)Lb8`tJ#b zoFd5~82&8~(j6Qiwd}7zEoxvx*<&TEriQ(4h@AF7715EfU23&brEm_MfkKvnB)1Ohk@K>r>yt47V6bqO56=TNU#B)L11F4 zu+l>9BJZ;JJj8&RqoY5$*al)wWV4s_){L=r=c5gW1SUnoGuq0WG2eiF&R*HJd z-|8m@&p#a;n@fBYalV&*jS(-6BNC%co#!f#UP=_LOS+-|<(bJ-E^F1Dy^$yuWj2M8 z7GFMh`p5~wuSF<{@%e{e2EW`(ei=J-kR$*gPDE*Z>5Jyf%lZXhUo%v*jyWSXefX7s z(c|TVt3o{{T&~0e9+a8!+tuYmXqC%n;;hgoHRoSS0O9HmCvy6hU%Vsrv%%7<3=EFH zITY?<|MgO2EOw02^1sQs$$0jbJ@UaJ3rS6=bF_CXKb1&}JOH5Z%fMQZc_59_r!4MC zV}~~c^*k5BN3tp+JbUXn4-+I5un7!O0kULgC2r&5>QQfQ%%USyIZP znCK+`-GCbhC$Dh3Id)E=b-8C>MTZBg`XhH?;a1jn(_TD{76-Y5$*8QRJB|4OaCGG6 zx@(_Sf%P;h^Gjordl9i;YZ8xzDoI_DYrW+NYGGg{~Avoef`pEGi^ z$?wk1o5rip{Wg6fbZccsp%hOtA_K)9&Uma$#|;!Egu&bpfEX0a{oI2YN)X}6C@hF7 z|G?oVxsCk_i&Kn!eonI%bKi@lQku6p$En%Hl}gLWB?66&;)Cpuj~iwT5*`eE{AdzK zQY>Yc z9vEQj!$Qy3is;s!oRE1t%0cCsN|M^1XJ5d^PV*h`fbO)WePxISKdhEo-Q69n&$8VA z_D#+TILBI8w$vUc+$m^duqaOz&r#*FX`?Kx#R-6q;k;m=3S8wC` zyTvdz;KL*p|~k&=D?d>|sp^2Ed9#^;R}}Ln=w$koB+OF|@hq)!eeK z*W)och|;zC2zs4gTK+)nSp9Cc`*R(Joq%k?nxho;DOB_z5A1NmJNqa_q4;QJ=Tcvnxl!~Gi6D=n|6nr$z4$@Bz*vqUlF!_F{nL$(v2S5He_nGi zL#sui>qF9nhyB5p96au$$R|_g$THQXcwT+1wlL)lzG5}fcqqMkXPWKo)7|sunY-ON zoy9r%OaoS*rDjD%K=U2Gv|MK>ZO3i$Y?z$^EU#ZBGnh>{7w_19_eN zcX!3QVXdr9jH%DOfiJX6-s({%3npl$?qWH%>8-%nTJ?BbSTkg}vMPvaPQda)GIC{* z|4cnhntkX3J!E$nM=_8fUwrsPjxaym6Zx)@Pssr~Aka&e{O z1#CRMzUi;?JepFn9{Q?oGN@JDU&!R&TAM~Q^YIIyF?=G41@C4u%2cq6W6~1w&OL~H z@;{`HVe%ynPS>qz#FB+P004=j(JB6JR163Mjz3ih{a=-f^W*%f%fcsJR{-LN+@qDP z>vXYa{3OM#D*9$FT3sk6zu@GTtgw|x4T9>*YgUDsR|4Xv=2Z};0-pmSi+z4?0{{Sd z!T!HKakHkKDJD>q%zXLuBa9(&?Py3vHlO)(Mf9FIRabP1^6W$^NW*|zBUw4mN z-#VK#sg}er!Tx#U&xQWulp`Bx62z{S&RR$c6Vf0{23i*(FAp}RID5822ECeC(vJvKyof?FaO$0 zub~~fkWe59%}G$oe*h#cTqgBj0UksF-thSal%MU->-ToUEzZ$;=U-iYH=iA+D{{mX z9%|Yoe@&-M>oL;tgz%zC-vGe=J`k3Ta(0MO z0-<4^Xm!k59%Q3kL6ob=?2d1Gu<=K$bEQ{}`IWw>e^7YpcZiT6CUz)kgzCw%6nUO(T~VG6axJJcOUIa<@mlHzuUCX;Ao>^V&a*b<68c<^j8)s*=)7y zWkpC0Hn?=L49|B(fS0=vHOdW%97RP<7HWI^VS#0D)wn0`L01%pKIMBl)a1qQ=0VM$ zs6O0^&aT8-Jr%f!|8PvCq&C0(g)usv!p$6y%()6$^O~i9wB`O#HODiXgfB?65QtAr ziOox=bF1kZYEfj?7lKCZmk%J324Ot=Y-7sl4Vzop2(z9eMPHyvO#@?^w{5e6T5S@m zJ;dI*L8lh>pPkV+<$oAMwz*KKT224D~+s54){6CY)9;uk=wdqU%k z>OtI0$YQ~VmggsbrDVBGmCa_&o1JQOt8#+Nmbe4c4EeU2!up9F;aQ6D5JLPRn&8_j z86p@LK=Vx7D*b&oGm^PU4}*R9_6=CBDa^_%H8@X(4z2@J^YGYM4WZ%V-`oEyRU!a7 zn&6VZ6HTRY6y=sZ;|l_M4Z{mM=i_{~Q;CgO4i`tZg%C`3$JqiE-P6~qo1zAu$-M=x zr+)QCwPzS_B}L@C)~xRA{^)#o7tv!X52TSW$$7{V7+{rd^G+~7A$mWlhpz%nrIWgS zA?VHQYdX?6m(w0o^K={T>)rG083;g!k1npC_vHh0F+FATzu<){gknzZKmS_bRIQM+ z)A4gp#ir~R4M$jge7wG%DjiA{Vnp1#!zsO0zgoY?2Ki^^KYx0Z;AD9fadVYxIqsOr zDE1wX6Sa2k?I^qyUbFSQ@j^J+q5zV=jn0R_L_tFdoHns89Wit+$HT-A(Y#X%IeCl6e%cZwwux1FTAV1s6c)=CWk7kd&+am+on1^--tQO*Vuu^5mSDDt z^<3Une~HIYVS*V4B!Cy$Gk=awI~^lGklKwfIBcLh5L`29&WoC_Bt2DUhjSmJp>LLp zj}jX{31KasdH8+K-oyU1pRLF}HjtrwAhnugxc-nLV3$TM43klC$}sqSYV=OEhT`kx z-~Sj5sn|eQmMq&NpID8z3075-^YIp|&V@h^Q1H1$&z;q-(5T~kyG$I(&tc1aBCv-yqALP& z1q)rKx6bkm>&N+8W}nHSMIVwd?3Lb7Ms|X85NBe$cyg!f=A_T=CZs4(?JG|OUUn2m zpf^f^`HNL?NZ?xOFS&7kNdv#r`as!2Lu1f{o#d&f^sA*a@?&^MQ=tD1&PJ#GLPpJ) zKk%fzc-oG|nG{~l=v}^kn0}u5Yn(>oFX5~Z^TvqtKhgYjkYQ3yJ(e>?mn>lj)f>HGethj~bJO3fG}RM_FY}#C zLU)7TL`9vdyG zu2=A+jSNNyI{P-Ke#TSRgNZfhM;;i}#@bZd-4wylVq3VB#>x(*hxg zbf!bGLzIExd<+zozGBL*u5LuxIGP_L**DLX{ZV4N^TVFU=i6rWrHSEysp{yCwe}Y_ z^?b>=ZUxk134fkG9%WIfeG>&+#W-YD={mUY9DO?T>l_Ih3`dv2k7pq2FNU+fX`^$5 zsa9K=sl*HM5U>u1$VE!dnr(+^249(Ws>VgYGH$VJdT?19`myJzbHB^;6DyI3^z^4D z!-~(z2kG4eja*>ar z)N694Brp!hOhT$8e9+V|OYP6bsY0!J8BlX$Tc(s4Z6IdR{mJNCuUni)VifGLPG430m>>C9sxJX( zY5E9%R%!?Laip4fz?%SSsd&y~x073T+?^lK%soz_G1d9JeIXX>k`2X{XHc|?m96$* z@pNBMb{7DTzzy~#M`any%Y(4TD;E(`mK@Qx9TTA%`yl!}kH)yreu!)!URh2H`YNwIv=Svpee zA{4dVxtUx(5mf!rUL^g3^V9F{)cRJVwIatEh^Ql-)Lms$`YbW>W6*%%vovg80~}-a zNvJ$S?CsRP(`s>pbrChT1XezLBDhhyMkSzd!Uy7sPB?Bb*;%}vS9g{o9ZD$(kKK%u z6tEb-a0~GLy}4M0_zoQDnC!*OYoo&F zXZ4QqcWPXw$18>C3uqLQnx~VeR1n1o$D|@`{euw}#)|?@OPZ3a)cpBBurU@AE3G&G z7YNI=ivFfyji!kIsAY)63RP58R!IcMc@gthF~wt%lC3zwF8PROgOUeGYoHT0annCR zaXG3YyLCsBJ*$t$b1iJ-(Hv>Nd$aHIfIW`u0hysJw4*E+=Mt9-Rhy!-k*?&4IVK|= z*&Ll8J7TnoId;DZV0X5`uX#_`zV0S(9-wQ91VM2*TQ;hby~xr;B6*`tJLY_&=Om6a zt7B`jlH@qzA9c3~dmAmeD|`Ul>lCIQRHc8dW|zJf=~Ava{u0QXE^Mpum0pyWQ(H=R z#-c7(T-p{Cv^Cq2i9i&3k%szci&N?8aj-WiJwN}{22BLxx?z(s+tGn>i~W>(J;vBM zl53$m!Yw!a=K&re!AUCql3Hva)9dYh$l#zjd?0Z$SEmzm`=x1Yqc2S%7Ivw0Gaj1i zmIc(DPx*I@KCz+>WHHiOb6Uh8v@3kjT5ClZ9MLt=3YTp}4zRF;W`mRuGLx4UtLZf+ z&MD38$Rd+8e;Kv<-hFoYOTzTgz`zq~(jLO2+FKFV`gKfxw>R0Td03xR06=4aN zsR2e=$OAD?&CWu?JR}ykv6bMtc67|>R(v_;p-rS>oe^QWDz7>&a3dm6*zgR@iY#Nv zl?wY!R%u0ddwxooSvG&k^l@N0UgiQ_M}3oym}xg)5uy0AN%YOiY1|E2s&X#^(L`03 zkWdt5hY8U)NPj)>m!dY(w0oSAZO$$GBJ^TMe29u zPwG{s!u;%Ex_f-Ff#CGx?IrBMDll}X{)GYn4mr<6a9tv@h-`|VTQ&rNu z5r7$mhNt1a+P_Lp*xMdKQA$s@)^+4=d5yVWSEiYwBZUs&fXkuILQNRyG-MrvEH{*raHCjy64erw^lI~iYveuliu*+ z;$Gri{J4iK@)ActoIeTcA)4;9t}ZB5yhvEVtuPo6Phe?opPYVQbr`NMsLx1Ngk(;2~%YG4R1j96?@Uy9C*RPXHz2+}k5zhMFm*D}%{ z(q^sPkzgr{gAZv}JL7RU{>TKC2*K9@c)>8Za#S>`qt_Vs%4q@2b6TPdcrvPUAU809>~R`EU!V^LVB>lExR?OOi`%B)Uo|u ztIw=ndC;~jq;O&3Rf4Cowabw~|A(0k)L=JRHvb|G zR+VJMbf`_YhxNfg3||2%p@pGBZ6p&~t!uAHFiPUTI7r$T&2tmdPMRRYSi-NGwDZtG zJQJ?s!5H#x=0o*|7^kobX3jr@L7T7X8w!*!YakpAXmMZZ);d;pNR)ZO*ia_tQZ4b`giO>2cY#BBK)CL zZ{4ccURW~<6QCGtAqijw73}u!6XRCPq+X&GVW9fc!iWp2D>h;Xl z={otEVX}UboL~}%mtu=+O5j`88WHbaKHje5anc1|60<*nMU1$tUc}jbJIcSJiMkmT z%~?W?t=`#~lwEyU?mB{}(BI;{t#r^ZU9=5Zdk#kDcCb^B0lZ{ti2($=Iht=Kvf*?W z=rCZQ1fpw&atb-t_B&DtaJ{5rT>dOxRNt{9JwzteYJ9l3N@`Xx1S1s|qg2l|c7?qJBG0>r-aJ5f>}^wJ`CS}~H`a}9 zQ8TuO<>3)s21?~~Tn#vleP-xb>`kSL(Q8rYr#j*Oi8t2v zln6fhq?UJbjMValhP41E0!4=h1NM!`OPkMy>F-`=%&$$SaF@$@*``uq=u4V-WZJ$% z0PrVTAAVKCiS$APq#DFj)AD zNxkZJO8LfWK!`BUToHKy?u)O6b{EGkR=L0C_Et~pv9QZh%BY@J4a}+JB-D?3i^71Brl%Kdo2s4)MW)$I$8OYWgjMZLF~z}{7fbzS%B0oC9&A4MKC5S`X2%Xadf+J-F~8orcdI)Z7;r5UKb`^ zU3c%GxWMgr24Er_UGSjOBVd4(l9!1$7#F>L7?liV+cCME5+zBZLBw=CW1oV0@1`*i zZ}d+pWdnG`o`J2}VKq%s&--v$+yYChr0=d{%diJ_bs>7c{gkWj!vAvZFsACe`{`P@ z>biL73r^@)%9T!uwA(+QX1L~B@~T$?^1h58&nRLbNe$CoXiXD|D7C@YZ~nwb zTeHA$!~IzLK8tp$e+Ciy-_K|y%{+h%ACe|Z!V55Vu85sovPTs0YoP=ZEsg8Tkt|w? z$Pa9z%or+En6L|r8ctDS%3E)oGHhZJlF z3mFni(7EvUJ^&f4Q)h$F+=uswFj@)dpo~Roas#YZ4%lCxS?R|**_zFQe5U`Bv3{q| zI&7cbA25V8!AuU=wF+E=!K^`p^k%V@0L3!FpDyQ)1-N#?8Wv7?xn91qh8O2j zprCoTEI?&jZa~1J>yG9$zxoCMxVA; zeUHsMl}ekLBDHv?#W86u{*>O=pPDsCX!qM_Db zVIDiXQ?@tuAz}mv`i97d82U8m$zy694N(5XF+Sz04_fjof=jo>C<#v*&}rz+$#G+p zO(aBftU%v5V`OBb`}m68lX3IhH+#yaW-794rEl_Y&bti!Z`NP4_|JcJ=zP+OKR+(x zqOCnD`|Rf#^`b%hc#L8fPtcJ0<6S_pL8u<4CH@F?z;y`6&v^$Avx2}?v6H@4(K#A- z!XrbM=oP{4P3nw?=b%j_>VJp|_@0kNIu;4{F4LM8J>X|qW&~5)DZ8cSjf=_Bxie4Q z@UhYkLxm$pBbU`)c&e#>J1+a&{&D-KqTtPJ`GoEBd7oA4;w+0)XfNubVMM!QGb@ZH z6kJe}@!PwV{JLHh#Or-UaInP!m_y4|=74z9WBVlGIp6?FnNio}`YVC-9rEZ-Wvpm9 zXe@YmsjS2JhH}5LPWXmkyey107K;&0=Vqd+2<(T9jQ_#{1tp2slA< z`Z3X-^u^V&4mNs0QvN4|8c$r1rUbL`)T`vfH@+ThS8@n%%6S39GT{UC34*WEhI_G2 zgHUfle(kbqj=idyi1mQzAJ(|C4;V=p%)xV(*rj&mVwjW&a^WPVM1-{{J%vrMnVOWx z#4^%jb5OAuRw+1(iFUx>j!NFrT!1`(Sfi6RtrrVLH3I5wr&knrIFTYEWX20pkZGjl zh(r0q#)0jX`5u-AzBZDzJ6TbTjPxzWH(>=SJC(x3TrLgbWc&F3=py=oGKr-JKz1$* z42sGuMr|^V^Fo8*J_~ZY3-xs)e@Po6^mpa-!n8ucBUm0GM0^0G+ca{{Kovo668~XZ zNIitGvQxAhKh!9NYfDBH!jd}u{(7{pmg(%R=5DYLkD$%kstQpBcE*jqbwrc5Y20X9 z;a$%wBbuW-4OqjiUI-;oqNpA{>g1jB(3 zRxaOX3M4Rmo%920X{{vfmI3L4FA|MX8WJZGEP*V>r-W!#vTkbRidT$`zdxOr&foCIMZs^WZj|s93lAK0K0-j0|WmPxYP3TQ^}6M&q0jaOBHX9k}4_ zhE}VSo5YAIb_rvusa~Ns+bL!@XieB3^@f_)(l zik}GIU>=ww8d)N9$YyctBHvFwJS~kkYygF0e_O~uAmn}a^u;c$VZC(IcxZZ+4 zCh>oE(H*X~NRhUH8q&_CZC+ko{#YCrO0p}HO4&&gjs-w~MktZ2g3Z-FVA^n%u!$X( zdxo1*zI8|Cr-kix36GdMAc}xl^cPQW6x;FhLgFP0mhX%O{S21;?(gVjx2_Oj*zN@2s<&Drd`&(u0501n#&C&rJ$sp zT=%4_KtrwERARAzytdy*j`O$6XZ4#oyJ$b&>k^;eD0Ordie`MM=zeK(!Up6dvx;0} zKy-ViWUjbL)5@qWS~jD7Rxe7tmix|`QTRe5-Ze~z<4YX5nFRQq{wDcp6f@>uye06edKF?!_>IJCDtl8Mj?A05wc~{ZWi<8pOy0j@g zn5DIjp&A7Yrjf^eq4@kv$e!l2IKdN`Z#Ee86%qBysxJDFiHWLZvtmd3FDkF_SX0($ zU;~x4-=JwbQ0tGJK~tTF12C%`f%kXaxWQABjYT}bl?z0+BHFZ(d!aBLcbHDuse4iy zsrb!FDc$ctq|)26m?PJazOh)0Bx|8GjEtR#`ejRYD*w(MszH;uSXAI}u{A$E_}Z|- zL!jA7G1|o+AM$AVREg(Op5bBJncJr zn3IrBVMYO?3jatAC{?I6f`O2u8JY7Lo@!)qd+d;%mE5n(D#vyJGoWy++RdD{tQMBE zV*93Opp9NZ`o9BfN)2wk+)X!ZF1&s5n)ceFezbBus- zW!7m4M2*(0Z|pQ#cE+gel#M#u=jo&yi5{!RDxzW()v#C&X4E0D1AgQ3>4 z-F@r-O~c!#$zLqxvf3t|2|B&4X7bTxOZ@FM4|B^-n)G4bnncT;RUHFfFP0QAYv(vC)}6pFeQ-fs zz=ew}-l8XSEPOV*dG_!`J!5itFwZM%hml0qgo-UqVGhQ%<;B1L{P}Zl=Zg1x|G)cx z@~xEG)l<4Fr>ydO*O8;>XwS35C1G2-Yp}(OqJvXq8{d`4upNJ*>%Js;nn#zqzFP+T0qgvDI-NV@Y-BZ-FwN>(r zroDB0ys7S?u})~swg(%PXEGV2JT}phX`OY1>+meW!)F{<)-2v|;d;yM%`#K{Urv0p zZe7Ho#uX2D9o-Q)@e5CuR8tc0sKkvv9cMaNwmmB5p3U5KCc?LHpN2ng^5S)eTvexD zQ*Pr3JF;+=|IaD2W&fWzv9-&0;?&helTJ>2lkqEv^+u$wbBwmo;cJs7N0vM^H@}#^ z?dUC+NTbw?M`ouko_6qSQcmkA|>R3z*FNSd#?ZSSwk z_Cd24g$-V<`ZRM@=x#&9jk@Y9kB0KR*`jAA;;>-Jo8zpK^^W4N?OAxTs@|%sQ~I;% zpj@y*v!TXT#p($%Yc)MMInJvJ#a>?g_Lc93!~=n|L%-#JQ+u3KBcs99(4i%I#`iy~ z=-Z}^A~Uo#`dUPLB5hv8)<5FXd7{xPJwZeNsQLfj3MtzBpI*wH&E}Cj6|53@-#|Io zfq@aY0+&I7fx-R&@M5}Oq8}Zr-v67u$7Pm{>hJ$`tf5Vxw39TN>~5@aXH@(y8{)M= z{Mav_TjjT^zF%21B`q!a!!eQl?|$rIWqP^fENl56_5Y^V>;E}A^SN;p&18G}IRC-I zxcgSz+)G~hIQstoEs(<4|AB#_zJY;(gMon`$YcbfB@h>w07Y3`jrA-o^o&+f0vrQe zLmb^*BQCHrFk}MFW(Hy)SO6x01n_(ZFbK&k$w_5!DlM*52+mB;OUwc8Tm=KiqLR$w z5{BUX(!3O>%%q%Dz2y8{R;Z+VYF-ggS59eaF%F9wpeh-Fu0S#!CW=I(+JGcM9VP%E C%rLe9 literal 0 HcmV?d00001 diff --git a/Signal/AudioFiles/completed.mp3 b/Signal/AudioFiles/completed.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..50daa99e1c0d81b824523c2bad2a6a0a6af41412 GIT binary patch literal 18390 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 literal 0 HcmV?d00001 diff --git a/Signal/AudioFiles/failure.mp3 b/Signal/AudioFiles/failure.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..33fc485576721592852cae2417f37257199b679a GIT binary patch literal 7941 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%0Db`{mFB>v% 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 literal 0 HcmV?d00001 diff --git a/Signal/AudioFiles/outring.mp3 b/Signal/AudioFiles/outring.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7442bf7d5b404922c6eefab8730ff5ff47e8d747 GIT binary patch literal 97200 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

0rZb`x7$@vP2ShKzQF@Pe*5DmEWO0m^ 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

#BFUX4S0ux#i`H;}uSMQV+M*bbZY@Q@h~O zlp{5&=9lGv!gp0xHaFXQU{9KrA2M{KTQz@RBDSYGD}NMs#AJn)g?05TEH%(2VT&Op ziWyQ~5J|g8`*>vqmdS-(d|F$_aG0AAye3o=RA(YY-cyq&UosP`4FRjm%3k})$S zE5$D0*b@_D%uOTp{_OT0JBU&~^%fUw^zOZpk$W7k`|$a5qo|Dy1$SpfiK|A8jo@@Y zVc&r24c8#mFL-hms{5=N9o_-&B*mrj3Z^*`h-_^WxWjLJ2Vu}u5xZr@c0PrL#4P+! z_(M@GttPA8jT+V0*V*Z;3U?tplH_1O29c1s9bOCMHjWdjD5YdFhh(1qoUGW zI3e2^5S15731o>?RR{YhVknm+TM47kso0w;v`0s2ev$9pSAi4VD=v|lix6qW5MznL zQnI*|DP4ty{1UjPGVT*=GBQ z>L?ZE<#g;)R^7wBQrYS4)jG5fWIotf4+i5SSzDA*HQ2OM-Kz)tYpozpWE?E@+~o3o%hY&!UV#2AkSi=B;f2Vm z--=iyzqhdM15V8JH)+S}AginTy@VO~!0>dw?a{IU0q43tj32|#%}D9=l}B=t=L0@jf|!BWolKSGy%jD)##BYbct*bSjwM4al5C$5}YS+-tPOcG+XbDLqPNd`HD4x?1h?ZXDK&pi~X zxj7>dL`^t^qCMyl#M$4-C9mKOO-*f5(NI{ZvAOG_795Onsfyre^uKC6CdijZMfXkb;MX(EJ9| zV`#w2=V~-xFGZXX`N1rFhWTCNG7yVP>)6~ZL@8@vPMH{-9?}Gplc(ENsT3uwmvEA9 zHPd5`Pj-pe$y~`z8Oz1Sj_e6kGN@~?H{#psnx@9n+F2%IP@hOa`rWSAf+s*J0l z!j;VMwn9a{*sL+r!?0Pfcwxy3Mki{8T^w>M$HKzaR(W|%ZAZI)P5-7Au7+_=8H3N* z$9X>+m-Ues!IDFpG0)*02n_=TBh~_6E)S3$e4Jh1I6g8OVJ1up;qs~$W?^-*QHU0! zHB*OjvVX9;q7n-*5YiwzxUl>}|HP9-&@;KLI9p!c0xv|T!b0~{!V2IU-b)nXo3vn< zBIo6$_aDZdS(B5xQEdzWFhC6FT>N>P32R*U@87vQIyyPExUhdvl&f#hS)@Ij_(WQh zPSWci;I+ts1hSXEZfB%sLPQDB@0$=Q7Kqj+TA@aND&}`3n7Gh z1ZR23=|rZpeOwqGv2RbWQn$|Vc4+j(xr>$&MscOG<@!KdvkMnxuF(;a4<`;Ll3&-dN5Jf3QO|UTnh{A zYdV&_@Lus*-zUq5_CoYC+`u-<%8sc~Tw2D5Usjh@pS-dbA3Hk%eHK40+W(QVRlULYVBjyu~b=a=b6+y@~w%Gkr=hDEvur$pH-Byr+B3ct7wrvxj^Xf*T<(5_UmnYH0~<3cNUA z5_+)U%?85|Nnj8JQ;3koCX($~Y0^d&$CB*Q3q5hVw-V@*`} z3h;aG+`Jdj9#RI*Jec-irHgx$x;dsM=oR5;LE*K6jAMj1!6u*`Dg{MFc;UvbuD)KQ zrY`C_@g!DODk)%i$u@{nh?VSK+ugPMH)Yk)wC3G^ELU&~qC(WrW9CGdaXdUKFRQDk zqMQYAPx#hph)(u^=IIBPp|p4I?3wz8>Nt5?i^x{NxDa*`8C346KFfVQHwV@qIDtqW z4K`pORoH`Ofn6kKou}4BTV}w#@2D4C>G$}{{F&HP;WPNfB-4$^f$ZjT)__C8qHEsV z-RJwFj~6k(jQNE(<73b7-$T|+260pWap%rmQY818@%-??Bf^or4)Cum`k67pO@&P-f3cHZM`$#wu zIu#Vd{;l>7S%%)r+LkiUrK9@ckcu4LNz`S)F~X%JufiW59hVSaG&P-J!VzW>z9d*@ zq45F>g-MylmnomLtqLPq2EBRU5 z&{Cuv5KnT|2L~He5s4R}XZ=O%l&@%OXj+)Y+oOqT)%gjK`?sQ3(v2-Z7MuGNc6n2 z?TJR1zM}+%T~J|ZrGavGW)t#^(6$w~RSmU^IJGyS1%5;|RIEtC0{V*L3MUOXX?$@B zzR7aRh8MUJx3FhM#o*{@cXw{y%tA2mMiznD7VZW1gcbo_WELUk#Ghy}HC^Mz96- zr}U6ps0ADzVi&cIP4@!s&Rme zP0T_ca2zVJ}z8{Ipq8qcG1z^K<`BGO<10a zrwWdImfK)OCoCLhNGQoHyH8u3E3Z0KiI$fh00rOcSIi2jkw}bq)aT#{n;UvOrv{|wav zA95=!!WiTpm&%2nWRiGcex5y2P)-G=b#K5YmeCg+9H7u1{FLVxXq|0sPQQX@Db%8; zgzpOF0mlZ#nth%dR10d~qTk${G0Mhz5p~NgE&kSEo$$?JlnR0m>vFDKxqSIDrh#%XJZx`3 zLnCMeL)M!+mtuW&i}Rx86GfQdHU6zI)NSZRhG;ic6BZ@>v%NxYx3-&nj@vZ ze?K~ECHVCerrNfQJ8}MrymMqkyLkD^6HEQAIh5d*7JaI{f_PMHA@T1FFsXL|Px(_a zm#6A#OUqaU2q({uYJyn?cKQmEnzLv$WyXc+v*eYTH{Tp}X*m82DS}z3s#;e;pJ_Lz zA=lScRp$z++#%vK$Ce_I6suwyK!)LnIZ|r+nA6QEszf81o|QPKk!f&F_Z?2eM}0}L z=Y^`YB_Fa#B*I)qBzoC}6?fnXooH%Wp5l@9(!wjkgLqahnwD4*O>)Vh;7hDP!IDdj zo(Rj#TLxMz@$QX6xQ%XWW}I1*v$(KD-Do*8tivvXRAhLe2gxkMm_*Na#J*o%$~b4T ziZC#_CU)UvZrzh|-!059b9$**Dzh*W&?-_cBDBUOgc^3NmiqqPv$02XApZIbz3ji6 zRaXiBlMBdzd0MYEG3mcvTH4+!W(G+6X{+G+&5{|FVh1|bAXzSqGz(K1=x=VRttmp` zOP#TsStRrqru8Y|1+H~&dy8BHQ#(UdFbpC8W%ZSMcTxvTz3`Mg=b1C;JvH?8(^+Ou znTEi7^#`l*P`a%5G*)5XF^#{j?o=xi0nxM)MmV#C%O}vG(A6WW5tfUK_7Ao;gV96b z#EcMlqW1tNj3;r%#N3>C>!f%mtz15 z&?vYU2qAgSsUP8DlS?o#khL|>U(p0A;;JHd23}MK<^0S!Ar~04c)YW_FmIO6--0(` zqp{U|aC?nVunr2gP_@ue*VhjYtee!Msfo*dtdl)soeYmr5RF8=){EzOFEpOTrI_}FT{t@e5>8J9aR->82MH#QJrIyUI|m1Qd!0^^ zD`X|&QrOa?rVdDOB4b8cg&y_zq@?6C-L&pY!WmOQoyC-tEuONwpK3Vg*%5z(1`&>8 z8z6=r#Q!vsh*5zHH1cXrmTNoBalOb-=592HP`^PYsZ24k3hfhsSuaR;06aj-%KLZ$Cd?Q#OT*x=c^T&l!1|v#6tg)hQc8!C(n{w3r zQfj{Gt$jB&1y@?%KY)?2G>Pk|XAvQ)#zXsXoJ0Z|7B=#f$gE)nh7Lj6=i>)hzvpHCN`|5GqrK1P9jJi;BwhXJ>gpD?`4}4)PBI5qX7y z3VVVx4wwmiy6w!_PV#ux>}YR^bsCJw;8ZSHYSIp<7kle#QQN}CQB^^?h(1)$yO9~E z2-UeFvgD?wzCN%Kwwa=k3sHN5S%e$85Z0l>XQ>A5i=vK1y^<-VBXoK^A|^dD|x0^HQABC>OC7p|WPX#9&ZgNJJ>3D=TZJ z1~gfQ`j+Re+ArFnYLWW3g5jNNt*w>AAzg?VTwj<);ufK3p?K}@*^feRQG0hcKB*(= zS=ybFYS4K@R4zLxsTVz>hHoX=#XQxV>YQ>5yQ%kfkKmj}DCiliZ>|u#XzpkBVnPdm z0=OUq$*{togL9#0;Q2tf!^3bPIHbH)B;7)+hb00R^23?gjg6h%Tp?Hy(TM)T??4MF zC)SGz^J>kGG&B@b=OZKX5+=eTM2heriDQIaynpj{X4O`)!9*3Uxgl!@FG=+MQ%p)T+(-Imr5}onGQ=da$H#j5e&#s)XKDP zh(Je&6aCDW$@}~JPqlI`X*n_{S6BrOB*#_cC+eN3BTngTx;a&c>g(F^L{bXb1V+KL zWEIHd%-nVG6>?PW=>5HwRVv|N*~TGuWieU8`Jg=A$;tZq!h-P}S(2SHy5rm0e7jme z@*5YE6Omx>A!=C8I5gDRnf3@_SyWsH`v$cQ%ZMI`B_RNj*Lwecc6NgthCO*I7uErl zS5%ajN7VT}GB-`L2kLI4i$jD;i&g2~!9^p2S~uU}73GpTRQ4tXmH z&595SkT1it8+h46k6#nX7ZzuxU%VWB`seN6ensuXDl#83VkJ0djZa zZL_siI^iA>K-m&*3ka4RD-(2LqjYpohf+xt6;&wmZ`_F0&%6ZJBlt~Z17>Fz7nzX- z;(N{q-V({h);=8y&_5t%7Oc|uwbv4bMhJ+WG9w~ zsGFJ@fmftds@o1g#+R_ns2A+L#YH;;w2U~}ZWX$TWtihRK3*n*H5OT4+ukWJuVY?< zc44;AJO~$9aq*%ti9f`8ut-$6o+iIlZZC+l`k8Lw{$y{szg51CMu;e(0Tb^(e%jpF z-7PHvFFs$D;6)e&c!A5~HLJMzs+|?T>g0by4ff zE8pr_DMHL^jAD+zWMbkOxzrmsu3v{;{08Y{wi=aMa*L#Ay>Lp-;(~Kyx3){s;Mz{X zD-`&mGM;52JWs}?RcDQnF_92&?=Qvx0OQnDC=<2`!o^@=AM>=jjx&j&+TbF1M&~LRp31sqC3S+t}FKt*C%$ zbqJTNK(-;Uhb%(XC_?Czh!3>UTSZ^7>K=1bBUct^;Y|#kllvDJD#JWEjD2Q3;5jsn z>iEj%pq!XRvI}a@=)z%oX#rU#&~M1t@|JSDq3`H?q; z6JnewObBw|iHS=M;yQ6%=sDleP+p#&pJJ$6cp~~DfjPmux>4#r-wn0|xuEhlx4Ifg zfm9?beGw$o|^9FdG{h%6!- zC#eeV*IU=$11lV@u4#=~NOsB<1-3=JjPwzn(>kJw$?@^=v9IuPREl6l=#rXz0b{7v z79~#rHbs2mlTAzxd^Gn$xv)MY^u-H^i&uSpZ|IhzBxdvl-cwOwd3kH=1+wh}Z-x-~ z)|H6#P%uQJ-Q9I~?76w7;-EOOVk||*N9NVNdr>E_Qx9!x>*$C%8_XgwWOf!+ZzdZ@ zVilq?4N`Tr-l@6b{$*_> zTe`3s5pjJ82Pm{4Uu5Z-K_uv^L3S4YgSE-V^bQWNfS}OL=tXjZH&R{QMc+pirZz)S z08sEiX<$}B-~uodE1sE{_CyO}5W+elGdzr1X#c&aF`u!rV|sdJWqXGX=_MMGkO$yI z_@%;36kQFL7Inur^qeO81f#MfQpRZbd{m{&Y))c0M#t>bU@h{e0TICrlZ=*EM0#op zKJ!K1bsPT(-#IcMz*Bs;yF;(8=~4Rdp&j1x>1}+4%AzLV!`rtbqv{oPh5j1(h}hKj zb7+h3PR)?PqDJ-!2L=Au&z_Xamu2{BYKjW&a?ha_2%?91RbJgY;S0wH97f*E5&1(E z6}5Gp9fsckE{K?}|21-2Zr%LHZ~ywA-+%v~|M}Oy{`G%&{Oh;htYNcPL7=)`;~qxP ziJcGtQNOg3`I(n*$5JP+*yS@=D=~z2gS@!uCjkQVQd= zo6+0B-liNuLCnbsUAtkMim6VLrvrVtL<$a%WGR>3e4_DKjtvQ`@C~%~}6Z--lhM`{rf*yc6s)==TGk5yME1`?e4Mm&r8v^ zN)DxNItnN>We3R?ae%-*KM5n?<8>Z=}BH5&Ij4i)b^wbJ^^H`*F_0Pi_JK5S=M*QH} zHq`(!mzZkgQroXR+6g%j(=9HpFyA)utnx_f8_O$7!JL<%*9)n%QzI&7@Xec-FO&vy zEXer&L>I{6k+IS7i7%g*mNqwxTyxL)Q7KG4!1e35Zr#3p_s-or2*EvCG5+u)b=nl? zMqkYze^g>P&B)Bn8-Pr@hrhG=A=u?I6xMjnt-iu~YU~8bK#HUCfphWJnv;@QnV`B_LrL?}jv#mvu zhqvNVzryu%^)M9jdLSq5gqZ-lyUYg5+!{WY-To{fx&(9 zt|3k8kj>4NWh~j`6c%U-G}8aUOz+g~;v<4F_-Sm+x>tC&1-bN0#x>K@>uTGH1&AF` z^?r)@m=^RrmB8TrU=lUrU<9UcWM$LIiH`idX+4j~L`v#3vAyJy8| z^H~&6CPW7*<76!rYhk$fOn)loY~|#51_}yYLAvo2f?o;>qxSHW8criH&g8TQTUM+x zX%Rrfz=U`Tx;8a6cu}AkJ|Y66{FcMQu_Vlnh9^N!=wYY}cOC6qt#VWC8|!V`a^*4x?k>5zLcKvj&*0i~)IpWBO9P(I56>XlR6Mdd8XSaP{PI zSRYoxl<+vvkxOSj9Umyp7P=CMfm~gok-Tzc#nDmVN^ImjOst$u>H>qqgeN^Q0k4PC zuHQaCZ{J|o<>aJ<7#Cb#c;{{y5v}l8H5^J_J@Kt#`lPVSY?vyixL6%)G|GT6r_lqs zvmN$nV`HDJC+}Ql$hs6YIa-^}Ewyy6NVUz%pgc((H3HVjgZ9XiVhyS2D!~_p|DYxT zmL%7@RS}vRrihqXbo}rxQCBi?Ev-iJxilwu#$irc#o$`hT1qjQYY3D?Ca@lP*f8q= zQy+??BQ`d^f6t{(#jJEMW!wGJR}MI4fCX!Oik#<@)4jKcL&5Lp`zote@v%5O@84BQ z=99W6$kwUcH>^RdkDkcJ`nSdL@evTo(9owL;0q7GzW?y_>0{#|PhP!_%AwF0L&J96 zS^Ty|Wh-k_;)$pP7j}2)bLgs@Rd5x2d7d5;bey5zj1et+5_NU9wv=;zPfxjB(6@lh z494)Gq+ig~VFOiu=yEeanB@Sta zWSZL-1ruTVKt%9;S`@#LVl3=2a|&JmGhc<}%GScy>-M;vY$`?wL(~Ab&OT9xbxI6c z4_FJTws7*%!67=2w*d9B;(jsfhd2>(0T-=yY-%J!i8=(TCc1n0AfkwFyL9>9T|4w# zyr|Ywe>g1*Y*A4`MPg}bZmuU>HbF&~a2KcEN<~WvLdcbPST&Kw0T%KQJw*qBAVEv= z^F{Ld7e_~CinAg}i}p*PuNv`>w>TtAi)LYNn=6NpocOe_Uq{Dig*5PyjsMtBS~uU= z9?5$3*RQM!c>auJJud9YJAQ!U& zJu_DaOFb<~LfLT$ek`aV&5cMm~&6h6W znP0tjg>+7?3toH?bLgYTq&j{VK8}3*Q}ko1S>c_NnY6RR!C;kBj*e@Bk^7#LtInop z2?|H>U|yhTRTN`|l9~u1qfl&d)8U?okk2FE=vZ2AD$fCu= z2z1H60wsCDt!=R^w*|ZFX-xwHjxw-jWG{k*_cu0-S=;|$c4ls#+za&g z%Ifr#R$~Z*@ey5t8u;-3!-w}g_=d(v{hopT5B5uXMYkk;5<0F84J}YLfeKzEzn6X` zYHJ`&$R`uL(CFsM;O=u_J$ZP^N^|L8c%giAbGEkSZ&2xhEA;`?^S(=d%tgP*!4Hn9;ke<1&lj=rtJzt9xIn9TJFEHvWc24F(vGf^icYzhya*TyUy%782w-k z7wDZeKZj?!GzQm|_zm#HfZ8DJN55k6{{5#x{*^|p!^5xMJb4oM;@R`(1D|Le#GT2T ze)%E}6bVOHXY6cVu6CKTM)xXv=68W8^g?jLp$`&guP2J-L|^9R*{M{{P$oeJ9JfzB zFUdle1X(&Uw(s}!H1Q%vLCi9DickwI6H~3p{RxW=`)v-md24z3Vkh@Jz6Q<|Ywqv6 zGU+*w^i*RW7W&*SS~ltA!|X#P%q$-+KnzuD8Q!F_GCN0UGpfR!A_b}Yp4wuD9{r#3 z+s((>+}PXC$*!!T*FxyHZp^Cs?dHvY{Nq<9VEp>)FTec!^N&9)+zL+=21pHqt7#%r51&JLpXqht={YoqxQwh$xJz@0 zkP58`vd~7uEi`^heH~C5DJGi>ad!SWXq~F?$cH2A*Nhy_E13}T3Sv$xm&qPNj&pL9 z3wMV36dmV#?%ZWi$`44xk9Jvu>-zzTxObZdY>Ya&)zed6t|HY-AQ#!14c@@=lCkfx zF)cU!lt*l;JUlcu9&voc!g>T>i*?H857~-5vW%OY^&J8(1B=HFTH!)hyH$2o7ONzF zW_j5RhK0`~BQIY*d*1gZgl~8d(HIy=2=g7++jpM^_?Hn33uY|+&ZkeWUVa>ygvCIM zArbR1q9DYDwN)8pZAd|Zn;m<=gwdu{kqsZnYg&kwMO{K4M*s8OR--*RTEZ%TGW5{F4vm0yTl6klLduANJ(J zg|4pphJyU4?Ls@M$2K=-XGc-k$^qIug)ai&efl&?++ma6jHoTmA+?qCsO7RIQCy@& zO?vg*xz<)aDCI?u>+n!t7O4oY%m@tHV!;f}moJ|`fBLkK-0k6!K`z8%Ag}~X<`7&3 zstTd}`_t^~^76zacQ{TD->IodG{ww3S!ACV=%<2Lk)34*N17W3H(S`$1PY|GkaLSL zxTa}ofpv;UT$L3@LmTQ#N{>XC{x25-nN(=b`!Jml}_$mi6IsLw*qF8I)Z+`D;GXXO0(?yhq)-?X8;_j9~v6#*;2^#ZL%|R^<9MBY${Qz4}pg#PFQIVuuQu z?n$n3F4#rRs-Z9?VrZCv_{_I&2!j|Ct;B^t6)4<=4eTvC;-M+;XlbS&;l7!8%7fmh z{^;7;x38ZDje)*;{R;c>44HWJ=)qra`i6&4N(24K-`KdZR%A1RpwX}+6O+Vwr|B>|GCDb>z3vcG9`-&b3gU3g&x|%KuUSXCo%+kd)e7?q_tcU6#02#UF-{r1&Iuq z@LY=lkiJUIDL>_MYn525IDBm_1f&?EnLh9R)a ziOj~vhMEFTELE6+`HUxQ@2rufhEI)qqYWu6ly~j!x_AE<3KRR+#O=T;u*pSc`kC_tjK`Syb zIXgF%$;Xhl(Epg2|8H087d{&R7-v@XEHbpQB^$7Rz&>(FP zKRZasSe2DheU6u&jV=M(u;s;xX-&OBYnb%X-P%UnjVm!L0)*C9GIcClz_WEK8T4h$ zO&0OAlegZ0_E3IhF_QHwFQ;pd+>ug&Z7_G!oF`BOazQof%J#O52%Rua%wLo}An$h% z{o~$%hr2rO+{Ie_!0fJHe*O8^Uqu%`{&e$(OfVlLHag|ol(x3Ef`Zjmy~WR;&FRHl zlNT|@6d?VNPlF>PY6%|!`sunZs3@V68#2J<*XAg>OZiDbu!GKJ0gFrIw~bA!FqT)$ z>jzQdcsgfFFXK5Dgu0-=YgJBnBps*MiA8sC>vbpsbY& z(fPG&RNZlTI+cd#fF~>javmI_@_ZvaxY!z|OI6#=T1H8{fmFaECQX3@f-F`uc|xAO ztE;&wV*;QJtEzD`;JwfkT(abzD?>CPi*&2EC%KjhOUI=Ymz5t;`>lOQGGRY$pdd(g zWp$otjXpHS560l+I5+R;(EN$D^}_>a5!Hm1Grv%5@x#4)_kX1IH$d~pn>VjqxoX~? zn!=<2QrXV;*kh%rFe{t+S3EP?0$nj4HhmbhIW#mukLh3r_%y*p4@}G0#wMsO#904X zEn1^@mdR?w-zBp^t7|4nVWB!5(cb;?)yXjKc0vpT*d>vr8sh~4u-9(}kP0=HU0qQV zL`0^hmXZ081U&LZxQ?ZzWk_S_rsauopTRmqSWo+^eHtEKSyhqQP1rc7zoi6GC3tGS zJ83ifF*pP%F5-EEqE{GoXX=cxal2q_peI(A7njWjb%GV{2H6*80nIPGefQVH z$B!R9e)@z~7cWWwCO_-jx3O{N@L^AYp+v%y-@dkH|5Eo#4;s7KiMz^K)qmHazJC25 zziMTF`WZLvhig}xn? zb7@tNtV&`&(Ad1Bd@H$E(H+$j;nXtaG{qsXS58VGdY&x3$;hp(mo7_MsU@yNLMKw; zH+)>aM0Yhdk0ZgMumKXYZokcQiQc3QS<{sL9aRDPfRodU-@KEf!>sJ;D&$;7UFt@^ zR8@!`R^M?P(y}P+jp3g*Uz87drzlx zUVnDhgjLv7qGxc8ocU!*yyz*Q9{BuuYHD=!HEbtO5Ip#MyxC{Z-u8irMkXM+rl)4n zJaXb#+||tuCq*-_n9_kR6VLdUhbfA7wnn>V_NVd%Hu1(p>R8K1N^ zOnx##MeNSns;6afX=8nFzlceK`><2S>GUjcS*Y;md;(j6TMZs|{E5cODp*7Op>F!# zNbTT##w;1N0g9HaL0GEjIG#_(i~4#yUX+yNMlI$Mkw_>o1%l7%QVy-XIy~Iiusizx zp5zfcT}ee{ZB0{iVZq6%*&3-cBift&Xkv1})hj>!bp3{Wr|S@|0qDldI|U?)5ec5v zFlB_fQRMX)-Bd}*XMMMR1cWEgXOzyUvJr#t)L|ADBEi(8?YmUmrk<+Oi|QinOt&6K z4rh)i{MOp4nd5)=kRaZIKOx6#OvHF3+9+z+jqB_Xk6{`SP2&+wydj-Xj1I69za%~I zP{t?71AYJU<&!5LKZ=c+RfV4l{+Zn-VhckZr${$ax6&RDt_NLCBHmeJMVQ9cukmdJ zLa~NACwckVS?Gmrpad5LE;<^ICMX(rBE3-QIXwxl4C4p@FmX>C1(`VWq=e%CKp^%t}@m$mODt*MuwFLCS)Es$y~; za!d#C@Qd-eDk_VM_SqMm#iTvF!_ty!`ptC^7jt3+riLZ=dSwZ{+-w zhh-NF4jh?i$cEN|sMI%DE%QU?=I6l!)SDByKeJk?n#6f!7=?*3zJPjCX6V?*fwym8 zzI^oP!w2n;c*RWeh0h_U=ybhwb-KG-!fG@&cK5WBMF7bs75nX3<&2DLKCf^E~2v#$^Ut;Q9hPGfn1qfthG-6+= zRn96dsikrvaVF?hd?MWp8v-QW%={$d(dJ@RSIeim`ymZULPJcI4YU)7&%LvToqqLd z2QqfJ7b;p|^^giyfQ)mq3_@zDN2Q{%>)KUnr-_y!6n+HI5(TKWaKrG4%}ug?{e+H7 z>+7CsBkJmJXpztn0K668d}wh{-{R)h_uZ^4dxiz{(nktq!R>~wa4$TkIoyx3v%T|; z>I!{2Ej0Q7bzxXiHi zdWwwDpN%#2{$RoeB491O{0!QYOKI>OXh#rjz9<{K$AW5lmIU?42^ZZdXlUWOgtx-L z@`4@bnwsn2)pGMH%4_TF6t7Q|Tu&4E=s-^dNhwx0+1Ia#yu->$trFW$j3Hvs^cnLU zMkGq9Kqu!S7l%h?XUlIBe}xkvo&nZJkIU_GVnd4*i5p}dyF0Jn=;@(L3mrAQh{J^$ z;a*vZfqK3v#DHE2k%F=#TJ*~%BWUC{z!)A9V5R5{>jd^Lxxz(*VcacUX)h{adA2$dN zAFDL*;Tq@+B={WJ-&IviBO}JXzh_skW3m?23))MBfroj8>a_GfGTGJDPFGd_4027R!qAu6z7iK-Ml*Fn) zqUr}8Vp4xc9YWlPf408!U|)6<4ap9uGfIlHvdxck&1E+{v!Pvb%C&oK=3g1r6N8eO z)Y6JuBu*4sL;yb~GnJPY&`o-6ooVT4`;?2qp?0bmrbDk#f6uQ=%TyEaN=(owKEPks z-XaomNMK;^NDIOxn1$CMWn?cH5uqmKb5zBn874>BQ+9T2W)^)xMVfg< z&?oY(aBJcW9(;wr@=}au6cREn`utncFviFE4gGZch+z1+DbE#LG;1m%Z*J;xmPX87 z^DK~e$tOKSA(jY0l;}K;qT(FBO7u_0EbzaD90$eGtRK2|pQu%mo+yv5Api=)4`7Ip3G|)Jx+kwXQ@wKbs>%hA zkPg4_M{&6Wn?!$QP0Rv?vx9B-)M`uF7m+8TtD$SCZ=tH{>Q%{I*=Rltd1Fr0o7lWy zTjAC)wRU}GuHq_vc8K}!z$j);bX2A$3qpdKNoK@v;m*kHqz;QrS7ebZNr%e9_&KVO z1w~)*)tcWmrjp5OVVunHK{Qs4tW$NeAhSv~mRmbGh$Wbsn_XU+nU<&`h)$%PbexZ* zi0n!tF$Np+x59jo4T#LhJ%fp`Yfykxhjkec{5DD=>;= zW*f~*5j|~6Td2;ktyos3aF@Av8=F24-~#<=jy`IR4|7fU?6*7GlY{;2oPzw=@0hwS zzNo+>L|X(;UI@z5_jJ}N2A&$GhC zS5_v~QQAgsG}Hy3M4s9aIwv_Z3bvEg70;a^3%hpehfGZd+dhPXn5I@~b$V6}Fv5l9 z7zR90BSMPapcMf6y2!zZU7z2)dGPSXOJrw2K6GT13B1@qz6JC z0taWwB`FW;(6A(|gxT=yR-If06I|Di$XCZmVneuf?4R6)^58=R=FjZ7T3p=NU>7_{ z>);8*7w)<|fgMrM6s@KJw=$K$I>EerF`W*)kkjl5;x1ScxPpuw!O!7&3ksR4*x)2% ztIkwgT9fp^O$tlxej^ufE62wh>vX~gxB?-V6mUv+6s@4z&g?TNI(}~4u7wPgt8f)$ zUf?VHKxEY!DviI-2y#)&T9B`ezTZa&1EyjJ^-z;H5?O0WF35@|woZ@)FE5GOfSfxa7ibKvQpg2dS$LzPLnHc`->9~po+1pE5GQy7 zIZzb1l1vODD9FVp<>KLkSFer6BR<1pqu#&oKp$0=6{mzG8#-#%c}6Z?Y-s^{qc9PS zBo`T>Naus>39dq7AFvbj;zXwLLZmFX0Go(|0dh????ul_bXZ!}(p*(tQlcFRE5dVV zmBcJc9w=9w%c9%u-~4Pmz+iWh_akG0FG3}T{%9~-tg4#7veH>%8j6G>QckG&O{^K% z36*GWx&4?nx3W&zd@=~^Yhx9L8ph+P)=WHOMHyYuo7>NknRh9vi%gF2-@137}m}^b5uc3k<%+PK11$+by2XbfC(W3*938~^j-WP zKjt5&r^m*Ihx_|qz54WNa%yaBW@cjY+cyLvWFn$vQJsNI00%28WR}ccJ!52390yf_ z2ZGjARR}Xkh_;8hk#n& zL1gf`PP63V*H#aYfcj*al$P2hmGe!fL^Vdt2}UE8f;!|{Iy!IOxN=#}41M9JuJkEp z72vw2b&+KwYmK~NXKJ3=Q)}MvM!d{l2X~r1Lmm)Q-FB#$blQy8+#EC`h$ryB;ETBV zIIG-Ye%@&ob0v1HV|6~g8N}oYByADBljh0tWlxE?4C8ZT-+%BYvg>PW`i-9Qz*?Lr;V+Tw;!5;!TdOe->M+yA zIYub>2xmk>UzkW_qN>U>LcDhU=s1WA{L@sAnv{k1B(w%f!;W)y;$@~S>;>w=&YjeK zf@qy00D_C0T15ldus)QCKz2#75p-+_7dnQl8r21v_oxfjP`{Y>puSLcPC;swvG=Oiqu%&%^H0$vyw#iX{bxF|25Zs0Hz@SNl)0c3-B#-^Y<62YY} zjQE`t6;)KU(DzExR1J~*j^Nqp-%6^lA3gEu{JHj~=GvNq!fGORVtqO36g4%g!>nXo z9bXO4|1gL7o%{~n`#D~LFL^_}DTIWa4)YW7zr#M@OQKQibXbHRQUbOpDsnOf#^{7~ zU|WodYDd&db#-*8Bo@@&)f$<%;vP^1A|ZE!+7x2pPyv7{OG>ku3bKSJ%JgOsH!|V= z0V`y*c>FIf)f(A-rb?8R1`Uj-rkYToxw!zH$}vF4MryywWG5mo&*-fJYfxwMpGTo5QLnm;E zdcE=%Xo$=u?i6kiy8?Uo8rrCco@R6f@&V)&Zib>j^pwI5YM5&4%FD8IDrL0-b{c(2 z76j~wUO=3UmSVl^K~T&yt6#Jtp)2_85#)O@XTsFr$>JhykCraQa;vKo<~)hapj2yS zsNb%LHPtPxogJ4i-?>8-OJ_H+LllM)l8DmC?4abZt99g-Caz_VxyNeaE!+^bg>ev0 zwyGj{PAM&w#`22&z^&j$V{`wW8flhOS5?)l%PZnwogQsFLIH5 zk?{$*tC2x)Be_;&;4LhS0Xs*zuop0!stEoYOb>Jmyq4JF-8(xQd<+?yo)!$+$rgKt zlqwY>9d&dWecW4`d(L0I+SSQP4ke*x7ix(BI3p1cAd<=XEiQ7V#3G%^_RTND`LJ#T zktQVI+V~SiMC)>V?8K^O7%RKFCyf!+YkH+ zk*zEv20Z+t+E^u`xv_Due@whuJ0ic6#ih1FB9LceWqjd^C{Ik4__jDZvqtuVHSG~A zL(aY=xuEXe+#AGVlpM}6Yvk6~=jX==m04aYYJj~;AIJtDIvy~a!^0mwIECifb39kj z26KYI%JMiU&rGCSg=My!_S}5m%a^eenok{cu0Y?SPA<_!9jgHcUYzo~zQb#wF7Q&7 z05nG6&Rwoh7WgQ7K5_y|7Bl2P3xdhuE#MJbS`Q*QdW|juJAP=Udrj?Es*RI~aO){|w#om~K-43lOj+*!)<`ft*Qs3{;S5piNX=85+ zz=fOzph86vs1tL-r7w7VTvl3GbbN$JBsLbm2{i~((tid)84~2QGMD%0`|ctYL-1J~ zIl6iSV`a3ykefS0_tuGtQP737#E1UjVPfm}BNJ1oh!L#=W+UK*RF8==OO3j`xl2w4^v1UrKM#FMTrFVD+6 zI&#+S7MWpe9{56Rk>mg?62c+IKxNPiO)s^A*`e4k)B*yG2nU(PorXB@DL8QIA)HPm zuc>!~X@dQ*kHo^lJelj%q>S4;2^;~T`ThgKFgiE;ZIR9epit3-Wyv3?Ieh=&{dViVMKCjSo@t9~C+5$#omk5~;MZ_Nf41peW)5=9pRCRPT&|4nTB63{S zMpzbzESVy!!@&G}oNKRlQgK01K^?J5xdC#_=|CgL8P!DDM@NT;%Al4?|I%9C<6}rV z{y}2jf6wsBSZ#e0*SoB&FeVP~e$UTGWmQ?Bnqot&0O@8}8+;!9pCq}~RNc67`Er|= z3xI}bY=YWET9t+r^)Ig|Ma!JfQ($FvVPT6)43Hw~rphMdsnW>EN7&udQ)1EN;S(-E zXLH3F)p`Jo!lHQB%D-|pLys0?q{>-jTae8%L#B^;6@x?a-2GORk*#H>w;h-UK$pq| z|CISqS)fJ08}Qwulau6&4}dS8eHs9_j&ES_z=#`699I$d^rcSrcD`r^7F&;ju&oD)0-X6GRn z)is?RX5AS!Crm~RHEPU4E)a)K&t-RaPfus3j&MF5>m!bh$r2fm0gPvGp-Fl-R z@i*+jMUSqp?mzws4v7v3HYA)?EQvodaym3Z*6{oHuURG4z@S|Y5mPy zWF`s&_-kshsU3Q$Ep*HcT|phgnKW>`wIhKp%tN7GF^hO198q|ust4l=+3oq?yy z#-W%vymC}^b=Fv#fm1_f2eEaw-_;e>YE2LjB(URLa|@j+@h*ys$?1(m95d1pUa$qL z#R6g`SWnR+)$EDAsL8Mwn2z|46%OT`VP>pitE_}TWSZc?K@lEs0kL&ff@2ca6!t=_ zYMidhS>M&7&YtskZui1=>aB|`)M{n{aHE04<#9{_u>0P@0Zsz>((16C3|EcbPZ}e5 zE&WwJ6eB2TIHp`B58g-w{H#_`brIsNe_{mC2s^nH^h1aaPs{cpN)-rG`lqs2@Du~^ z6j}(YKwvfIL=k$VAr+|#(OwtUu=l?Y4U%FbkLlOh*XQ(PStw%`@_T||89tC1&1Gz~ zA(sR5d!@6Bns9_bq$y90|G}9(s{zpOH;PrpmB(tj&ulZS1i9G8JD3h&Z6W*30-if( zFO@5ouivI(w|F>YZ|iBa={XgkLc>;^9(n1^z85 zmZ4V1v(x0J`ACr~-;G{@x%cEsfs14u^}Mb9C?JZN{4@M^K4AI6dw11byCr-xT~?>T zMh{XOZ;_I`w9I1j4@zoaRoD-*?6lET^17PV5BSjk)Uz|Alb*M{u&2y(25M&UPftAg z0WM&uw9qIH9?5q-RN0UtFYvC&Gu7*xD$1K()XosvrM4P)DqNS`#8uHK#=lo^nL?56Fn)DvgU{4xd-a#QEqW>&CPDUQNvx&i7s-n~^;A zaL!}tPz9=e&ZF!!%SH`XCH)PIQphD&(2ZB0N+j)9FV9HqD?6&6L%m_j*ND@j&+5BK z=i67t83jXFe*#y{^gi{dPbz8Y{(~xs;+wTZsqq3St^@_9hz(6nr4F~u+KIxe{(f-x z`?pcK{mw76m=k3YDw>$U7LVY~Pfe?h$%y>CwCqMGn`d4%CkJx~m$b7%CDu0kLZgOg z&Ms`k?Zg;JVLI!I1Hd#*`{Avw=p!sI!_wHxjo2NI9#GP_5&OvzS2w@`i^++w<4u8b zI6|9S^n~Dqkh5>b^%A+6J~xw|Q5c*Q@-2);JA1*CL1u!9E7bP3wkU1TJ#>~4lX;yd z3QkE&Z#x-P$i3Ufjh+ced3z+prA&zYpUe%*$2@S#rRonpI=Nn4ww^qeB||YyFVR z$(i$AI|a{U!tJ3JbBS1sRii527z=ENCyyNqtV}&VG0=_8FQ3U~qN?2*tC^WWF75XK zAL#_{&DC8Jr?&#>~EL9d88)g;6ODkXkEvNb4qRx*q z9lUHh)f}u?#S-+8N8Ma(jwO#;dWWun2doSY=bTL($;;C~Tvi@Q|RYMt?PA#My z2>972cIn`7W!cr6o3}&2D%%wdpI>?K8Fo$sfrY?IILkxcdT-DE&w2@=i9@+4m*_8W zrglvm8Y{Y&o#WXM1Ik!o4EPh@0{)YEfcj8q;Ht{nx+b`2qb9}^MDkwbZ_#C%HyBlL zm)DOsmI-kgx``< z!HnhkdLB+QMK}+&C55FB<&ij#d5g@>TTvo#>Ktr zifV$%7&I$h+)a&!wZQDN)=1gl0;U)<)CrZ?Y;J;e#PT^;T!j+oDEc_*NM$}ykjLaZ z>4mBLR#(m7z|QkwN{qW_2NT)1%kS_iu5KNl}3A0UIOg=hM4#7hg= zJ#6Sv9&8mA-1J}7&Wb|VE~_R2ktywbPk&-x+0LOMo6c0{XYL1DimmY$h<31G!U%YK zEGoIp?4D3aG+-dS!ge9z%38CVf9;nOnLDq%L97fzY>3p5z-+irTy z6Wc8ylOQfL4!GPtZ7!R{Hszrqn9zX7o|Eg`PfQ;yxsxx$xH46y@6GEs)*R7AD?Xk) z{p-&^|9tYKzyA{#x=?M17xRgJ2y}&sghO9he2o^|G4DebP}D1qRZDwrG?-cv=RR$5 zZEx#IudHls1^NrAQEO?ZJbtKcPq-mXVzmwR&_b;Z4MoNA19Y6A@eDs{>(ISIE}$`1 z1bmOmkx1rK#%eGxMs$k*8@#$Wj2;;DE(LnQOJKk;;U_0XLez#l;!7L=&n}x;NDiM` z!2L(zsCY1Xg+w+GHH|(0X~B|)OZLJ<{ zYn#YTe+|?Y)l#5AE+{;*(9CcV*H_m-=WBlDkhB_Mf)kGQ-h9w$7DhQ6ZdOJf54o)s z|0r1v@v_|DsCmO~~bQBKAMWKrsbEj(7&e0RaeB82NiJk7s>^G3V4 zbNHi>2hd89VN;9Q!1bs>BXfM}>v-nqV+JDImC=nkJ4D+_;CFgW(bd2hiuT(v3(7n_>BjYc6uIp_$s{k*96XaRMl5iN= z!{EDfb9m)#_S}Eug!voED^ZEL1D;*fG!jwG%GcJ)E0wS>Ns0R`oYbf?adrI*b2g62 zciJN!LgbjgR$Zp4llpLozegba962%St0LB(lbgv6M-w9ET(Iu+O^>WYlTrwhSnHyH zSW6diZN40jU|u_%a*0Yy3kpQd=sc!xx^LgVJ9R>|ns#rANUSo4y2I)lLD1ry&^=sE zE@}17PTiUweD2Q9TJm1g?&(+QUo|&%cG>%@sY&h*$SZVD*ahJV06@Dhxf{Ktssc5X@b$c?e+tN0_}&d_n1bg!AvG%L5q))3Y;(;V3sq{lo-5%G4zJDog@0 zV&^Nm@5%(69uwv~RpjXs(@?E>VwFtoPROU?R$5ur)Y#fa$CpwMbw~s{IzQjP6h)*? zzSb+JoKa;fKaUR3#bGMM-wAyq{Pcf}Ova>vahiM~wDPlB{ldg|oKZLwIJDswg!LgvzS=g18 zr6p_vE9~T@*wig*)G!u1blA-U6=~6-f!bxe?WzmXWa?#O6Bvz21x- zZZZi)e@mub7QpkOjnEqd+Oyi8QqP8`BM1&aMR|F1GdQKK4bEH{tgCC~YHO>hPDok) z0frpe5Mf%Cq&RJDE|A$gzd&X)on5A*#54GH+dNM`t?~ zld6@3I=x2^h`%Gc+D5*$Nh_;!M4HAY7v`6!>^IsxhW-1zs4{#_ic7|c5Z%-T5z}*dt9icmzIxC?B?KVm`B92Mb8&cvC%&zAapc0 zr{*g{+q+`K*l`$BZmr&fHrR(9{Fo`H>H`E7fw;4&v#zeGM=R@bSn z9_iG^WE<_+*|FbP#^1py&%6c*Ng1_zjcx%^<+b-+EgL#;Wo8CpBbNC-IwZ!lmp8AN zIT3qaz>_r)79Vo2~#( z4{IU5sH@c)+P_2lS_HZ<Z-iFgMG;x;W2283yX(Wu6=OP(o_XsEibRO4k54?RC`zFjT=AH zCF19we}Y~1LD!+5fBZqF`?ufz@!Kyyo7G{YT|NsPBac;{m%mFa0nGs76KAfhe5R+! z-0b3#*)!r__gRaG*Tsi;zN}2|27$P6O{(@h5XN{uC-puqlmV)H;%-A(4ckA&8!z1jvl|>ExRDoD^^uP#A=@GW2O## z-(|)seM(nWseCi(iV0-P^h`9r*9XUnO9{lo1VPv{$vCuVKS^j(`ONIgmtrE1fcz4k zAF2CJRMHGE|HbD8_Z_NT^;Pu8R#v8`N3iGgKcKfC#vKjU_x1&|0lc0)d;U!K<3(~o zUNA7=1^)*koR}hG1C{ytv-c&ku|tx)Q^MpTG6A*V9V^q+##$5<=oxQw1^Ndm$jti@ zK``ExZ=ODZ{Kg(L%hdq(`o!bVOGD<8yh)3>Np^&h4^iF{vhy7)zf4)7pXwP(mXlJ- z&4Jq@GtfTV5Ozde#6kdiSsSnwF^=1}ZrLgG=FPizf1;z&ty|r6oMj(*{f6kHWb~CR zo~S55hcIbOdpgVHsM#4;z{~fi=kKd>rUH2C+k0nplcWwB9d}Mmh1hTxSVUqmz}Qt+_bL}?GGn6@_2nCjA!UN3vDLe!}X zPRSZ?y&QeOgMGT4gS|a#fuYEnCHIwJ5VkjvAK4zfz&C0d=@k3n>9Z$Ko-lWS&yOGR zp_23eo<5828Ltq(m-xR7cYpqTX!z?F9dFU6)WR0Jcnbz07ADrktPb}TD}sH}!rB)v z?Te^EjDbfS+8g{puJ#u5MDuczpQ*L+J7w35m*Vf3^z>{VvWR^?q+N3u|G=vKgw8p9N!o?qd_>mTj*RGqR zW&WOWC(e^{X{26Tdxi$V|AlqSNihrfzKmCs`GU5Kw}p;9B1=RKZm$n*!3kDQ%vk_- z&=Vv`fb`>p!Z}k31AFIw(MKB2x3q+R;@|Xj{daTTP@R}fowMYjncc!0s4K7V)XHNR z8A6o8aLCBR^hL)h(Y3wAxT9my4noX(uqF#t$Hm!zx%ZvOvT&Z5POqQzG?-!pC`KTL;Px|I@#41=dO8R99y=E-U-Zyt(b<;7PbFL69J%G?dGb_04y0bh zwCq8%(UAK2(`QQJ)2A3%t_KhP{Ohm3UcQEE`IHiuhX$vnV`LASSi#q>*%vFVMPp+X zJ)MJNF^>YdkUvoG&{ROA0YQX@@6^IY)#0ge=Wx+HmW(p_VfU$s4%a! ztIG$n7cu;h!1l-hLJFwNQ*+Qa%vuC>Zf%XZfAIe5Zp8`}5y>tH8B7H4{p1vS*|!{A z5Y;LC+R2F+0m;X+Rkmc3$Zuh(qSH5T*X9!VBZl+{>Um4u-7U@DBUuqO_$oLMWlVTv zU3us`i(oS=<2{BxQFh2h7oznLTPeN}38GQNp@t<4|9`?~-?EU-xe!X5Ek<|w8fu%kHI@!l~8aJYvy2+vYNgp6kgW zch!AAiAOPd!l_MU8}UeG${D(zDyH|KFU|2#>i(8I?+4-ISACsIVz~`u!-26%UVi*G$RU-U=K9R(IlZ zLY(rDh6U6;1qC7p?RR-;BRRtQx@y*>hj|6*7UU7x3@OBGAoM5CY%ex_F%?{_w4X4e zyfNOHHjee6i?8+Fcr9o~{ah?iU}Sw4z+W&(uA;mFyaTJ5l|^`7$RHcEv$!}*e`lqR zhp{jEjEMk0(>ZZ&?%OwPR;YQ;wK`MIJvUFT+-H|t!}7tnRM%9%w_xOW^V?hWus~*P zgHLCHh1q%Y^9KfAzk1EQxQCA)Jb3We%a`woo5@A10YjTD zEqb$ZG3%7R0n)v}+beb2@CXUz8y~S^nz&|gHmFSXCU)O^QcXzW0=y&9qrNfs2=>Zj z9+Vd=4@Onrl2KA&XEf8cv4M@E_>OjGlk}i;ad2T;np=W{;|4QbGyw4SYThp5?-kj02#JO4h{{1^K zcYQJN1GCUC&XO{aWQkILT z)fL$zpBu?Swp54c@O*tVx|WpKg->5b{f*9EUE~M>;t$C zs@(GFs{}Vg+NO4fzeQe)iYu5Y)7IL139co2re3;yIhd9UTs>UGmm|ujU#U%OYDSiG zzQ;Tx_`RsmSPp_hoT**lf9PF_m0_KngQ7R&=_~=y;lkiZnVZ+q(bIkIoStoMt%#Nh zU9BKQ&m89^bvl;8qfZHcugoREJ0g?9KCpRYu<~}ukvNk&0iBMu_Ad1dvnJfYcqJ8KYvz6MMqG}pbnTu)LwFP2B;V>x1-&owq$Ym>ti zKiQV(YPG+G|EY7ZC5M?(w0MJCa3lPTzA|QDv*mh#8jo970cYplxhx_-)7`#g! zABlpuu3x*(bj~|>?%lt5QLUu*s6aM|q@Y<)!Ufyg>Iolq7$nhvO7Q8a4c?D|08bE* zkZsUPMRWp|v81%Kv$yx^)!Vo1J>Suhm?^tR)kb{4LyH&08%sV1y&E~b1^M;~Fk+g> zO`)+Lk=7y>3CrNA7G>EBY75O)Z`19kka=pz|aV)tk=d3qu&kJn`o-bMas8RH(D9%SNGu$@d zKIJ{kw)Ax*cbGhQtj{VkCVpzQR0ARf7Wb@lci+B!JvzbOym|Zf9a396yFm9;9AJqt z#iCHOD>@c`5g7<=oVT&T5&S&EjBX={={2np#NHrz*eNbH8lD-=muqL{>gvV@&JO)L zbZk01s;cGqL`^a<+I&D78kSQ5lT=yN*+G0p9_yVIVy5qPnYJzmALqzucz` zl3PIE^jEK5yntolp%)w!-;1YD9z9kN-(h)&2C=)QC!ku0Z5*1x28;r`6F7${*_vxtSl#o4sbwx4jO3pG|NN)of-Cur#WLF6=Z<1G&mp4O}&@y z+#we_qhqb?kmG{}3~w=5MdZ(S=c|Zc@qvpxPYAXz2UtiDDv*@_7m}y{Ww%Vd6OaWO z(A-!QHg=({y`r2vFGM8SSy5YYQFTo_+$e5N#^ddxQjf_g-g-pKjdkObk%K^uP%Z1m zDj-B^HkQ|(TS?RG?OE^7n=g=qKo{-iM4ZWW1DpHXvU`a$NTdOAm?JW$H<>U+_T^Vw zR(S!gLg(A#e-V+6R|G`ZCfdQ(@2WMu`%A5|4#+vxb ziaxcUJEX^o2kAa@AuTb9Mem1B31m`e+!71&kqf0U`Mzq90y_*pqI|yZ9@-c~3JNBOXnjH>X3t#9ZR&aa9Y;-0Y;b zva-55t7p_kSa$@kE%j(e@uG<6l4t%XtX-)w4#hV4jwh$d+ci=Jf-;Q9hrR7&e$<6} zky_x$e#1v{o>9v9fJTJp@m+D0@~f+KbM;U8IqV*63l~rWFI?`ihAzOK#M;^hCc&xa zU5Qw=U{|F2Lf0hT4ne0UBKfU(xg5Aqmq>Qms&RSRI{>;JS~wc14)C&pf|1b z4GjbH-u9VOh2C>s#Peq`&5z&W>jDTr@;Q9o;Q7J>8No+cq#x7Tn#^%lmKi@}4Di0x z2+0z7HpR>GKcQYg&H4Gx^>vzWdcUFXBVO*BsS%0Z@K8F{kpA8ty*ilfC?geu>0i2T zJ|G$78hEGL6Z-HL@p9-eXA`Sa{dveUaa`~STCtQ|CM|bkgO#kTl#7+V@Edh?Fe<3J zsO!^1#Om3d0&Iv?OtwBn3*a^Qv3EjNWMq<(M*#88EVA!QOCUh_2{Dnzi%E6ph)t}V zQm8hhLYX=@@3Oi1TsxUmTxPW3iEw2k?pzlXUk?`<)t4{zUcY|f0c&c-$ z=d?E73}({~9QLwdzE;@S;u6^+eHStn*b^0keio6Glz(5-05_`#C<6dyLk6?5ohl+0 z^kK@2?|`7?iap(pR*EjT_`;jQmWt7>yiCd?_K1uPc~E~rWN)8qEjLj+D_IGb%oFaN zp6>5|1KaYH8F$a0n=|kHf#h?%k^uDKM_(VE8{f*mz`D%ScO&M-)2~NE3XKBp^7XX5 zoKW5VRt!~0CtP|F=nEi4=%LVdykFIr|Cch))tH`XR5h-zDDC@R)+wi!OK>f^ zHho3B<@1PDfNw%ER8c@CBu0mas$@GfBb+`vdbo^?UvzScv%<4PEKr88#Cnc+RM*hk zoa_u)6|JyUV`mK{+6xoHRmA2r`+07Yd=rjzFxqXq~m;R3((aKog%Y~ljV!w8> z@XMNF;z3t;TZ=5F)}QBw1Bw0~_-S?-?C!qU+tC5~i;K=dFw1KDw0^v=I9MLP$dSgA zLW2I6))8%GG5IV^G}&k_AWO)5)gGByrUm-XC21!t;$wSz87G1$*m5v)3um#zq}HlR|KGNsyEs)R&XHU|gf9I6q%*K-N!4!|lO+d&U)HaRPsUV(NLyFNFAMWz;d9=u&ZHaahMp`(ZPBV1kt-r^?p_nH!&}WjvuUzTA$TI$MMSM zULaE{aCKnfJH&G0WqMYSi-0wV{M35Z-?bIz$r}3L;-H5Eh$AQ?r5$y?-#GIf(dFd9 z;umlk$>Nf?6i92!olB8+d|^z|_-7h37z@}%J#O>=8XMc&D=SY8Fr2uP z`?Z37`u27H^VX*6a^AaVlYJ)&rq5HxRJ-%%FJHcP?fUhOj<&W7^l?3Zo_he_$t-tv zbVUT)e5>wmis{HF?e0E*zNuLz!;FCp%gK35w$y9{6`SiK8g@5wh;9~86Qi!<$1GUL zg@WNR3?5(klx?u5?e2~}*Zu2!Vqv}!8J2|d64H|c)6&XRpf^vIOFw*&>r7ujeOxVp z&#vY}j<=|Oq)Ls@Z$j+xSlgrQ@GsdVJdhvpYE&<&qJf1&){-TdAGXMAg{qgUA z|MBM^fBg05AB1=RBB|=}<463CzrjSHvC)MEMvTm?t{Te$>9P0jsJY-A+t78g%b1Md z`p8@A>S(X7F`u8CgQnM7Nq7qJRQH+y;~K|k64;B^Ps*KgkF>h@ju%X-;bcwd;eE4VE!$md*RW@VP0B%(e@k8pO)<3jOj%V1hU?-zhQ zo0_g(y?(8;D?D9pJn3;WAnT&{bK>}n{E7kU>Z_{S+Pk})HY3x5|KPz9!SdNdDP9q= z$-Y>^GWF;>a9IZz!Pjogi+eXyn1Cxc!X6G0qlt>Xqi2JO>(w6LO zub>NgB$5Tk1L;IUjb7}+wV?KpN}~ki+V7|}_@18bJ5j}DuG6i#cYH2R7;I#t>dhWxI#r*pFSlj48r5~ec?-T#iG)GPW_sA~8rkUrehe;#=kCrJv zmu}y_dGr4LJ9nH^=bT&SP%*zU^3aKT;kbu=7KGBh6a;W`f{Gx93%pZN|0kcyv?^s# zDbSvso`Q1GvM?@q!TI?O#8b#uZY`xRsHWf3n($r=z2k_ijkbiLxY6sIn2d9=>}^E`2|# z^#pmHLBR8MwM_eJ=Syr)R{1j`R=2D z0SguxRbnT0jo7p}wy@A@Z*i5skhmuLk2kcFNo5CKwU~w+CzL$fy1F(lEpR5MrIXu; zM`s7!gke!pZp=io-&_X6Au=gyp$#=V(iGkA;gQ-QT5TtiLHYx!;F5%&ezuIt8>{JZ zf+xi_6&218SC`{`ZTHz#EZhy!PR1&j zZyS?M*Ffr6nahQ%3&}$2jQPj|9~pQB!-A8+h1rGWd-nXvlZTJ~{Ojc_+*>NeKOlA= zpZL+w0T-P zU-5+ORnC4!4ag+zlQ(s6AXh<5G@K73_QER^3;BHog=M8pO}3KKj=EaScrp67WYZw+ zoyb}|;){t5-Ly~Wq1S0Gr=_?g*d^91oxjN7Q@~Je>M*(~bHMZ5FzAH|ta; zB2%B91vEvSo}%aX4~&5t2{TU-TvI3pQ{K%k z&?_K%%=`9t#CZrF44I6h{WQ#eQ0FMKqE)3@p?2yY{t;bc?aowF!=_a#fg(Z_Wz>%7 zANz1ad2>i1bvVTDJSp2dAS8Swm@lHLUa~8&%f@$!Y^WiTj=-V~Aj~w?MMeOli;Ym5 z;-L`?M-=UM*3r}5Lp9Tdi$?j#>0`QzzB-R2J}#B#7c>`Gi;mXY)QrdyAVgt3j-#4Q zkHf#@_gy_M{j{>uT&6FepKyxe!&?l-WqW%ua^?^klpb;ece%7^hK_oSHzk)I-=Orc ze(5u2iYa>|U1YWb{4w@CX9dsRbN+r{2d~s1< z1p2_SbrEs#4ycYxadO!|;b0>j;ghJvXYq@D=k(8xk6m*aVs)9q#g>TX;1BZiQ#NFJ zx7Nh@R~c`Ybxv#TPY_yl6{!5=bZu2hfz4e*xR^ZX86r+@m?rX#5^srh$4mC-&QcAT zH?l`Yq1+QlFFL?uB9T3#v1V({Q0X{2HaaTL%>R+qr+^`5$QnA&Zyb)zH+_AmypKae zqhlZ5I~}O6PZpX2xBiLo&tKQqGjgX1K#pVr^7FIkjqWZ1FJGb8+6yIYk}r_DM= zBuvX(M^9vUK@M)LC3;WPEIWadXuK5dXI6P3osva_;&Z({bew7~p*RFkPgq7Vq^}+| z(JBH@NLoarrBeQsJuP)u>(F@W5#>~L(9j^eA=V?9g#lIp5$KAXxCXNi&`vxzRgJ;; z@J#$Q8hXPXGhlnUeY0_^+An7+YM8;9Ib_dKMY3FBlgqRMg~KFev=y zvcIo)D0L`T$ic%7m_e`tzc)t@-{Fy&nQz247-k1eVy8~(ZYMqt;KV-F-@u!FD(3>~ z1M9thO;XDPeO>Zdo|0C@Pl`jL1EP-+IW3eQuyvv* zFNid%zcj*B-Gh{@c2?H1bp3TnIfHoSu#(CgXKhbj{2M(lis}5Iw1t8~Nd<7zSYp_d z&SFXf{0`mQT9PkZ$y6XC>jHdvvBV+}89?gOq-J`JGZVzR%%E|+btJg0-O1;XLJH~< z{A&cXO50M61f0OR?_!~keGJPXgYXv?;~{D2(lZBmn*a{v49zx~(UJGZD#^>@6K#`bd;=>&C=OHRSBMiKb8mflO(uV20D z&$_`pKT*s0(~m#?c>n$n4Dr2n>*B>;ez&(5R*np)o*uLLywDlvjEDB}&rY=Ars&h> zT&%V>BBT%&H*Ws+U%&tJpTGb2KY#!IzkmPjA3yzk|K6?JH?Ci~a`8ep`0B=u>wMqa zd;UCGooxmAL^bj0mRPaK)*~B|mWbnH_S+t@Gb-&=nNn=7&zm>&RpuHVo@TcF!U9W7 z9F+WXvO<=ZV7MZy(EQK6y)|ke_P(#I+Sf~svA({%1Oz8PQao-OJNT~g0=qyC7 z3-OqWCQ$sBR3PBKV?Mug3Fc(iFvsTX-HQ3_Q^M>V_~LRh zi9P4`0^*QImAySGP^5H_QW`Em^>-szy_dj4PDo)*Fu+{;)B*b7Xq7l+v9ZBeoF}=k zu%Uta>`vlkm#@_3PKJUn7oY97Acn#J}IYckeEOeL=3pE(J~vv??U@ z95!1C03cAeEsuJ7B0F4!j>UUtJ;mW_iNq$0>(wK$BjzL%OC>)HGr^x@FRagHMi+<= zMMkDd8D5YckPmtdG;u#%SGc&zYd3D*zIFHBom&?!$iH~?01qx*zI*5XedkK(E2r~} z$CSaS)|$%BH#f6#ydRbo2ItC^YgY-D&=!+7h##HoJBHn`UOahM_k|0+7hAwB#n@`i zlPItVM#H=H7WHWnxmXQ2L{O55&tA>-^~S!%mDUg0LqN@nq>(l(H|?=mp;OT0q$d~o zqAcU}8;mXH_7RtS^0VjG=gPOB@*Y2a@)|t{^y{~e#=rnfA01Cen9k!I1Y^f%`rDi# z&b7?A+(U+SO{Z>;e>aez47}p`WjlEhF7Z5xib9--dB^@+gUHE_crNzDiJ@||2s@6e(G3w7;E>o_HCdXj zYI&JwM;vDEKbdkhR>K%h@B}9xDKe^fB0Zes<=E3vneuU_E6lsA!1j9&SY0mOT#`Gz zdbEuwmuJL3lqxWnr?`k?8lHz8GWZW$EOOQ+i=62RS0kJ1m7iziG>iT-T&$mbi&CPO z7I>L`AV-;O@b&ehBZP*__ds+QE;_95A;vdZ;P{H)XeJ*W(l$YTP6{*oii-m6n|9Sa5B8QG5BhT1Cj5>4mHF0ivUHSBcC6XpB6?y_>mArZ=9noduw&GNkA-m)t!6t*(XSe!GqKRgkM z2~LlO=RhhNeuiGrN*fYJ_bFqALQs}M$A*}TiS(>4Cj>8~qqW7p@3Pds_w+?CO{*5N z$32fZR zPy71RgTt^XFn~!k=aAKKiW7t(br}Lk4`!R1TQgXE7rs_OfvA>4&HlJi++c{JG*mtl zCe4b=nQ1o%`?KO07>boFSPrvyHe#X{O2AtB60uvBleIF9+}$O*0!cbbHXah)rx6N z-8Ti$H>y^(&ro)@GkIp|%rXbUlJN({r8rb%f&@7hHM^vDG6RM@Wjf}SmZ*YJzX+L3 z&RM*La1g{wL?-DHV)jLxU6zM>OX}}@)wI&Sy4jm4yUe+HI!|&>)s?6+1XwE1^^}JQ*`LexWpN#WVKT~lsl!K2&2BY ziZnNu>KrPzw&7SfjA&0~4wrTUXl#wzFr`UY+o7R>fe%~*14`s4I;9!irI3iPgG^nW zoSK@7ek@FBo}QYS{bJU3&ysa~Z>5F?KdWIv!|lj|TbDJjDT$N%?b|vTRN*5AH>S5oTp7`hd{Z*lF&co&@%u3Q__2wGSHRnuDCG zbG1hD>sF)lmRhQ+)KY5mu$|h^c!*W?Mfe~(quw;DDyHV5omn&1>P*g+bsrq!IL7Es z7+o@Z*tLyKCX!$k*jiM(aZ~s`vslQyTR{6wO;6DKYMxH6T83p}C^%M2vvjZj^vTxs zcp7|1{S^|&^muB9XYJwlk)IC?DR@ZUDB5ylbZs?{nPj{)_*gq?$D-Wa>Kfi;tFCNt z^tzvBrps8duIJCUwy3|%-KE~2n5R7*(!Lt$74xCL#ULn&l=;sAT0cB0F0Q9*H{L77 zv(8QPJ^LsH1Z!!vwM@(+D_3j;mM=6j1B?O?WL0TVlLbeB`eNP0B|_(9eIlv>^h4U% z;PXA#uA9ntsh9R>7kavzTf|)4ZD6m83QktsT984&nY6~A!>%BI3)c64rK+Efnd`nH z`xKT9oWUK62b8FWx_l<)=~cD2ce5%Nds|!V*q-o;JW4Ps=HZDfwFyL{>+4EO_78SI zT3Q0%ygJP%j9g_Zo66D@>#(8eDU{T;(n6wi#i{8g$(pa87!y$P|-yjp5 zBjDiSB>7Y+_KA@Rpn{v?25C zpweyB;6U7V5>D$k?pC)s$S}JNm)5@7!SuMg#NZ zIX$IJxTZ}?{#E!k^yH9_#Zm;4`TEtK#TU#hjY=jgdLbQ?v;g|#${vWI=K2jiM)JCu zLu_6=F%DQebv8SrfSO%@7oP)7iLKC6uBvKly?5{K-8SmW)qwH}u0{P#Gh@iRaiS=C z&!54rUjd!5#LU`ZW(%{3+2f)*IAyNkfJx1AHRjcQVW#*Qo$ah>b#YwNRD)%|C0>JP z_={vGXibb*bAWKYKj2*0yZA&tA7Y5?IWeB>W#T!;R?g{!mBMh^EF_uA3u0}AjA8tA zBIu;^_3PA>O!HHn+h@<7y`ulJ$pjxh7*ivM8D|9Y1r`fyJ%R!pTU|997xB7j43Z~@ zHKxiPG$}`9MmBScTVa0jE!0hwRm?X+cd!IJLr&rwP zLd$whU=iagXbH8g?3uHPvSIAZ!@MABh=zk1t5PD#*kgr|IoWc0gdlM>HSSkZyRFSUO&%i}0*knRCH=GM!Xk<0C z@9CKy7!nFO85qj=T9BFgu|_vWhCd9wc=7PzqrZGYEtAyul3r*YZ{EB?6Pj25`t`uz z>@2GC(;_*f%ioUZ&0>X+Ys$q(|3^LAD!T8;)F%m&yTUdD*Y&ETE#ynk9WW?nj5?jk z9U@9mJ7A(X!UYgfz?bV;(A^|>Y@>A4PSc|T*^L8-7d(PMi2ig#L48&!su|5)%g;lt z*#8*IOqW?ASwU{IGhn0xaalVn>S}?LL<_;S)KjzOE+RU3t(y{}7CfNbcsmz7at5!31rMK%U=*Rg3v13W8#Bjklj#to-U&pmcNI|c2ROP*26{K?MV#D@}hU3!~qYZ+dP9kz!*;s4Nojkr7;7<7cqU7}u9}D=OAs`5~pR?mFBTz?laY$CocIF}YNxL(Nx7CYL@;ya)ZYU{x}b`rK(d0 z(iJ$rFqcW)0iH(X=j2)6vPq>G8GQ83{A5g*%Ar}ZS*E8Iy-AJ%;%oK-H;4Jj(}4k} zJx@)~Oi$0u@R*%5Up_q+IhK~_QHeJzCMjUPu~0!9$(pEz(D6z7LWI0f;=gVzyY`_6 z@Qjlr@tG_ufHQkJm<**)?Z9!)lxMMQng(N^d+#m?pN|Lhol)1j`^=}^+t1B(csD0l zCQt3_6y9*m_+;it4fS*0NZ1QB4w_3A63ruXuQCyb(MP2gD1jZWHE)@GO^4O9#j3>h z@)e2I$;6ojn@Es+;5q!-22nG>_u5v>5fg%{<&V^cpswsJnJJe;TXvCEQ>_*@sE`~o zv5IG?tQl?4M{DJ1Z*OeW?*IZ;mlP)+Svz^kPP4yi=om!d7>P5RFozxO%8O;d2P~WV zp9M~9j6os$M4ker7ZCxt1+$b{NS-_7gBhjS*>S3mZiqQ}&Xus@mAfo3^g1|Dn(;39 zw`a%HYaSk1H(tWJS|h9G{qmKWLL74db3%ng@G^UAK!iAAf2X#wX%PfGF>>1Scd zKOgjh>#i3U`9n-A5P4HU4Ms7T+n7A6~mwWwEXYokk= z>K*+q*3|PzpcU=0YEG$|TjswewtZZ(r_9*1d z>jGe)V`C4fZJFxwbs5N*{*Aznv>I`=W+tt$2(N15VYc7nD0CX{Nwku4U2~#PN!2zTq?ouT3;XY2dU5P?rMXF3XSZUg?&`fN}y})MWAxtIYhwl zD?rTjeFJk_U4&weIAB~0aeO? z*W26OLwZ4nJ(-nix>MjOA;;WL)EOV*6ocgxcUlW$4hzjYzkH#RIXoyU!Hty>Ka?N2 zLAHZ>jSjeOKjwr{J=x6!}55Z0{N5!bw}wNYwr_M^%^TH>sj2i14NzZIeIZu z&&%&x$%(&){gEs2xVYG03f&S?@nvauPe0fl?VhHBC?e~tD{PSsV(eJ6<0I`U>Js?p z>e-2sJEW+xyW}M<&ZF|Pu#kC&>i1RTHv$310hq9^3@&~7{(YaiL-5en8uL*XKF`jr z;31%@=v|F8@{5K{76|LY^MzJhTomG}e@g{#e?Od_xqBRV#TaSl z!PvP?R&jkjJKOsb`i2{KgL*4bAnOg?XXdT^h1Eoj4a^Uy$NGVgocRL`VJ&D*&X;x) zG2=#m)he7+6##~VK?kvso2$jGWOhZ;rHSXFHj*R70g8*l!@(y}3$x4V^Q9&(xJoq* zMudDiZ4*zk;iwrSQPllm)~3lEdY4U2__)y%w56q|tGnkm#gpwF*XzpYBO!_v$Rc%! zo!XSs#}uIs_~4anZ!Hp2b32Jky(Vkw#Il?uXi`=^F~#ONmXusv>{D$?m1|&8VTab$ zxWD)%<+=DR5oLLKVZ z$p=rLy?OmEBYEa74{;20jE_%GeO-1QuQ!2Cn8u7?PT8OMhfXN7qjiR7gDX0ww|oR} z(A{hzRGs}Z<63cwaf^B6kPnZ>K|Ll$3ss4tR3_A)2uXwfpV<4xb$vr8jh@qIe|dR& z20TirI;e6wxq?NxIN5v59VXAq-QJehxuD}lBZ*pVZNL8dza0PbKR!9=Isfagzx?v+ zum9qB@IZ4*e*)}nJJAi#qJiEmhlesu>6n`X!^})Fcav>ymtr*@`<`Wt1Pt{E_i255 z$9$>k>aNav9A*nz``B~4udj&;<9Z4*>{ozGK^hs*lB#fu=GtnnnSJAWUL*4XPZ2TK zuji>)`&0?}{Hf-e2V+J!e(JV6geyxKzp_m#Pyy|l8M4;BnEBSlI*tjim zUlQ624M!IgdVVh~%*@Qwl#~)50ug{e{?QL_Up=pn8y-*8Xv+D;$#JsGWv1g&CVM8% z#(nk$&IVPtsO!AAFf~ntcYJgVbIfBbriBj^>3#O>5%s$Ae2d;syZ*`Bpqq`FYAbrPYAwG5f{0Or{w|paz zrDbB20LuI&AUT)=Jvg7iAb|B?=(#{w{lsV^nHL9#Mh&f)a{qd$^Yru7Z{k52TP>QJ zH~=Uil9uK^^7-x}4|newO|8eDz>ke+XgEF@-0r>72AN@>m9^!p3gSZROm8+iU~PW9 zBgz(U6K*k{+k7{B0n`keCb$lv4RV|3nQV`@~2KO)2B#k;rW+*^G| zMi9zYQrnDcRubvtIK+JDz5N!5 z&?t;x1~-j|IJAMzML68t^f2D$rg4A}L4DcS*&H}+_AK{XTp(|cf&>_-(>yEg9if*U zc`58=P%7JN#PjK9DXPYP%Ch(y-Z7Clc)~^G07Zm`HA;4L?4AP1ayQIRAls5(5=WX; zzsn>dqiw)Qn2-&!v=@Tco0xd}_9d-F>l#b~W z$6*Wuedvey%5E4UuzIQ`h!02qPb(AkpW`?D&BC*DKo)P!N2!?Lp_ab^Cde(cmYEf% zcF#2vbt&0eXM8f|mDgRDPgc^JZ{4;DOYd!t9+2Jb+Zx)=6vOI}^}y2Ofwr`GLO1KH ztMZv4v9~WnpDJmLTBX%sEp}M+J-x-?U-`sM%-qGZGrDh(t%6jggKQN{EOi^HD=Aol zBlx^X1y?Vo&fq%LSIJK^6B85b3gg+tGK(|?FIr0+SME5|vz;Vtv_G3eO=&#~ehY{Z z!HylR9%m0=hV;3`-TCw+xXi^hCa8?=?Qd@_Id=^}1}Drxb>n9kK9IyWR(j>z#-y}--;TO^ z@+88$6cW98J3c-)H$7`U)#AdJL*7dJ;?A{C9*kFx(xil_B~VVmnF_6Gg|H9?I18WB&v2-^Trq0f1vqw`K*Zy!E)_%HgQ*bVad%gV!|1doiRf_WAy zc*m|@p%RBq4=LAy*bY4qtu5rtLcRmZbEe#OY?CL4TFlA6;!Gg9colq%NCJ8BQqG|@oc)<#_PWI++KkY(3z*hinu zLXoGMT(l=e!^>hL%E_n33Q?7o^q%_3x)k!8rsw8nc?538{q!NSU09GSH;%Egq6W`} zjo^~N38keh7)$Ary2h?;X{oEEt(5^xeF}u7%%zKbi}CTdZ=Z2Ip##C=zwNhz#eIsL zX?8>6;scJxQ}S1yJ+o5i_3KZc-0_jMu&MJ5P#~(8mnVMZnv##TM$c_=qNVBN5|qBJp^yC$|W7}A*2Rqa}?xFVtofEQO#Mlarh%MBCkyT#Xb zdUZ`w^xp1I^z~z=8`+RBQ{m^bNs`@lT|j2J#YN0DMI_Q4s;%XOO)r3sY-#S{=cB-pTd-$(jscY&z=8KFqFndk2mv(bW$*J4`)==B7jYsN1 zsIqWwgsh9BV|luyfz)j~C^?)_EG-PU6UB|v?qV;|*L%j0G#lP0j$}K0orFH!0lB$E ze27umWi+%NmR%sp>*85Q_5k^LMnu${@_sVKqa(6>ujz*I^5xLbN zWT1EV%wyc)5VIijSSq8eTHYcumqbv3@$fj>==uEvoxUGDxPSivJ%R3-#BvvHND^N+ zvIn`>5sg~#p03q0ON$MEa=JnMe2p%!GFtzA${u+5y``m<6`T<)GG{{eTmBV#OM>It zm}T10iJyy+zth*)bp1NEwz;*%eG1l~)1Q(D=0oy2JDXd`*(+wQhT4zo#eN+hpzTHb zYQYdGC@FsEPoonGb-_fWMd4I}R#~r%yc!e%WU9Yqo&hfAezLnUFR0yoqD0j5tSgb? zC*^Z}{WBIf#T<;M>Bn2WVWSpsER)2(EpPAWXY%IkKt&a^iwZeB$j+fNEka=a8*CZJ z8J;@Y<hZBA)Zi4A}%o;smg69O+Z`Fu@a}qi^2WZ=RM^@GMqJK8}u5@A}Ofot`$x z=IpJ{pXX+mzU=KrtuSkWHpwn5gjeQZEy>!09ciHIj!XghDVfsB%1c4xbBLWRl`>ac zl#>fmfa;BmH zv9C2e$OYDxJ&10N=xz8U1%=32FypdB#_F8iXRJ+SjiHK|wq8TH=B!pFG3g?(E$k#a zN8|0wq0h*qlj#N@ge?^#V0I1hX%=FZLme|^S#2z>yV@m|?@%Y4o+KRVM=NK)e@9c1 zVYafWy)|G@zyL0TIPTXd*P%{Ai))lH=|2>qE;!E%W;*j8!FT^7-&zGK39^_+OMH^M zhpvR1SR?Yml&huB3zL)YINrX))z#OvH0{l^XF;f7c=-&ug6skMo<7wFe)W2EbaILu zF4*L?HMAdj;cgISHUj1%3n-=th?!g$f(rx~ta52=C@2tTqJx4N=N_>^j`2vm677_J z362u?j$K37ZiLHT%B)&($+a3fl40s_L=_0Yg+^AnFa}@`&sgs8cCXTv(T?-nDPH63 z?EB_788&pZnUWiH0pmyE^J;m4V`$5qT(Z|yv9KW8)5L_dw2-=t4}$AK0&~hR#Wx_o z6z>C$@y2y0-HQpBlt+e&5J8Ni|9o*SehRNl?+UetF$2HvALQl$Oez76!7)2|!K_qe zl*~BtRg01TQ(f0G+Z@$ndVdB!PFgLkN3uVqOPSv5|KAcz^$%!O`qNU=CHVP z>{Nde-Q}$Sc8#So%!e^`iau%b&C!u%X$i-Ly72iq=KJ;cGiPIRQpK0y_(q0Dw6BCU zaCrHWe_@5mnjab(_%J=qEyQ)!x!c}eS(S6#VsaY(S4fa}+gvAez^G~Jw5Nzv zXD7sU_wAmBh8m)P#JQ7$hBw{VK$>VcBM3fq!!_69P7AeACc3Zb>A6__;>P9Pv!=?8 z!lcb~?lSY9suIs<%~KH_x17=?Yaq(O>QGT6mlf0j?paRGE(dZ$Wy}7)oU)Uy^f-{2 zMXLei?1Spl0>|<7>&}ioAE(D55;{4-3qd1b*y&b?$Cyjlnp_673wGJbKs!v_p8tgL ztKLixkwit`5OLHg|3GZfy_r&!x3|MX&h^R|C{Y#xGp9vX7Nsw_@r{~#*lurtQ_ zN?`Eah!SOH5D>q-d9B9&A)bq1!zUplR;DOZ;jRBKqwK5#_h$6;AVY<{n`{-*@brQ=bWhvCG$!v?p66K&NTB1Tx(1# ziZOX&UZHlksJN=qydm=of=?nL(chq#UrDNmNm&}&B4kyfMqlu7Wo3RoeiO(PS?fPa3^z7L|@biaTdwrCR2<18(q=D^^3*X-d@0OCXI?js;Q~*iFa=a zn&H`MY5#eWy!Y4Nj~@R0`1y0DoRUkbkNf)d$D!$&X;L&kFNzP~EZJl*XXqL;yJeU4 z8~ors){pc4>dRG=-FJAHI0<8VYE05tgCmTn;Esnl55vR<33 zyQCeJCFRIn%s)Vm62|?wR>}-5|4y%0)=TzFQ?4D>*ASuGnNe)2WEg)S|Fzmav+7jb z4|Hr(V|VC1Yxr-`8_G#P#`X{z!7MvU|4vNfD~2HoR>g@Tb2++qa*fY?&M+7qyeY{4u2mB4Q_BdL=g~Zs}>8V+Jy;knWtJm8%sp^tZR*97{ zrw%mFVSNCdkcs5`PD(y@7jWz*)kat`6wWJ*vp%d|!s+Q{RdpVnTlJLr0S|4dcu0$@ zOK-Klm1tf!x|bs>>xg_sJxq6ys*TrYxs?tw-00^l)0j^S=co)5@`we2`?VV$1Ojv`TbY%HSz*7*r7>kCw6eHK8AF63kkQz18+eC-!^(An z@3`&Fon7r%T)s#sud%$0D29Oyu1f8e#S~LJVam8?eBDdG*G?>f&o3oT9WLWxE`L3f zs&RKCm#3#kj{}}3vKnQW&0cN`Zy1wIUyNqv2{Q{a|MbNBt7D5Wv2j$i-g~dvt5;e= zAt5HL4>$kpw6c<1iI|8EUjR1F?A(xFqBCp&d1WP^mlJgIqSRLfj#ygauH$`5eN2pB zPIX@7z+;Grb$I#Fxxl&EilciumnhBEt9m%v_T;5m_h8kHIR$E05Giqwg{9HaadPvN zFDKLk^vJS$^5oAy|NPHyzmrP!$8W!rPxZ%t{(MMM3vsr;|MQ=}{(Ao&!#p}MF)=

?yWeSNc7oKI0u2x359)FQ{43?<-UolCM{Ku-!h(}iBQmM0jtrcSn5CA9{%vf0 z`h=nZhb?@L1e0aFT+&nI)|fBA4nmHQFeO$W-iT?zRjpH4CC=1{C>g{AQ_WO2!`eKO zuU}{9W~L{m#zu#R5gh0{rWw2-YD>mI6h!`Ir|-Z1dK3p7%irL>=dWHqdHUk{@JO^Q zfQ^}){QMazoVXw^mD-N%MSJGp$OGDlEL+LS-A#CpYOk8a6O%_n!i32zTEXK(?$jAZ zmj9!i(23qMrBA%fukf3@+j;qo(HThVeLZ5C>|n`y9>@kCkz&?WgsCP&{_5!w3o~)G z;N;i}%y=(eQ_CV&G4qrK;~^6u?7YG~Dk-U|YP!{eXK7MZ^R2!+_vshZiz@8vQyq43 z5QtzKK^k$8U+UYDC8$Q#1LR@=+Ey@atc#DdFyEGSuOq05>w)J>wvw~g3kuW-#&e8< zoA1NzqZSkvrtT(YotO~XPwm*D*TGwNb=#W=-iO^I_^?Z~8Edf;VH@oJ;x%d+^>+90 zGmj3HX1W*gJFTXWX5@com*J^`emA93cZJh zRUReyCMTySC-7`PW}ZWSPDPUZ!sx{0@-pwa?BDs9E34Y~KnC0hd=B;nHP;#uGy?Yy5oyfVguM`y>)dKghOpmj%HscP1Ug69wh*Webm z((1WO#Kk5)0d#C%WK~sSbAhO_OUZ20>Y5>FC#>Z2>BSY%AVvRuWM)iZ!4J?UCxaqYVhl?zx?v!kN59)bs0gY&lYyL4c5M=hg(Y`)4jWBnY*nm ziD$cULx#(&&IWYet!4tQeRuEjOLrVO+Cvv&^$&Q7qpPd6jY^waeSJS5Ha~Iv>YM#~ zr!O)r+tC=L9d~v2-iAH7eeWJWGCz-Oy?>FdM`N)EU7fYe7b}AipoR%;at&OhSF6{( z2M4d?1F1x8$lN^O4&K@v3@ilf{I`|8-PKjn23ZO7jLdI zX9HLy>TGT6?$V!a?;xgzl()CB1sWT$vKm^HhxyFZMVK)s|53}8R#n<3*6g9gS};G{ z40I$dz*Oi81eh7Mdb}z_J3{U=%~saGfX-_L1)4)R*(10UqZveK)PL+?oE=PVr)=Y$ zJH5AW-MWE(Hq#Ve2#*Bq+1t|{d9B2lSvg@39I-r5VuEt%ti{!VOl0;rW1s>eqmqM< z(CAya*W5}y1Nx#3A1C=h=4qADHHeqwg-e;57CxG|65XlSV%}a^SswZH+HjGLeThX~ z%pgJMSutuJ*(MyQ`*vvwe}#&n>FM!_j~{LBJvKHoJ2~;L->8{L?;*9}6o>0o008|C zwHbdsw-YYMTMs3YV{mY2=>7WvXHO1IO~E4oN%f~UHjJaw?0`d+ETgi^A%ApC*L$|^ z@*4Xd^$Z4c{Q+EPX7Rp}0d%!y&CYJ1>(jGi{McfUH{Bcy+|x&gE2~&H9%_LV!nTHB`MN$u18RGqFqH;}2lKGXGPw)zxH7p$ za#>^J3WjWApAkn+t2rgTu%LI|JAyPr-i1dO19Kd|jS$ z!XLac`Fq39pX+11c=5jf1Bq2b^yC}jNYM>a4ZwrrsBLo0&a&UG6hs z6flE}HGs)=TT@SFPeVOwx?IXxw8cTLOCyU?f3upob>c()qdX)KyvIQlo&3ivdg>5x z;s^W$S3z(e*ba6D7=1$Zj|)f^5o(r$8WHvxcQVa>Do+&KqZAJve&=oMqOF8A0Au{2@3p^_-b0a`*0Vxx^HNNBUy}Ac;uh zE;5@;G&@`0{e+k;P@a`^4xDxrnMj>Uqnmj(aq+Wr?d@eY zjFSb?!CbYv992Zlg^@@d@#<DhW&5^T?qXJ z@ty>wHMwdMSv+|is(g`D8C8)hkX`V||4PA+6%{w&8N97>W3|^Rs)(?IOHa=>Hc4fn zIzzkyyhs(gA#&snyawE%@t!<3hVklk|GW3}o4=edKKKEz1n-0`0KH>+5IPOiV1FHb zjFZzMx~0TfqzpYwZ4DEL^#v=aw#|i)xAUiY>xD%Lk?1qQO@W$Kc*>LyzT~j-R$d|t zHbMEYiHD6~u}v6)fp76>3Nl#FyT*j39Sf zmj0i^`*CsM>)9Ks1$L;Xr;CU`5H))7ru;ka=zn7fm2ezRYGH12^s?LBAXYT<2?AAq z*}Iprsj#*Qm)o0&d|RXNB_(nJH{h2_=srV6dwHgZu+~ql9NDa$U2Py|eGm1tUBat_ zvridh=2uIJn*nWRtel(I^$flIP;BZLd33In9yM4wtgDBd0XhcT?m`*NfFVG9#a?Sv0TLd4XBqB3Vypo`)GAqH*&1>hxi_*{y^W+KA_y%SC^!D@<^({yW`G`60DMWN(9+zkHdU zo#R2s2vfE2SyXJrmTscGL$nF%233X82?UmTy`6L{?*X&5x7ATIL}Z1Drq+o!HzyoR zthbu8WUXLoqvD;3z+?mC*g~*T0EX70w+mA1UMwXFj`C-rL&&sM~Z>WQjcXBroQDhZRPxOcU!srXRw(0MWf0 zct7}Ykcp;4SlmG-Y|s+u^&4>B+o+fXDgqZj49?A~l&PHAP)e>`H8c4JJq=So2RSNk zpp{pVwFX7jB{oKL5o>e9R#CHH-`9$Y+}yYvWQLRB;v#B+C=q`n&u4w@fUGn&G)6gH zjoMQ^rMKg>4i7oj;P^23tTFK_=_oOfyVbp|q^X}hPcd^Tbu%Z&=nHhKPk%?fSI;3? zO~=2wYNq)`lvI#jX6I*|dX|n17_ybZ-6Nu<6&8bmK=`Ee8(A_+i?{Qg`1AxQ~})o?+GHqjqAw1n~-(3aTc>DkT>vbCTE__DMBQDCW` zJJpDq8gOFb4LMZLo{>ZVpKN{5)2Fh|j~@N`@Z~FNa*1T1;R5YVO-)TspeDh5i}upn zqVN1=#6WACm}RXbvnk4)AnV!}!Siw7;g6hlNxuX(Q_3((w~WgbZGgKt(Ac|7<8g{f zY0M!nW$Kwed#dy`>R-(Yi*^tU*?$m^Q3AIy*L~HMAe$5xQol0dL>sOf~KJp_SzcH(WdKn*i@{Nyl!~O8V@5nI^eWX+0 zqrd3T{C;{0gGM@w-DjNKgerwq=D%Jj}% zrUIIr%Z$e5eg!XsG}Xh_)Yn6z@YFwY!pBcP{_w-yd)=MwsO;9ZTTK;}`rl$1a6rT{ zKms1HrMh{_`PSA7KDYWL$(|1PHh2K;0c)N-$&?FF#uR|0$EX--@9etWLv}HU7H*2F z5+ivxtT$kWrS?-sh{Zs|8P0)$thsWPlDOceLa)QXi&X_%50Ukc1~x_r24Dxd?yNMB06(HMH($R?u7K)XE1lsk=jLYM9~T$o7}S?3J6nAj zRHPqxexV;hZZ4S*(TP$I$e2;ebEHNbRx(8x!#;4IM0mk3$L-&56mcUtR8#6M6}%lmQo zW@rWULUB=Uo{>Hv5i3l!&_CH-_}X+JMo$RO(aLy_$VeVtVsmrI(GSm2CQr{%=}}3B zX^x(T%(f}BPhS#SKUP(ua`ctB*<2TlioOtkrksIgjX{gm%x62G8v*SioX10Vd~;dI zq5?*w$xyPL$O2fTCMxZXBD$hC*(I%XAMGZ>CO-guU^fM7)`?tTgw@9>|4SZ7FvpHz zzj(*(Lq%TrzBW>*rYE735JV*1SOP_(#$c-0Gwfa@*j@tj>7X{k9eoU|gW?QNEg_!x zKQ9_ppiFEq#$+XMFg9omuteUjta!wIkWbj*g~gOdHF23Kk9YC$gG%3;f4+{_tD`4)7QHG|r->-myxQkk8-&g>Glzat^Ycw>U+s!@uROojJ>$&uGG z&W9C-9Xp`EacK#?O^b4K5*I>=q>&+f+YXejM>~0@gmO|gDY-4Zz1`gn4Z=Bfsdyoe zDFy0bJb>^7)LLK``+X{2ny&dhv7nc>L3fB$}9aCC(7jHyY$9T8sKT)wheV|s3GV)A|eyLaz!I0gqs@N!Ke z_3UKljh*Oba(YyH>*tD5WdK15WN{Z4HPBZ?og<5%kbiWN3hefe?$-bydtmztCyh zMW#39BLh#WxUb)6Z|_6yQbyISCi9pw?3t%UM{RvXeG~5!H&ow%Y0jR<);69s37AVu za#7#E8@-8J;kM&|RYLADlVFE@bT(6udfVHzx11Gu1fGZI*Vu@CU?Z_gWQtmcKu!+Y z5OhoTdz{9TbSMSxR@`vkAY<2%WvZp$-FEQE;DedB&Gdl~PQxO!ghnRB2xlou+x3Ygnl=RH%)yWwDz z^~34J!Y-hDCE}j?Ftesjw|Z~)l3Qa9mI#?k$KYUU#Tod_oC}AQxk{er!;@}e=t2WZPoD1+{F0kDA5gE-idK*C?}`p=bZJDpXT~-vGx{A*0Lb< zf|v>|5!gqY$ZB9RjS!{%V+xX%mRpvb+&~*q&&ry~tH}7_U%-`Agk7hfI#t&8$`I4x zlW_#16y()9Z`KtygGm48A`2IcHPxfq4qsRMA6h7*k=0${yk!>^Iq`yBg4O+5m*c_x z2l~2weO+Dc95T&FTL>PV3ZmyY+eeO&ZeicInAtf$Z%H-T2A@Cag(al3OdWr!Ss}d^ z2!i1<2BsHD4M|gzGTheI79IFHJ6hrt!?eZTRDr)&Rfm)+Cu|# zBp-+catkTnyl(1DYEjFj1;#4w*ndA6W;q3Ucd;eDo2r|?OuK{Ei&w}e8mwOwJrOWv zD1{TdKgB-6%QYuKylB1NCec#0{o;aB_UY5NZ<-`Bwdg<3udRvlSd@9}C@AmCl6@5P ze$6E`^o`K3Eq{ybDO}m1AzBqMt63=>9SzSH=l8=P9`DDOFBDGl?V)5OWCjPRBHBE_ zo5tSK0bC~xD-=6Krd)rwC^G!Y%NiP8J<>}$Z{C#OQz-on^CrWf#hujJ$bO_4S01@# zR-Dkqg&h?EuZX#kiyRg?EG=rpB^2$O^(?Ee&F6b~GW+{6MFw!gdg4hSs^qSsB&pA^ zCY;-=z%{4C?r7~D5AOZ&BeC84SVWYmJ?C`{3t;%zD9{z^Xl{lMkG!O=_2K&Z0>Pg} z_$;&3zI-wJBUO2**Yxhou?~9voC7YHuiVshk4b&VAAWrDDdbJA)0@>@8x=0$7klo? zn!J2PrI~g{#4?Bm$x#C-eOdIktRk|Anl!~888v6y-lJI;YJDyOtn6!%>*QY`ts${o z7pgdGpZh2irDZupiEnH|@MWSjI1MXKbj08QFsuX=+h#s|qh1%3=aOK3sg?2C@v`u< zqh}O|gWP3)n(E~QOS)Y0Gn<y3E#08pmY7rB!OhV6vz7{IRX(G0bp&ty|8|mC0T5~uh$V$E@4xo zZL|IXXG1y=S5%L~d#a}*`u4ju*BSv}D4>S=8&$kAxgpAZ*yhA%hkwmG$vW#vmzG_< z0@F`#PWB%{!9C5B=kR@aBVBaf{+ymnMt8KtBBA4BIG9rJyP8J;sS3idg}2Nfx;w*P zuDRApq%Zmc_H=gI=MYT8QtRIu#Z2CAIQdjS*-S}!#14{eba0>s{=O+zR;r*l;5f|4 z!z)_fMGGMaE;u5AZ()2W@~6y2wWp9ML5DEwf(yiFvxp-cLs2YHGkKCCbAF6`nUQLC zQf`j#h&;pOtr7ZAR*g6U^*FQ+nh0tm3-xayG~qzr3CcLJV{16S6HncJCsE!D&m*?MSzQF%2<>4 z8$Xgs+5DsiMT0}9Jp&3M7u-b-`F{TSr=NcQg)jc~uV2x4_wU@1Gm^WA+Z&lv z)DF`1AHBw*VI{R}c79%6x3t9aaV%n6v@Vh2=UH%*)ajKKZF0lCU8?*pLOk$#{Qq`e zUvKX{I-HvOrjM(2C4Sr9e9OQ}b93EwI(%ly%z}a8Dz!dBskFslmOE$aUA zb&MmStSNF0XJ_rYy#kl1CQPc&qDlOF-FcAX@k!JNBa@m4V{ftw>O~BN*%+K6QE)Il z$xp~XxSm%dTNUd(JZwS%{w|M!_roJpbWBduk6?J@)hn16Ro~mUxVnRb_9Gn}7@u%o zt*orA?(HFeTm(@f9r8 zKR7m*M<)}H>^BuiE*KQFT~uw@h16Jvuu}HyDiv_{($s^e4uM%s)@ixI5o&YObN4w1 zKJo#E>HHkNN9~8YR{N=8^#GjKX1%x-rB&5h054KM#@Z6<838Iq(JRV)Vi|zQ@6%Jl zjcBOvA>Dv%hxXcQC~H;;ltKn<@wt9V)Erxr-rM(#)Z$4iz4NybE$FXSYd{{=ov zQ5Y0o;_DT0*%irVY3t+2GW5<^#Q?7eM{tb}$2V>i6@#)=EW8tM5e$oHM5a60wY3dr zxxDXpeifz_DSC!QeeyK50s8Cl>(?X0!y`k?$tA);I75IpN&?ly#4Nt-PRcjp^|;Fz zLiMonrB=zxGKU8IRIL>i>xnYZz)~Y1H8BCgjh;fr`9Sqapj6n*I0UTju^lEtT&3$? zu)5*7)Zc9HFvl*_6#$-1KO@E46E7lPqC6?7sz|jZS>&ACrqkqkTOct!moYpKObS;* z6*DrCxL~X)cil~|WmMYBN;}$r{PF(%2M_Mu)7R)Fd({l+wl-OFtsBCK!4JC|{v;48 zAenu|^!1MD@Gp-*M#~o?PpE0sl=jXY;?l{AB=hWKaHI83OwC9c9-H=-s)5)NM%6^4 zZDa-TCEtq=DzXOl3A_xQm&))DM09bu6tJSD=HqE$k$^z|${4GS7Uc%$gl(>@W@d~V z%+%fzk5l3|d+jmhE3Gt&_b*&7)NBO8@L6S8}gQ@uJDGo$xn%gyg6$N6-N39ZD| zG}># zBbc#E)|cz^vpdk3Teu-ys29r};NUaeRI05fxCP4;IZ+r@3~P56=YfA=#TmI2{4$BD;FC)4tLf0Msk1(gni^>0>s$l8iXI!+)N)y?F;OS z*M*8gSLtTS5r}O{N|;}@CD*U#O4gGb6^%etV{T?4K8hy} zM?}}c!+7)N&AWF@`x|)o4x{^yl$U-e<^ld_aGnYV;-AXY58J{TxL@(&W-C_%)50t?Z% z%L3u(fKkAdTCGnexW>IjH>fV*Y-KL29BFQGYUI7U_wM!d$Z{xpNvG1|6Napq8K81h zK<40rwIy&t>>a%`yL_pu508vuD`BD-EG@Ry&zf9$C-eo@vZ6dJ6MSn!y+|WfuE+{j zRe6r)X^AIvdCbi;PACIOoq_5Hfz!~$wmLcCt>~yl7oMacoo}vnF4k8&5#wM*q>R}EC#jBzLJjFOyrTBnMJd~h1!HWuZRTHd9vZh3YyNG=*E0Ty6k*ue@9tl~6-DMa5Mu=vYgs2Lm^t zLm4O|Ft)PCp}xtYON}@g7RoxEWb?@IGt=Pw%xq077?6cjm&D7n6Qu9Z5oC>Y{s_G% z!FT`uJ_ z{~`B|yhQUTRI7DJVLq86PBv{Nf1mub)^_qmn_E(rKl)N*+seTgB&XTBfJf6U^mp>b zh*G!$+H_8Xec&NTQ(Iz4iL0U3SdBnFU>-?(vn;-Eoza=V2uM2gHblqDae5}c_R^Qv z@&@E&-$YcrJUfRz)MRYI&7H|A}p~MK$K4xeu$+ zB5_o)A$a*@b z(D%4eqv-r`?Wh(P1&N*af7oyM&X9uh!-FsMua;*MPXqW?R@c;!Fv~mUoEPCKqa%2h z(CG?o*U&^A2lq0xuk|t=?f35gFCH-6=YRS67f5CV@&1Djrh=Hg#r$)XAuyg3X48u< zJP#$2jV97TN$s&yU3)#jjg9m3qGBBebyWpg4ZQ{nRZISg{!L2@s0mYBL_f!{p`o>F zuHAxhuCF&6L>@;(tJlF_N>Bza0f7bl;z#su4IoAy5Y~l#N_UT@mKJBR>!ryKOc4*you~=-J@6x)T1x0roVmmPN;Haxr5<*;nQa0ph@NqhrIvK_u&0a13BS`v)HX{o8N<{_WrY{y&cY z_y7L=+yC*O-+p`a=%anBP?QKGNj0DVvYh=7tI@uwLe+C5!7W zan>ddFYb^}&lDOaH$BKR40k{bA$&td-L{rGvvtGTl1pmGLE~P@bK_6ZfO)xAT$O;W zWl?zWNn;l~6i~x$4%{ZSAl{)XAW+i#RYgu$IrXoitoTc8m-Z`mJYTu_y>HrG@hSHX z#g2M8)N9Ynt7O&>K8NTO4+qK8NzzT{8PHty8VJ3r()v9%j}zU@NNCE>JEAM1v;U0q z?(J`G?veo@v!>sXurl4GclY2?_V+~Y$+B2wCB}ngyw)swkR%YmD8@WPZ?^rn_boTV z>J84(j#Nx*;q2vLU{J{e_`19ZcPuwFH8q0`oSyN}y3Nnqkqes=xnw{&MIeGp?p#wERN?N1Ts(AptS?hX!I5K>{YAYg4n>Z8FMB7(k7$bV zR1Q1wsvmN5&15usmiz$ol7pp1wDbbiK-k@D$=7H=sq#d*dJ~P}LV$x%xeWR^p=MAt z2o|aZo;f;Vq?{-31Qb?y7g#$gfs(zU51yb%dGUdL&ZWP(Oo&nrEfR{%`%?`rb>?MG zPDS}mEWe1WpEuL)O9S>&#d)^5eYSOiT^KC|8J;R-~+iUR+q<$;Zz`=@u5qEghP) zK@j^xZ9G1%-5!7fG|zPC=0SuDlj`e-DNi8_Nv(8nbcJ;azv% zTGQtD8aNso+sL*TMv0g}$I8`b_3r390sqpamkQADx;y`VB zPHvm;q%R^jAWncC2)0jMt9+`t&3{YQ(cPSWFE>|wm~lJ+%SJtRVW(0>OcFgAKiHxZ z@2|c}IyN@M^lAgKz7;MhW0d)R9M;;et}ZRj&Z6?B#wTzx@Gxd(lslyE%goFGjPd*a zcOt?!Z>SdMKX|izZ(wvJng>NT6>2$EaRLl=yq(;+v#X+^Y6|^^asv^$<y>UK)h*U7vy7Pq#Rid}&wQlIgCVO+GX zQG1F8EHA%G)jQQ@P|u02sIgy4bPX~>Q&M`yjBa&`Ud3=Tpbp}wK_d>j(ujH*%IXwC>Eb68SLuR)bQ|F9LmJ_#Kic_%-SkK$7D!z z5s0~mx^^FwlqHF+roRZak=O`hC8nH%K@29Nw3Yl)GKCKhR#q_QGZR?X@rkj~;gSCS zR}#!H%rBllqqr;1= zF!JTx0^l&V7oJ64rMN^rZ**yYZ}G$gKa@UIaXtX7vSv? zPxK|zDXHf&<_@An8jK3@X54qbuH$PKMWk0=L&1nPM+ z&+(-ZT;2&+$nJj;;Js?K?Xk=tnIYM9p}M%(+2MM*Oo)}w$(L&}z;;vn*s>&w=4qhGorENRFg5W@Bm<9Vz znU_nf7u3GMl=ZExa2A3GGk?V0QG-6vch#U`@jH8Zh%EQ?bocgR*kKyUovPQ@ZX=@G z+1c7Ea4s*)CbRSKh`bVcWlp-Af8gXz1L@Sb?1AhMTKqcw~4#8cc2ZM zjf}9!k==K_&K=`!(Kpun)~7PFB{$bVl-144T3v4Imt0zUXlDTq<93ME`LBxKkvO)Ba96b`hcYGX- zGTg4)GWo^3J7?z+5(v}jT<72r$m2?x3=u00dkfTulcmR);>T@nMP*#SfxcqKeFGg{ zh%5Vj9_Ca@_PHmxUvd^`J(PLq*vmPB^yC54VBSyT5nL@20@jbc9`@D@_7uZQnj$+# z{evac--xVGJy5l_+52J5R8$IsdMq=P>Lr%(S`NXdY|cQT>52mm@qvkyEPi;kXgYc_Rx?W*{b(de$5TD@ zU!N01k=TsrY^GOlWfb)bOrbc~Ut6b77Jh&}3GANAlHDm-&gc$KK@UJZO~omlcoHdM zpK-j8pKZ({t$fF$Pp9KH-CH_3P)xyTm<3T?lNdEEp#E<;mY$WM-z#i8YA|Ja;0t9O zBKP?=ec-+0qWw!8s-<+Fncsy#f-)*98X84x>Dk@c+6MRxLYYtW;bqeho#S)p?-ph! z@^_kKc02xz!V5Q~f3Hr&NU|dE%f!E22RjBJdmNnD>cL@(A&EZhQU@FX^QgrR59rC~ zP2{Xnu-sP;`0l6b9qe*8qHls@xuYOHwyn+`I8x925{7sSh=Yp!3vb*rYv)hs9D!))^aY5>%# zYA5BP;zBF)8JCm)5{E+{!cmr&nVo)RNyLtglIbZE`V z^b80LpI~fsB04ipkTsWBKTIziM`W#$EVi~TmOVVAzE7A3$SJrgXD$*>9T+t1%4rF? z5$U{;Z`gM`0H9-uj8D%Ho|shr`uqR>`{7^qXCY~x96rLiCeic2@8Hn&c=>98%qi0h zW`F^9q?;p$>ENKSu!V|@&W@g*p6=U_6L;_2gRY>^TH4}=zTR$rz>nZHIQTgVr4OBQ z*O--iS02}lSNDA)XDh3M4VDqsxgh=xOwmCVuJC~jeRTv=p*c_tFeWH0dZlsT0)nUP z9_qBxJJfpr_~S2R=>03E=KkwnKmPP1ziHi&-m1OF4EfenGbt4rIy~AXuV^dksr3J0 zU+I+2!gI+Ogvs%Vx%pK(ZfHM5LD&)qcVms8KCP|-(&Z%tp&1~$ZwE@;8&v) zhu*Tf81%43pAR#HnS0E8t*$OEO>j(28COVLYU)PIeV-4{)2T@ye$kye$K8Hr=16*= z0*D;pGjU~#8jEKpq8%V49Ya5`T@pIc>%dHEDx=nbZjF%#aX}wVQMDF$3x4dUb&KkJtn7sO$t(hU6d`8U0M8M)Xlt0 zyLiu$!!V5vhJ=B5Uc^#6hht&_CCAFMCd8~h%XgbgDdgl=#M*mIXVIp>so-^>fSGzh zGz(?#tRpWkv?2Pi0Hvk2LaUiWPWLFb3TO(Zm0jb`u{%o7?Ynm$kjIq7w5`=Vt!j{~ z2j(TB=dliIfD8}jvSi^kq9wSmvR%jY0MR=K#_jEQ zgW2op!RCe0>+IB>B*PB?Sl?i^NdOzvwmkqiq;$d$`MwFO7c8F{8WB^Y^PbgCR+IwX zsb2fGvcH#bS^ztsomjtu$u70*X6N$(E{vI5QRGywx;72ooEmt z*Cmdy2pWaho!SJmEpA*dE!|~W#`^ji-6ZYj```KyBk3TH@E^o@>{W6FhHzI02Hw1R zN&eKkcX+h)8ts3V>@Bv|gG?4ae8As$kFW7YN0uB>EUyta4}YWoJ&9QS^c}A50No>> zGA-fhlYv2uxt_-;nOT$nct;5J=Y_=u`Vd8gFtX_oNYEl`o=31>bj-mTY7rAFg#`^v zD~_|9hKQQU7BK1h>WEc(aMjz?0Kt6H;h$bxG|OTu(QitRis(miE9p6h{K=>S1Sf!j%a7(ZSMpO76XNtcjJm5??^omh}9NYw+$H)83Hr_v2 zTUGDO&aSMOFQg8aLI4LE(FlKAv=54B*ToCPi@~9SfSlB%2D}y(BUcODmi8TmVIi0= z+6ngn9?DAxo$2=TELoG$u4rng7AtGXu0Geog8On zBZtB5rTIhvx~LsTNp?WaM7B!{yk7?g^SD}D+w@ox^?c)IDN^W}di_L@Bc`t69kj55On&rEqcc<+V1e)7<6NnemNC_aWYHOsMa5X5V%de&HSAz9Sdm1_~!57F%$CP>24THm>9J7ZxbV!Umxob*rB+)cp z0e@^2s+@{-hf8!g+}zO5zGNn3$zkBZ){qSV`;%j>KYc;*kFb%%Dp)LVKz)5*Uw3ys zEN4qgJw3k3c8U9H%@Ynu@bqjkl7JF|zkwaM zUoZ5K_Sh>BmE@rT-4`XraD6xg$yev6lKE)cJm&u44;xm4Z;>UEC)X>959|$lL}FVE z9U4{SARB_}c-b4w@H)C_P3Q=r5_Zt(m4-Rs3 z@ngBqxpX#2g;KIp$*xtS%V#m^>3Hhfc5 zO18MtpmJwtH%BiF&Ye5RUQBoaR+;V6R9olH)L&E%IYD}FXq~CU$_<+8^!y^1o(RaA zPu+T-Xk8fs_6rB%UCEVYJkMk`WD<&)MupI4be8L_kq~~DFgI1Xu+z8xkx^Tv1OtnS zjXW`&6mzugb)g?Z-c{rVu5TPNuT}CPbyLIwM9!9frEPalMno8YODWxAl6(%86>x8R zd;3iHnAfz1@21nngLy+4pJNgT$Cs$SslWpZiz#aV_3JcUtmZ#MPp>frQpLu*bGM4* zhV&?`fRT<^-9`ukX1l%Id)dx-R1?TQM~ zS1mcE#Bu1q&P^bm<130_f=k42i_Q_Uk1EJGJEuXqd(A`Pr;+edTvE>@Tl0`ebOMSK zSHo)y{DP38L_i0gw;hu8Q1p+@&z1w1^|8Z*Or|kAx<_!wJO?wGU3<}yns{%|9QB=D z`3di(T#PKW9+H#Gm@@^OP^-E+sY{_Grg=2g^dG1v<28~m%=m`ki&%~9U#3T@GXmvD ztqrBW5fy@131-r5eh&?Ws!F?d5iM<MHr4Q;O8wEYqss^v2}#r1_j!|G{3O zFJ8WQP8sfV`|rJY@${LKgpBC+zsK_zzMGd}PHF}otbkbpCmBmYCEE+^@Q{8SbfqMHc|j=qDwK~6Sw7g86!2I+!krnw8O zQ0Ql=s<`oC+l?F7U{jKXDl5m!<^Vf~>Erf;i#-RHlEVyEO^9N_Zl-D)$DHzWd4}Q= zGcMysHn-qGHa9oWeH0ymm8&4rSr3$NM+a>QK`rym8)W-)CvtK`>aIgTcNnuIatX@w zUo^+*+4j~-^aQjnO6lWw&|f9R`lDCLP;m1I4Xti-{{}9>7*GY=aEGswOp$C$%sjmWB5s#{iV360NJI}Xe@115;{)c}NiwR{%|3@-e zWPw~Uj`W-AsQA@OYLM7M>~AlPLlvw?gZf1;q`a(z*G~g9``Wv^XQv|0Q>wYl{9O8? z#>3m7-z}6P2bDuOtiy7Qe8l476JvJy@bTjddJ{Z-`s&sD0e^t(c0|n( zz4&=r4R2+i((WfKVNQM zxumaQND(taWQYi0klDC%>2;x75GDb!AS-rQ&K6TmMLbqCNbrNi;jo-mMJzWN zcR;Wh!$bTgT5D+F&Ex&)$=aj!z12h~a`iXJ)LcfgXxXt!vf5e3-i}BS?NZ9Qw!qnl zwQKYSW%qIwzO7ZCF!ve~LF^2k18<%=EQj(8xho z%DS0d+jG0Ow}skkc~B9%WR}yaKvwiY<2I?$;+v_LVPsD+FGL~ah95ieSX>T#1W!~j zVNI`_XW%Zs;>vk=A)*P_L33YTR(HM3?u%Ta)3ijIayDvidn}6wjGGwZQ;#K0o}|{_ zAUmMDt~Tj6e})<6@C^8%r=VFx2kTF;gt2|e8f6viz^AV&xQfUUTRMZt!b;$42kGL6 z_<}sx*yxbtXQ#$nL`cMxgGqI&qQC-Zk-rf?PpjOrRyvUY8_IMf~2S5R0{UVjq_ zaI+F_;Qlk|CD0dsoph<)yfS{LaI%~p)nw}gx?-mMH*;8{ip|VfpaMt~x(&BYttn4s z=Ue%GC6`6_^M&}lwY8UdQ-nMskMuqvU0L1U-X7vHUCEu3=)mX5YsRO`5jNVeh( z%Sz9|Yx+P73$l#itFgGQSw=PJBw4}1daQc=M8M-dpXEEhi&<`FieA6of?aRp^*acO zb+J<--{p2MyQQ!19uem5Zc=I!0zpMzuS*gq6A)|wJ^94+$wt8Jlf!E+zSd(4!ny2ew^Udp`b&xJq+pAS&M!bL2&o`8SSh{` z&t_$%FzNYOs(wQw;lY0S3Zz8(W{C{V!5psR*>Z2lxLx8{SeTw30|pKg)g2^)J3Kr# z#uUGC5_6?7fHe3_g7_z^}KvYFcP5qu9ZjXvWQ2_=rW$Gw2L)7Yr5QVR0q)gyaMwC{&TU=a6hp$@t`J0}WZ?*($X0(C;Hbk5h zHJA=#%W4Si&`qw1>tg}L@$nUAOMv-LsWAYF#A1eSQu}CWe7~y5Uay0bxYbOsKBTL; zrJ=r*PT8vC=zSKN8uId{tOZd$Jwt9tAz31jOjtf_hyo7RO;5lRwOHJ)@VlIzoH8&V zDbNvQ7)3Nnjs+hVEI=g^epOKs)p+@r{-xLz!7u}Xol;a#a}9Nv{8AY--I0{u7hkL6 z>*@%3$o6q)dDZQ_dL~|B_uzA8az-8qkaK@eg#gisX86AO>C^bc#JEqY7sm!a5Lg%- z9ya1XIx;psGi?_T`Zul{(bM9G=L-{Q2ZoAD;!ZU;Zv-v}nMbcMqAJRzI>W zj^AlLYEATP5TWiIzWS6NQJbBT!v!Wrk-gF1bowWVOBa41^L2ZE7sIDyr8iUV6tT)*Q3RWaG+8 zMG5vdqN9~YR%?rGPR^xqNEjpJF6n4tY-UQ5kO*#_$O69x+6?zC^E|)UhONX0h>XU4>c6$0w`w zB@)v_NI+=AiUeQr*_oZ#eX0;sMBBgN^ubWnSCKD44Ef%J`}ZIG^7H-sB4e%`p2taB zPFB$cyHjc z-0}({gTaSTp^Nqm4b7`+ZHyv$mD-3fxxr3xdwAP|!o9t8rG`DcO0HV-udaLpQBSy4}CuM*YpO^oYh7Eyh~zk z{Ri+62QN&gcXGiq2e*WqVnuL51{h2KvD)G*ejAoKAt_fRJdV1W>YF#gZ+IT+Ya+$U zCbv3+3G7w?!&t-O>3=7U7hVSvqGIJi^pKev+4d(8M*xJQLe^NbR@&#=9Ny}E21&Bef(XIAKO{iVcS3%dI=i-{knpMEjfjD0tc3#&E7@;mMGKZau z2Z+FRp;t9)JM@H!-cv?0kP@9^)D=o=Y;0iQHIp_lrPNB&1&!Fn!21!p1EC0q`M`4o zJec_)0vH<`rKkiYjm{>NY*b~d?TSmNA;N|^AgeA#5hzmRp`@MWs1AMiG5x*o$ir80aTI@RTQEej?f6 zVY%R5B3I}N)4l-SXa=-H_-ASjP_sYBy5%xWS?q2`Xc;T1bu@-0Ghx*=yjn*)y~W@H z;d#~3DCqjSI@PFJQ90nobMv)~#v=4Sb8`=mHaGBuNEMg4wL7+bTh&qmRr8l{hzydG zBe6@34q4DvM=*uny4)%%=X-j3dvBA|LbMiUIVR{x7PYoxV%z!P6I0t}*E9Dpt`IJJ zb+tH)TyXjb9vw@eA_Zt45x+=W3=@@&Yu{jtl1HR3fF=T|b0gw*$Y1J4>X)e}U7>ge z^KVSjh@<*}dJ+K+eQ$D+bn%_Op#AH2;Kw$ll(bN*9yi8n(OdMS~kg0PI+4)4&os;8yVpb2^ z>gt)zig5{c>yU~8fb({CwuD34hx&oc;>1|u9@jQCc92ck*?GI?E)^P)Vff(w1E++h zctI=o(O5w}vrzNkZPcrJdw#UqiIgbr4JajgUYK*X5LRb#K|A8Ui8BbWn#J5Bxqc(C zP9|-EtEP5aKb8m1i~<7!?&|L0=@Y4445>;%9 zEU+wzEHEp=Tn8szvA}kHz%rELySKF&D>m!ed8JGz7R&^ix#X4DEb2&BNl9|@5kT;G zcZZbkb)dq^=LPTgFkG)5$kgQ2G?YnP^#TW)E4=wdI#(#Q){&o{C5-Id(~qY5xZ+w3 z`Y@tWM3cGYdW7QI^aK+UX;Tk<{krhksyV%!0T|>F*k#pT|9cz|a!DiP@dWjVjx^uk z<%<`5ctt74`}YeA>v+(sOx6BIeA(#{=7+oMKsl_8u}oLL{r^+-9u84u*}FDbMZk;# zWl8(S)H!Fb@UC~Qy*9%N zi*EK|cCdr((L)E(VFn7#JXsHFr}v6?riQ@vC+q9;^Tyl4%Sq}8OW%&kN%YheF5avh z)Aqt(M$BF{Y|;~yiWVy!Iyz3D0nc6X_z{$q`qJLs)4dWIJg_V$5s02179s+*=ybQ2 z?5cI*UWpp8UN!&Oo`ZI;6GJNoTC$BVzLeo0Bc-omm^INhU~~g3==V!znY{Ei!m8|S)rfcVoN)T&b^!q(P?10#XhaN5wI(5e zb`vB>4v|#?U=)#-9#>T88uF|?Wq!f{!11GKXj`6nrRIqd#XMRSJox$sX2?(3O`=>Y zm&@%a_W=-uF+~!vr25yf7Xt%C6Lk5$*Z=exu#krc(D9u0n3fSprn~|nPB5<-NcgK) z+-34BW?8()2Aky5>6B}!%{Zu9=~osA&p~{M9tv8RwHemLSzu5!*B_WoLn_v`rJZfS6g&L%9KUe!hw~RG;WN@Wdsd$BG+o}UuH-~m zNfLI?yaBm_($B}auBlxovJ=#t-Ll18 zp;%ITiLx}ew|%#dT`XnSKxS30O5y7h^ab<{=>vNQ!CdnnaV^OT+101IhP*xVhFAT7 z(MB)D^Xe{e9;D?tj*fYTMx1t< zf+_&Qv7FRd659e0Qzfu~k8*|X;E;P)5uci~=1LFAm?JeCV;|F8H47764jbr40MY zOdruirVlJqTN}m##8*>e##>wQg|2BqcldfO1(-DA4=NxE_Ah`d`HPLPkKmVW@??9c z*^gB4ljfEV>JB3RlHGtCvu93C@HQXK|EnBL&3mYBYlg=W`SOIe$-r7ypheUsnTe~e zs;ggLo1JsNU9(L{Ffuv6pjQP0FRZR|UST9b6WGt~WSI3HIPPwqKzaBO#gKvXAYs=7 zI_!ZB_qXVA#V(cXzk^i_l))jclh>M_Hrv1qJINfpR54ZvW-WXryg7@Ig11P6C>}gfxH+H!ar!0Q6I*EKxi9IUurtBX~E?0^>)ECy{4XN1hv-j-IRi+My z&*KJk(dk)f1l(kohW!W39403eX(;i!cTaxUwIl=>V0aH+aGPJh9vrl1&kz;FAUnLG zb#_7d@qs7?^|#++MD&Ke|HStwnVz}^HdEQt9@RIvs}Z~k>oLRnVafEO)bP?SC*>?%oP<6f%vqZz4+Iz%rEd$VYr)D-G=@ay2-Bb`Pi$j-ifMlDA0 zKm(A$p568(64S|=7;MR9N&Mh8wHy5s+FJ?`6vXG$(hTlwXSILlRhFJ5rrAQ}NI*T7XwPtVO21cUo!;L)?b zdhb5@3T9Y0MKY?h>)hF2{_@MuKlAwc(oeF?mo8p7ea1{~H@*waz@&ik_Ch0(GupXx z%NCe0&Vp#$8HdL&XwNLB{Oc5JA7aQc=`t1mo+6?o5VlahWzDy`cjyr7e6#A)b?ovu zDq%bZq)Az)3*3ZW!F2OLB=b_fWnUXukG)J}bwntZRZ$tp#R6;!gB!9WGprMVS&0*& z3nciS=<@9wrBNI}bqNE*_KDq*Gl)pAtO4b)Wm)Y1?b7#JG{f+|;LKL-s6~)u zYLWW7tRu&_I1gI`pS9U`xNy(nSRu4umPVjqMii^$r)myzRLYTAp@(C__~Qn^SQ67U zH6rN3=he}*mOUd%%z@wWo@tiq2=YqQN3C@ub?yH3eA*6naM;$|My*48htVl(U$Nde z&2)+(AcYx*3}+p?)i327ckI|iPb6*uw$~X1Np4#BGWA}Yi7v8M60gNoe`TfLmj$cZ zgwKcv(qZWAJt_CX3Ql@{;!WGz-2sUh{S1N7nJlh5ZH#{E)alc`r%&?=;)Yn{`0*~{ zG^j}GsG3`npZU;ndw-Bec zMo&Vt4vnZc6IJNvjm*syn#J5*YHsnkH8r*L#C3Ng_qi&2*pHCwpsvLZd{?esVF|A7 ze=h&=X9e&5{`+sg{`Th|tRc!?YIpA zA+vhiF@qrhvieankb7VOMZF0Q@um93s`YtxgLfu$AJ+R5gu<$nI7BJMoscRLix)gz zU4=j~oxlrr8d7kq!!%x{V;PYNpX-BxZfY=53*Bds7QDp)yt~X61($#ls+Jh;?uhSE z*irO?;`{(V12IOQprJu6rN5UocJ7vHCW^g7sT5ZKHq&>;*hqw(XEG?U*BA=dFLENB zisxyR+}=+86l|PYv~wq13HfB#ZX0p<_y3bmh?V3rfqKvzVL$kyt#PMUK9P?jCM6b? zLv3p-b*=}%GpGbnG-P8jQG~8I+IU)5$x&TL4Fh}aB(08lWazLz%}&C15CAjFDa65r z=?V2BGoVq}XDWr{Fhqy%Vb#9T|1WaDJz62JK^-<7QS{}SPp3J|9LTSXnilfJvxuDV9!=FQu;Z{EIh`})l*mzCD5WzQjg0`|L2KV8;b z%7#w8Uxbf$CLH=WSIbLcSU0I2>L+->wH`gHr?WDt({IpH;x-mdw6!S_2rai%<7XWw zyKhMaX~x05mUHE%@C;u-((fg!CtNnd%>o*Km`NPHWyY){N~RfHa`qLCBy!F=H`h`H?x!=gJ!=26E~xM!WJ*Am=005e zj833=KDD&SWGdSA%-W=_NxP;khQk&^h# zA=|6ezJ5J3L#Cgf&JbPjKL3Z69!ekM1ADXiH<&=Udk`a-3FKrb%#@~{3ekEw{)BEW z4&H|w!pZ0<>5nx zYNMiMVBp1znHhN|BdLndBiD+~aqtG(=eKAe|CW}wZr!=7fv$4Kx9nWPWs9+=rdiQN z@o9x!j7s)MvJknbbNVatPgF z`%|%w@hn|7WO)@v?Q}!}4LNXdFMX~x9;rFd1^TaPZC(4MACUPLM7S=9%!?(Qj5#Vi zzkG3*Q0?Q_#W|`@%KBZ-Jy3(He^$YWI1}Y^jfW1MJ&T3LM*r}`5A^o{*cq4|S3j`n zHi+aDeUEn%A)((WX(iVI#9dmNp4OjYwY5L6%S5G>*jxuPPMnl*-B(2G6O*4l0+Ulr z0JMU6avibOBhHC07!jCeInb8W$BS)2D%6it#|(N}r6=ZF?4-G<7g^mmHa0YbN4<9K zMr82v%UYwd=i2_*bN*@#iLP4d{mVm$ub%-K)jpzQh#G3!u zGz4gNeJvMfF%u6p&c30B8i9OyrI$w-8D_A|)9LMbg|FAU$<~XB^=`nN6;W1((&hWG$U>+@z}YmTd`! zG~bS0`#a8_`}wER&G#(3Bgtg)aa2L5y`|>bRaZs^snOZ9Wy>;ssPqn0|4eHVUcEB> z7+`hTAOs&}!lPMpiX;{p#TDWf%^bbQk`OzAaM&%+kS@t8X>_{6vWr`?0qoI@T#{>U zfOoU{M6M>a)M(o0`Hxs-*5nR7dPMRgoiP$!^j*F7uYdmEfBt`rf4hLgl^dxo{R5#y zPOW{pa^uF`yAK}FkdGdb)6C>Gw? zBrllAV-_1$!AuCIwc5n)Ez)ynh3khRaq=kp-TQ^2UsnXX){p%SiHrouB~Dhi#C+v^ zHq`?er>=#2p4E33mN>GPv!1OvoJPS4Pv=|ib?7TGKNuh>cP z`M~2xn80UxcJ^h+{8NR@C*fC8EmS9<@ZodkT&{s%WdNtJ7kaN_cr)=GCjq74dG6UGzvF6Zrh&xpIy@Tj8f&J# z%X+gC8=A{iV>mPJgc>5QcGZZ8#TeEYcl-8eC20=3^Y5k~I{k?l5IBeSQ}>RV@|hw( z-$hr*&U9N>it+61+_PKOTo+?lW^}!IvtE&y6K?^p!Uwsxn(?FvnEn75Hsd2%oPa#) z;Q3Aa7lQlCQ9}m>_0~haSzo&0W2FTex9AyMn+3cel4k`!?h4$+7YF#}Qyh1Qc8q3W zn6r%k>WOyZ7X=7}UCHn4e(K(J`VI7{B!jVqDW81L-Km)LSeMx>JZ0RNuq}2 znn6Hv7miYKq9b>;aQGAUIr1K8M`TZQ+p>SMeEfh2;j?RB=2uoXvv$-o6tn0V+V<(I z(F)SFd@&pX+=2Eq+R@iz$4(QZL;Rh_-?Ast9=g<&l@o>SU?;UXouxA;+H)42&heKc zqjuyj<7vh-hHH29{_0Q3@_nAOw=Q=d$t#$Th;660NyshG!PSz0L8+NDBRY0+Uso2u zEA(#qz&fHgZ=Mf6CM)rfih{no)I(jre#5PrujjaFOYobLE3U_VL>1TbfyoK;jM!8# zGNMHXQYx^)8z?1yLp*i%>`#op{`GHv{hxpQuYde6#y|e?_rL#xZ~X56{GWgP<8Oca zE04ea?eBm6```ci*PnhmPpiI5KmK&-r%OM(k!@<$`JQhsF)lKI)aN&LAL#AvJ>{*H zMm>N2!i69B-{;T&=@7fO}Zc#r`S+E5;bz|HiNz zV=vgdN%{tSWknDvs8j@LNyEp}yJ(R;%;)A#6dE=mXDBByNCepsFXbcE+!=5k3gD@bLVny6~&$txOdK z9&%j_KPk7aE>+o3O5%QS21voZr!n^QEhy-tV2^5k1n%JRZ3#9e7*4h)aWBtexheYV z;wb48YvxoNIyFs%L=VPNa^XabF&!?u8yOzrfw#w+!%DKQy}$oCnLRuoF_q9b(189$ zVs@ikQU!Qm^>=yM`ffP-4EL2*i*pbgX=SuCC~kTK?%us=^A{?3jKekE;(xRIA%Va0 zcgc+F+2xkz{4FHH-|_by`n+$8?#RRJaMM6!P6an-%jO+BWtLC`-8xT(M27*V){>Q7 zy(mRsy_sBSdz+PBMhX~RdMzUUB5Bwd{>FYlBXWUHrpqaUbn@^KBdz2g@Yv?kl_fyM znX_ju{BYsoMIUN*>S?i*;tXJhkqLrEATJSXWn0lGnT0mGWV7;@T0$y*xnUK4$BuP` z_XvE2Z7CJ0AYN1cUG0LjO3Co|zfa9xW6J=6d$6!Q=BrCWae%VfJDE@YqLDOUx z%AS6_WbV!2i(yLTra;$-Q_q?36MSJ@`0m|EvNXfP12oP`^#!}d5ueeSmmVXYPy~Dm z`081i2^9JI3=2r5((0NRv?wWxsxom@SJl>;9+6DokuTn)W~#T;Jars54~JVzFEjV3 zbv>4!_YOS8)vHf%tJ2+Q!gvLFW6ws%9zD9#*LSBJcVG~1-+@J7zigoMbF!KaqCUUe zb)5_urF#rZ-hBHZ&g#L#N8ly-FdQWN9Y1^aVob*5)2z77yX4*aj%iSN%($TlxC9B{ zYB%OV1a1ye>ux77rXafKd}!4R0@_Od4_Hi)N)7=I@r-|qs%bRWJB3xp7gPBWoGH5~ z%GNTAw)h%K&X`T)74)_Im1=xXrI9)@F)O#1-MkMTGUspT=(z8tJs)T39p*JG(LCo9OY2<&WdkZ0IJa9usQ7b1?D>_yH(u*_NnR zTkT^D`#cd4F3@pS6G$d6HO5P1dmKy4A6ez5j1qy#N2aTfpWXBA)LTrf$Qs@O>^Wy~h4o<-cVv(DYx zDlkg`=z|ZIL&R)w(Cr-bESFh|X2?oMx{xq36%~zT^)kC-%)Tcth7wZaTk>J%#t1&h zET|!}aMpy3>03r8U$KKkC8Wo8L z-K1k;Le(R00k{3~$>UqMe*c~N0mc^aEDB|J!bSWvexk!|MY)jTvLjq7u z;GI<$N?c*oRo@_IEva2_g0GU>&0MRwA_-~G62U0TN;4K57FpJSz*u+3L;lWTR(O4_ zVUz4&;errXgK99FM*{Ff9F@0t&7d3j9>Fx~)Wo`?-FpyzhX0TMC4W|_E_Dr=7DFl| z5)lpZgZ-`|M~KlTs!EGhU$u)ehP~LX!iS+0mDns-!&OvMJ4k#>oGCouJXrHfRK^M+ z*#f{CjuJPU%!u7+tcEdD#PgxfqsNZpTLFJQ<>=1Z4)(BQHStzEWL4wwP|=`X?;V7f zh4}F0VU>JB9QB3{wb*z>UjkmEqvCSV90suPQB-by{o2rAZJZMe=*0{aZ&Tz)OYAGB zWu09pH&js~i%uQ$m9-G?1yReeItU?1NBw*?DQi7pRTqU+npr zq9YtPgq#^TJ(RJQ|6`Kg+E&xj>0o%ogb(BK<2zIe>SH|aKe&IdztmcjL3Z51apAIV z-Dbt6o{K;!Vbj%X*REZ?cH=sGIXoI2D_y^=YjUWD=;LeFSWd(5gWrojc^Tq3E|yU0 z>sP@=$%DAZJLD4?Wh}(^^&!C*Sdrq#{Tl)jIj*H|N=1nSvG8~>)&u~@TzT6WPXbv2 zk^)L83dn>tG1zW!+uqi=I^Eq)MO#E=oJ-GV)~#fX1obVRrx(qd-fc@jC5U|+)<7eHYY%UJpA?U zu5MSIx3*e+q4n1$_*wKsRS|_wdN)qlYZjzfN76_(Uf-|I==a%UZnLZa5JvixNC*}d zV($u{KmHJ@YL)@TtF;MLel3}q%1<^C{a;v?SAS; zg_AM=T2rP(YnNg6Pxn~4~0b}CoQsmD>?A&10nsW5+{qz(bRUi~BRG1o306vzZSgPW0 z?d)cds;%}ABk1GSs}{e4^WKfUusV$)dq90!Kh?%+vo;!~&!hnWA3b`Pc513e$~ zI{*6DzyA48j(`2@`n7CgM+Ny^)#~ouyJEciVmo%5kP1=oVc5}DYprBu3%4bSsg8X3 z0Fg@%RM&oK8?ZP$=BW`vDF}k1M@B&$GGI$fvvX?H6n~FD#UHhQsz@iE0~=QdKNX~c zJ5$W$v-lpHT|5<))3M&Emq31$qbl}jpy!MgYTO*mCZ@AI39Ixv-dpc*U5yEO9nY4= z0v~*~Thg&^mQ9~R6G~w(vC)~cXL+)!_Za&xsXs-B)CrXd6OMiyKQ0r?IN(ZpEG$p3 zk<`(^T!d}Ml54}G1nR@8h?wVZ>|# z8i&MEW59XyS#%c`Z6PChU@I7(K8gS3DDe``43>M5esg8t>5uG0zf2zrZ@OzJyjyx1 zqlg+Zy>!)}GA}w4Z>)DcKu17)TL;Vo@#x_r1g9#U=8Zf>2pp0pGA@?4*{QxVU)*mQsZ}_)@c-QFRVx%H>s&}B9>2$CLhE9w{$D6Eh#x}>+VzaTg7Lou_ku@!L z!T3gFAu_=F@7`(gKhJw{Ob4G_IQs9`%bEe4y9=24Fz}~AJ^z0mtY^GwWCs6?Z2MV- z`$cHs&y`;5?B(K~F&?9Uk64{X5BUdV%21=b_Z~ge7(9M*^XAp7SFc^Uq8hrJ1`8nn z_~Y`G%cjZjSCmNIxPFTqF5V2?wfp?g&@8KjP%*MaMk+OR7UyX>^Mqf~!W$KFm|3;% z5<_o}bswF)4q*F`9H+vm!^5rOO1t5&@G>KNg{XTY z5ndg;I$J#q6s?i_K{ypg4s;eN@v4BX(FZCI7+N4rk(mt~*4E~hg9l;juoZ_6?qm0c z4t5~vBzG<6a{Tz=!vUvu+Q+{Dwk3jj08Y{1Ho6>nM)vo0Znc=D!l@FaG8e|FB^pXd z6l|h(b>GzO+xHZv0tuk&TzDDgI~-xM$9gn>BQ?KaG9V0Cw@W0%Olf~4F))=D8QJyT z%K4$8=VX*eMvBMEk|tPB`es0h%cvN4WMI+@@WZ@ke4ckbr@qX42%~7_ELS9kq0Wfa3j;foUU}A6Yy2m=7^-U zGp?y4HiUTm_+fRmftAMQ8RP-Dd4L|i#k5C+n#KLTtBk3Se#4s#cBwpI7j|X{bSml8YUtVIOQ*TiLq-rZtQsVP6bGniutMVZA^;G1y zQ+-Y3qlg_oB- z9^2jAoJbc(VLU{tmse53AUonB`t*4(aW2d$v&yiZPQm#0O=e0<%MO1GUX?&BE1$q{ z`;T^F8HAc;^ql0hdwcDN4m5^)IdXI_{W`S_F$vhVm^%!^PS(|kM2Y?RDo2Y&z$R?p zzJ+^?2{)ZY6l7MFS>*Q`@ygCnQ^-Np@E9I;wAJ`b+89kBf5+4 z3f1>c4D+Kq<%&6U6Ul9>808(l(DTU%O-WZ>)~Jdh2LLd}-{bLjo27^i6ZdZ3B0yGm zvI80Sx}u{A`c~uFzSrSBxwck&B?~Wo3GmOfHhsW4g``$gP zI&Rv8PNmMm1U(ZD(<)@V2A zd1;yb`p|R2d-i=-SMi}tMchn67*mzOVLNtFVdn~YF_2Y2K|;ljYtdD-z0EDMFLpP# zBY-goD5y9zpLyvOh=m9~7$7#CHjFaiQR=|9QB$hDD1OUx@ebr1VsmP;Ta#$9;$@5Z zKq_37wSp$P8o!ct!jrF1z2mbws`moSRa19fT{47=-?D04J5!?$s1>rMR1Y5RWSHO8 zX=4?Dw+>%*c9Kg-#Y2I32CavW96x3if@#*SR3fzQ(HeS3FjZL8oCOnZ?v`9X+M>-h zi}T@w70}6=BP>#4CB0_dhgd^S9H^1lAXN(C$L$0mcJzF;$$S*Ll$bH|2f2mE+p%y>q*-kE-_) zW}&5k!-rzThYub-dKl_lkLKr_Sq?8UO`Q)@ z^b=COpIxn8w}Nu05vl^ul6BNZDZmvP!;V3seQ2eIwN?8?f~r7{zJrn7lv*rw)hKT> zeS#2wHNgEx{p;u*W^y-`H=Rd2S(!tqk*{c(Yl*ZEM)%#vPMz%RB4q|8U}DR|JXOLF zQorr(=Bd1&b&Oh8Ac|e%cKK3WO>|8JWxg~4BF4n%+vrV}Wr>=E6>1V(i!Lq|%yqQe zbskoNew#*K1sMts@GuxP5COu8ntLy-PUX6c44o%E3;VuOvD&ep@XiR&j2xp_J7PVs zY~cJJ?gqC&PDE?bLPy}XeKHq&Tj{SB28PIwxL6w``dX`+{q&wJlhz$f1+b!`IufhY zrUHo?Q^*Q>Jd@Gp;p!qM?b!$-%5RszT0=8 z^*HRM1;)F~E?ZZKn6ANSz-L^((vK!(MJMc_St#N!VBu3kG2q|eAkzPSe%`29rnbes zv@#=ler_s9<5ulAk_fW*w>BqyF7qkp;J(CyWDg}qxv6GdR^c({xigE41%u&$ggS{X z)8AYTD$Yj&J?oM45q?Tyb@`1=k`N86pcOr(v6b$O4h~qb5EkgkCM0p=jEMA;^U!K* z4}cN57(8aTG(=Ai>q8iTJW^sxKds72(y=)DSC_~nb0%LN6Sa|i;i@SSq;=8&=QQ*~*L`sbR=4T3t`Yvu=Xx^+JR1D#guwv8rpH zfzJ&Mt8B{>E-zcT{$5TCKFhp_^t~EWOXYf5M{d_J`sr-oedrL3I9kB~s`5vOnVbay zlMJI6%*79{-^|RsXGh5eGE2xFoCFw-$Z0ye+!>yk+trimqh7zJNi%g4<1a<5oD6Zi znJtjr_yklPgSZYO@N#hI-n~9LnMBe0p#NN)#g{(xFba9UukQ-A<#%}xwd^+@bI+Y| z{?4n{g{je*i1o$&FntW=1GF2vsD8m|VKWgnTt%YN%#5?c5MSk|RPSKxFtEX4uuN@A zYZ-0rrh=Wn=BDbJC9vdja(&tt{Gf10cbKuPm0A3pmEmz}d1622Dj6~&ctd!eRLi2E zu0d|0#_=%g>5m;dM`h9l#>I;lf8?Mlf^R%7($QTP6gOmDhP)Rv^I==pKjM%-tE5*gu zd!-X9HToGFRzk=NN?@nYl~u8TEF5Nsavd(28?a+Lp2rjC{Mo7XB*R{KL|z+7sF z1A*#0mPH zIi>Q_ZwO7I9zZ)OZN{tALcD#aB_m?3oG<7rd0_Bg{?GW!x9pdxb)K8!VqD3W^?R=v z#5;_!mycO7gbynZ8e8<=yW4l0T_$fq=-py1g>0n+%umPXurS{rCKx+qdvk z?{u`(V`_O-{%`E%&VW0nlOZ7!1H2N*0;bob)YR6gTbt-x&P}26RjJn$EwkI0E1!3; zcayn3*=X#GEV3^~TA?Y4Q91U3N4im#N}LMO)oDqM*4g)Chxg zkYVHjO%@yNC41#^LMsjU^Gv4pl$x0;ip)}x&KdJy6|7eT?Tj~NHy8)|7F4LR&dw7j zd&oN-KdwhJ5INoZgO!CG7cQjV+C_Kx)w!L9sz1q~aP9=Z&oFj5cwi^{jDnJh_>D%s zeEmH4jtcCJI791IiByI4)(sJ{mW&?ud+Ni-&vWbSvWEquyv^&deom}b;*Fi)xm|RS zbU##B6vkN!seH{h7KFqH_w*E`3^U2?6VdM20l;90GBl}W@%}DUUa7y z%i|;N0pXXq$U}!mhbep!E4X!ni{FBeD;k&!*lYzdm`~%y&J>M}>s7*nc|2v|NyMIV zdg_{^(w2x9y>^XQw{4cxivVC zmDRO`G@1|ya z90Ed5E6sXtQY=2&f<Il?DbwY(%#lXWJ$G7~ zuAW^ey_GRZT0(mfdG%AQ#mvHfm<;Q-rd-1!jbV}uM*kB#lvmei6$|+S8H-PYM61#zY0w0?hyi zI;kOM_^vhp2aU_CuaoQ53JBxSAT&z!SCE_<<$z5fQj)V*MGKQ6ol;!xR(3qmuIN<@ zf}`IvmQC+uyoz>#lZ*%bnC(02=+9jEANoy&ZTt4k-}OX2OJ6lov5U`UY6NrWqZ+vZ z^86|i&o@=7iw#ov2fJRX9D$Q+5}D!EFor*=U<_!4-mtij&e)Z0nph? zV~LSN6;KB?!Y(teek+kMd@gHJu(rv2Wsl|9NwI=#`1iU!?RD{AGjHFhiyt z^!*%Vj4(!{5OlhI3Eu^SzwpHjw8%qOiOwUk<&61`b2j=S6>O(7QbOShiGvY9E~9Xh zC}4QLTYI`6?{L{aUGHZh^l z>T`nn?!1GS0DkVpimIesEyaYvAVjk(cK{h zko=TK#s7r?GiCrY@Bs6}?e?qeNPwaUUH zl&pBtGwF>yC+melDw0al%shYn;tIs6X^7paJcHW(7Lj2+hK5B0?Ok7(Us{H%;o9Kh zU@kT?(ypE9K6c{7iJlXjfy~LlaVrrj z-X$?0$hQczAV4VM7o%9Z(ZCZ4?xIuL6=nddBbF&EUt~t`rrzRm6Tn`m4_YLZWK8B& z30k-h#1X6PQY@YW2+sS=6F(KnIC8V7`<0V}Z}_BtnVxaKl&`5VL^P8jRZH|uGI=l^ zVTrugevTk1f0&O@N66@YdizeqiT0s>gA~BSDZY_w92+g(bR=3iOqF~>q34N;SbE3) zT#p~x#ndgBD%ZJp3Cy9A4;U;LM(H>MkFUr9z2lRp@2X$Q9D`rWB?Iz zk+aio7;PpLL)Ofh6s%=Uj#?7|tZFRm5)W6`lsO1Lw(Zbud639i3F+dvx>9ug80^ z7Q})0Y>Wlg;>3xQM~})P?Plx(hP%&rC1OHD$b@BgQlcM*rs3UNNbo|2o9l5)U?Zl; z>Pvb<8z~lM%~gL07P}~LW#MIf(x0>EiY#Y|lQmTwyLK1;U(Qjxpk)+Yb2;Pztm2gi zFBxKXhZ+}~F`mE_Q1gv$pdC+9jVHWQ={5rUb`N6CIO-mqy=3uqaYH;D_H!K_0qPCf zp$w`amE-1p%0UA#$^9I%grj3wwXu=<0xJ#=qlC{K1Jql*ePd*$P80@M%ktHTP=oQ+ z+NXE#-LFD_iU~{Bk)C!`%E;Il7v?}LbQ(UJ8PRj-9+2n^tG`S9ic@CjM_8$rA!Z3` zz;Brx7dI3H!FRp|KfklZPUEw3CV`Dhb(NFHwu@un3m+V>fuTQ0J>21OxE3x@Hz@D%PE5IGvOT)x3fk&21n@_JlX7+50|At8 z5O^7U6#^qKsc++y*4L3FAZ9VX)^yZwl4f!Kyw9eHJ&Vtu*>9Q90@dI#`O2sIk)CJ=~08)|@+R!bm-C)Y#){4c~hO{)B5dy7o|7HH_>(|NiG2!?Q z?}2CzIHN1Z8S{mQ9-oiQ-4OMNbH#h2b7^vWJ;>z2KF}vm4YvNIYTbRMMQMOFlt~N9 z1ErA;Whg^ z@GIOtag_b?%~wJm82{w@KzMZQ=O3>0F#Nu~M|QDmguYvCD%y`9%BrxI7CX;SVd2Ne z4AijF)d(215aZ?HT`g>!(MyP@LOw06uDJtc8#@&n_MvN)jx_C<0Hb3IPCPNWahW!6 zrqWQH4s?-8@QjrgzGRM-m=4i3oCeAUBa7%@JvM?694{vVVl)q zE8s+`VoVRaymA|BMFq9BP4ulQbt{Qv6#+v}vd?R)N}Td-JOiqkdGprY&xhe1h>#dh zgYBMVJf>oa>W2sS9+2$?>D}$Ke-WK3Etlo{d-oqv)b!-Z-~fMswUc1QL~>(s$Ud19 zQ^Wi%3V(&ikfP|JBJLbO?)~nf&}+Q`OI5=gM9ABp|}v@YQM=^miK`*p!__LQmPPH zZ74v*+etNp?<%<{B9y%AoA~)%L{5=nDMTb0H^#6Q!~KKIt!hHpftjo-%1MuL|Ccxj zPa6|pZ3936PE5<8u=|z02Wqx7;?#L7Z@}hjb+jh_Cf?px$)H&lQwhHOvxuhAtFTlq zUBqHW#jjvLa+GT7#@#3m%t(d`@0d;|C!rFP$W$dx#&jw(c)z$n_f5sOYWj$X&2lt? zP+hgPQgV@q8#kAa5z$zSFV+Wenhz6nvfdr`F0KtaQS2$tSPtrmhDU~ojZ_zCxknG~ zKcWlC^t8IAb{REH6AH!sVf$x@A*LA8gVy_4^#D_58jFEK_zo|EFe~{dij+W!)+t^7 z(+c!!*RR_;_U6r-bPxk~hBh1;hV!&SBxWOZAw)(XKdp}PqIq*h*{Oqj-PkC0%3QYW zpa#}@9j$4nKn&mm@}X-%2R-c(nRAdPA9phfSmv~RY0pHSYcxDf5<`5k&7J!JGYF~d> zUN|*A*7Xq9h zYe$7gJ;fr%+&XCGEpguhI~ONh4Xgp!ur3!q43q^c#VRibUK@*CD*Bd|o3`$2v&26f zgeHpK0Nn!2SPs!A>`FLKp=lSr?v8STq8xJUfs=-7h?cVNTSw#9a688F%%wE96~j zAwYLLsFz?^Dq#VVersfuM6=0Z{=BO9?j8F?8Y4F%Z%6?bjEWe})APGpSuYquFH0vc zSn&op0#;+=i<5*?+1SNPTNHz2$0(=cdqucj&O7g&(R9syURh!1QK}IT`I4PMruTt1 zZx&9qMU63d^YlT?`uwN7OdMI>K)#i9S^ti9 zuhTVMsTzSU>S7ejL475lqpV$SZQZubPZN@B1--t~)zJNWVZuuBjs-j~hDWR6gh+%+ zeEId)t5^H`@4Grmok~{t!TqOC2lP5V!CgFk_5>1yD;pRcO;q~@O`v_7VR|AA_JiB-Dvl#jJ&9D3=T<1=sw5`$9LFgK)>_%$74&f%(oP zQ;OxrWC?dG>jPvbsM4bgw|R5xU9{g?O8224(ke`am`+WoAZX(fc|c47z*!liALYKz zPJ4e-i$gVz0z$PzI!c^BC*V6zEfP=knLc;6=Y+p!uJbUApT#@wb$8@2Roump8c*^^ z*(nlRvT?0NYK@t&$kdD@P(nX!HRqd(zAp<~8UeK!2X_mQVc)fTADP?^hVNE#$N0{C zpHe5fzUgi9~_m(0O1l5RAJtpH4W=n<0n3ZHc44 z9>d5E!P=S<>$&5&6~(0nWe@wuA2?ujEUQ}%_cAaL)O{p%fWLFi0QF%dLR0Nv+NcI+ zHLv!8sv~D8USmFesWWppTAT*rC12s%K8M^aPD97Rn4c$j(7_d?j=A2*A*rIpek)iq z^!|>0pURkK;0CBW85w!`!cH693cK6Q=!)Lsig-+uP8F@O8%>BgdX%#Z<;S?#l6B%l z*d@KJCZXii492HVYzQ|!jULhwX&fXM-4WD18-PNz=Dbfr;!mFZhgLFvb@O-B@Q*+L zV9)m}bexc{xOTn&?l5*^cv#-^#S2}Ran#I*`nqtJVJY+i+qWwN@fCIPkQ{LQ*=Z8bC51GCNKSUuWq(tq#?qJ2LVZ zAr6hLvSNnGwKRx~Od?KoUz^~otGk=SAgafb*q%f5z{bll%Gr@kYUMu}YOpq6FDcU{ zZL7~LZnFaU$%NJL185wzLvKZPH{{+MCiAr*XB~m{<3!@%Rp7UyD2i?P#@tJ}kCvoHuE+&4) z>@~mR`SN>w<>Wc=Wg%9nR`J8k3frLzqadFLPqC_fQWiTD$W=yhg31f24OJ@JNeN8|N1ZULMX)8bXJw!D(#z%s9cy08e# z?x#oP7oWkW5RAd4kI?P!QCU-UpB{X7sZ_dso22c39eupb5DMbFo)0`j*Z3b)Q#^SL zZT{T7Ki<9l#x9xb?3t(BruLWnVnrr{>!%tftd6VeV6?bHYDV!sP0fwePwd@mb(t3K zG~>)!$GLMjg7fFkg{b{uW7k`|?)~`VPe1+m)20Ba^%Rd#72irQfVw;x~03c=X(-ov4<%*nD`m{ z5P3q$W%iT^(a+bfFJC@=`rv^%^lR6E7_0-pcrkwe{qp6j>}q%Y`b{b<@7@)HKO|fh z5DgAO9MN^bim}O8cHp#zOZkWm2g%XtslIhDSu>b;O+|KCAXqv@7Rg(E;ezGA7cX4= z@gjHk{JB$%)2Dm+X>afO^B4K)k3Z&d=@MJOmZLO(`uUeLXXxV`e^_j0i|ocWB2e}W zu_jRkacu!tOz2!+FRH@Bz{7mansZEF?4tE^`UQ4ZOLwz3Z$5&3T>Wcyh5ybyi;+Ob zU~70CN`_v8fiuLJg};%lQRn#$dL0)(jTpk8>`eXpp4~0m*4LJnKCrU_s1CQpkud2! z&M7HUXnY`;ehYsF!3g*SV2Fa;?ba04uCy^yJ zjgX7|9cHHa(*Q_4jp->KaXQ9JI-hs%W%%tP3}2IB=MeiS0?)Ct_4V{`A^+qY2N07b zm}L0(6;|Re1F%G3{IPO1-cDSu$Gi6*W)O9RLN1J@%HhV~Z&&H5#g3ERps?Odw-V9Y z>vwqvrcGHmd6J*9lD(7M5}`goo_+d&_D7Ez|I6IT#8E!LpgQ!7_^q>xRa5&g=*4@< zKkwMR$C?A`&hx*zSvM}@$NzLl=~WK@9`Isv<{xYohZ6pTbaF9ow%%N?OYg5>Ge*0{@b4<@9r zkcKBK;JoK0Luw2%fnnf=Pv-5>@$sSId;P50MiX764K5)z8ICS$HXJWmQBLowCJCHy0Y7{y__oN z2=xL_3}P^f4u&A6be)&_y?HZ^AC|}GI`?D+glp4Mr_K731)_B;amHT`3_g6Qb-O~$ zbeVYR^5vU1@I1F}-MUIdb>#{+FGdgWW^EBqzy4lwPNb>yS}WK{$0HNFz!zj9Yi@Y~4#QS9Gm2+?v+k zHHf%C)y^lsvV(z$)fg>dWwVvH8|=EZxSo^vDfR)cXrcSgfH|{J;w$2jz$@TL2-YeW>#+5maFDOONuV>#1w_R)hFVeyWS zDc`sIyUxHvJI$dZNR84y{qhQOnn7+6ibF@RO^7H z_*D2I^U8^Hk+;N6;8>$Q_qCj1J=Nr-`_uQ`y?o_2xQX93qNC(Xy+c#p?*d$Zgm z_j?Os@D{Dc`gq(02j08)@SzrN;Q1iye%S|x>$T3%*o6*(?7+NzM>HtriYHAMGz-#= zwcvhp&&f1~ZGmaRGJ8{vo}C8g!pxu{iO*&Q>Daf6-i>rRbnfTFfX>p5P>xU^27$dOlt12vp2Z;R>Y_`Nx^RMEZ#_Mu!{q;g}^E_NUWqQMaoE4Al!_-wsEC*3`&>)DgrP?bM(|qCG4S@9>g+ z3-KZ?*D(z-0pqy z1aRO~#z07Bexy6-PQc1g_tM(K>e-@3N;GBxN2X?4<$jGiT@2;EWV#2Jwpe+ zjFHA29i>MzJ1t$k$`&^_u3snp*etewu-E+#B-vaiihiYxesgct5rWou;(69&yp zQsn`{kZx#^2VGxR@ARhJ398RGdcc#ZAqF+Vs90XvVno^~TaMA40^MH^(1K+JzoZCB z+~9C$L72Z|y{8E!E*e*OT)2>>hqhEF>;u#RBhQ>Ueq4<*$3%V!*-f1juIFfES9w+! zxhSAhJG?%%JCS6(u;D$&%4#jvQ9iJ6=&+0=?xD2iZcEaH= zu-E6Fj*hNw*WP2IEL=!t0guNW6}NG7IT>AGu_C^k^a5*si4KeAMf=pjQkP_xYik8r z=ja)6UdC()zlzAPK0z*S6>a$Z*%{ZK6H#P+goSPu$wcyzO^~Sv!aO-nFFR#~>~?AZ zq-U*o*5l(kzCQ&52njNbtNaOSa!r+j(*y%e?uDIuPG`^~xQE>7<6;a%I?? zH)C`y+|Voj4{B}f2eCKUvL|I!oe-4oJP{&Rx8C z>C(ju!IA7|cA7}gn6HPpOx&4DKG2^2!o7KfP*&}fOp4WoFoW9-rPkKI;#oHsEn{D8 zaJk1+s1R*%mt`mLZqAF5hczKzznbsY+(XNOs#jPYrApcb&Fk3I0J30DmJ%f<(4_7a zGTg1rQcy4NdHlHEEcee26TH;gwbNIJ8_WIFLU{5?de3f^2pF+? z@;$H-6^oCoVTCIqhqJ&}I3^H7E-|nTX(GNio}Zk2$ucP_DZbUPKZQC34`%%9oP!^I z;{A9*t*!i&tfvYln`%}R$HVLcN=MUsc0DUy{% zkP`j(=)iaF+EwyViKPk?ZbaqayVUVf+n4yNL{+!%Jbd`*@%XsfBs1~hBj~E47UVD% z*imhzLc!p%J!^fq8D6e0{IH=a;_x3dw@(hp4BzVNmj#tr4jj0cfia^Kw-v$IHH-RE zcPm=5pzXv9O9)VUv z6#Q?HqBMZ~u;YA(#g5JH@V^N8T3efmSW2blD*mF5{lti-w2WLi{4O1k&HhB6c_xno z&)`JEUVF)UQNc}_Q39#D+7&WU?1xKWL1az#DM&YxX!MaTNR`SSCtnGu92;NgJ*yNu z5NI6y5prlyFw{A8bssvo3x2+yUa?Z$WDKc6wXUA7m&+^5dJ=IIdGm1GOsuyMRP0w{ zQ*0i$cgemb{)gB+HF1@la<^}9Z|~_jb*krt$g)J9y^NC#^{%U%!U}kJXisvxDxMYs zF~I(DUx;7gL%18o+icndTC7=Hb>GeLaT&})Tnr7$WV*iLg$oVoARCm1Z5R8byhoQu zbgt6}F z?LE!F-JAvK<*+|rSLZP$d6$*paRI^>4%`4qN2G>Y&DB*XOrj%C#@uR7Cw4@ckGIB) zD8bIIaa7_dUR70XX>^YR;w0C?%TKEOEIO;F&CoLVZ}A%KFU$@QaEY7oHUZp;83oTN>8vi{bm^K z4FU6~Bp0U5Y*hIhaqRiu<)g@W3m$!})Uek`3UM@A_BW7(=OM&{gCJ-~4R)ov{YN~PWmdp2gM@W_ zhn!)BKxByGM#c>bL`4%h05L7-4z5o2T(U&pD{JYRU&r$S`o!M(^H13eDlX*xpe$0$ zcf*SHo9rUc-%lLqdS#ro@I#b`!FXRP#<6(e-Vli}NXrbMH5P6gl_DKj|6q6g3JN)3*%Im$(V7N&!a&UFyv+$BSVD zD?c!(Ms8!R!E!t>W1zW$^hAB(d61|2hV9$-w00iVB5&{zFoX_tYlA8bhh8Z3ZcKFe zI{r_O|I3KVaTr3Q^cG95YHv%+riSY3%4#?6Rs|Mw=jPMVPW)!wUh(1;XI=OedSoO}ynd~x8y%aRsJJ#XPFKShUKI8aFp5@ypsDZkeRP88#x){VxvQI~AdCTd*~gC` zKX^1Sh~s=|4hSd;Fokg>r;HBqU-)H!slGMJ%h#gZiQYyhCj4}wES(^pH1kV6;VivD z#GUMf@#WJee+RFKp88wdPTX(UHCD#ci!H#}pBht@-|;Vj}p z*r5OvWQK{AR%+a5z*F40b?fFW zeToOTj?C2c>$hd=`pNh9!`Iz~wdm_(9|8GskN$gu1EXW_-eUcj0{5N_&MIFJIpz@l z^7H2Csj7kH2O>CX&|amABmw)u>2 zO9ibX8F<=a3FC3~Cm?#>F|@`u!k(t?S`A&KT2#~tv>^^9Uh&3N4pF;@^*H1$sdV$c z8{F+{-A}zc9)#G#s%t{=MB}GUp0a|2j?1vl*&9Y42^=e40>FI#i+)^lJ!`R$T@PKH zc2bcJQo#4Y0q6rcaZj6PRbXkUrrP|i>qa_{96Rm~g4y33Aw9vW5WuB$MR#Gt3w2n0 z2EHSlA<&O#kr77(V~7!vIi*WSi@heIYv`G0GC%J=Orl_UR*jc4Y8({@!;?UZ9vm7E zCJeVAw8H)vZx}6l5IuDqI(Yo}DL57H)QJ-(U@dgjIaRFPkt5`*fflk70!kyuOa}3i zu&sd#DgHRdi4|<|G58$D;^IeEc@sg5jE!dZ_L7N8*rF8{&BbkCwF?Uq+sxZIl#~W+ z06qziw{P_gxCxk!4<8ri+2gZuE68zc@=QvlE-rqKK3#<~b&fo^4Wu$;E6ozx5u;>5 z&35u&d?sH1jMtl(7#Vx=1m&@yT;f}OBDVy0A$5#VwxVZWJtnwrSG)x+ykU8SodWyw3uA0HZ{Mo?AGU45ZB&T2)GU) zh&@N05i{`AUo@?jwrv#A&fb0lA`riP(6f+|rI5)?UNB@`G6 zgtV;ZqpOe+eB!2Khz0APqEFURoa#AV$?A{-rn&;LQGklN@na=lh-U(dFCJKGgbGZ= zDkJfSJKx%+RJkGYg#k?NNe&Um2W(M}bRnqThP5aNG&X~mi;L_y80O!K&b2jqa&0wJ z8n;FKBMG9f%ss`Qac8-$lAuwlxav0gv!kzF-Cb?%ds|cEDnXF?L@*w!5nYeGiC%xf za9L?nc+5!InyH&52HPE?amH8M%UcoayM&& z+v%__tvtk}AU!nGl}<}647U2m+%9=SZUPEM z6}~e=3=W z(H~Vv|9(D3ZI7#uMj~r3&ghbK*o7_{eVPhYqFN)0VYWSBPeM#nL6{m?2~C zG;bUmuegyPK5({SD?p}r7(GjvJe8J|n)0^;7VhvNJ*>RFSo7Sulc(%W4s_!>4v>4| zzCi}bbgErguLP>j%716!nGdN?hQF8YXq$=oK*dIyMw!XQBJ5S*N=L26BsD!4cckCD zZ+!Lt#PU*TbRj7@RdL+Z)I!!qA;TIPZ4K6D!|BKN)bNKna*_4Aml|^1jg@^Z!5;;J z(7C>_tuZ#d-m=AAH>ecn&XyQSU#IpI=H6bi=w!OG3?utPL(iz#+sHd*VPyW+RI zcW^Ja8EC=n$Lt3Ba@>?>NHD}*F`t>SiOTo&RV1ipSmTJo`k_9`oMtASbzU&-0%4;` zb)QPDnYV98MxH#O+r%I$sd}QN*b4?BT7EGIkBk(tQ*_JTgvPWCTr4*pc$6E4W!Jhh zSv7X*Zf`#Z64Mg|{SYNv7a*2Dbco$K^me4a} zb&c+;GvsYlENyGizt#A8JqV)72v#*H2CgXn~HoS(6csy7)Ca6IVJpSiZ0+%%n# z$TQ*4aA`U+m+l1-ncXdC4=+LP)H-kiFm^{PjDQA0NmqP{M3U9<8w7bD>!m=I5Zfin z)FEg$e1r>NSEE_nwN+RZ&IN)4TjABgDzZELm#-kDMBg(EtaH5gmsq%+JKgoBi=La7 zdsI9wytq_m=uDr^oIS&%Xed@iH+_%A zyNh3?dLV=wCOxa1kZ50yPK+yA=6-!BXB)hAI9xEt-0aG#J2i0YVv=K(4VWv3d?xe3 zbTkEJrVfrcUoJ8zuNM78<5gF5zJl=#LKhb37&uOk%X?3s4m^K8Ffd>)>Jbr?s{)^p zlQNh}&wpYm*F51-z<77nIFKBQMvwE`L{>L$T)F!DZ?frST)Xyw&aorxAM6O}n8p0m zKupN!$z(tZ8Y8~>@DY7LEA01cn1t*_z|2b}8%3Dt;Ci_QR+zY&i^y1SpkH8^9np$? zrH9sMBV|M7e3|>K_JiGgjtKbG7X`&-p(XSOnFi{fn(5|hs=vZl!ClE;K_;Vv9@yvT zVu`KG_rABmXi?BWFm;d}%@Jn?f9N3lOSEj-L}xbScWKF>$oNP+gdvRyK>dWfMFZ2@ z!j%ceTRt~2war_bsCrlHwi1XG?syY@i1)Fw$nZlsPHlye5$9>>-^NY{*b6}s`+q}k zQgv1kG)AHT>hE7%NGtau3UtTyy4otd2IjVAv$i80w|HQcys2(#jtBGk_jH zQCihnahMgi@88WYd|PxMYwnC~0mPSlxaT2hVfNp6*t1}IDjk~$BEji3MprWCt58(f zq}flHA&rsV?fuJ_&j%hnxN?Q!S4v=$&D}`u!i$%@?jMdn|M>Gyo~@I;ehrmF@A^NrZXme6MDN946z8Gsh>?%$;QmHB z%vFVqDU1xVVXk3qT`cbGvzk^Ii#67lfFu+XLJxf$*2SNdpYu~C-zVSqU1O2LHUjX_ zQ&=r}TI}5E8S3L?=`?SJsV{4wbQK-R;8+FadU2vGMsYkE=QF3F?#^np&r-PVN|V#Q zN2ny&y$97neT)FKFB}Z(oXtWxKT5eE)V9ZpH;3GgdLiP#U{nGbJX-u(8BtV{!Y z{&bWa9m$_vz;2_a1(G8o5FxJG>A!L#EWjTPEZH(-$&&;`vJ_%Op>PwUx))No(zUL zynH1*0!VRH$@GSAj)TK4V~aifiO4GdH{9W?*PlMlfmjl$Az!H>1iTm$(p#0)g#1A< z9k?xgWj+`H2a*Iq0{sl%sXgYq_wUs?e~Xy@{p(kQL(iVx?Z08zL<5PUQ>s)S$E};! zuIKGr;z%{_$&(?f6NiDH#ALFa2@LUKrJxVUm*fFGXZ!pXDVxEQ@n>Ne78Mm-h6?Ii8CLo0ythEyLl+dm3RF?<=C(l z#e%>=b-NI5%t=QFYYe@` zp0#I>_Q}M^!4%Xga_#DJ2fQs?)EYw=Ya#Vss=)dhJUoa)MnW$L4>qgpY0tUsSqr;0S0fC48NN&R<(IfLT-%tybC{nKKhfK0c-Y-*@PJthb4ak;z?KVjAi&^5TY1>=jO3j7G7z z7wXSkkKkUOSYZy>2*8AKM@cj#c;4t5u*dbcyLQ?wDYOP8h4^wWH5gjQ{V<2xNWM}z zuxmFQ4!brOYKS+D>We;ARj*OSQ1Iu5mQ}cJ{2`scF_{L;SZK^KxWF%9JX*PtFW5pY z>i+%5kNx!1U;gs*&n$Xm4dkVt;3+Qt^z+Yty>t;ZWGOx4OkCUP)B3v}hLKAN60sue zJ7_>aP$+C-Y~L;i_mSLW;jE11lbESkjklF*zG=eYlGXnFc^RdoMrxh9L!x^mpL&J* z`mbLwAN<5yUd81!Hn#5R*ni^0nKQlc7iSn}9a+J0;v{JFztYw{RPyPoae)6RU3O}i z8@VFEp3#%&o(xpOD!E?;O~VdG=L=?xHRP@Y(06Fr5}>Do6ni(7B|?>|a80{=hrOiE zph%-SF){)RKJ#f#j1gd3oFrb76iwMgkogRGqFjc8~L(Czc8q?bKe&A+YQ-q320-ZHR5-BK^j_^ z)-VgTg|5S8R|)n~7AAB1B(I9UP`eG4P&`3S7+SJitR(;?g)1$p=Pqs4jPe$W01R1K zsjF{hk0M}v1W&phj zab`U5;lum)%5dR@ghNg*cb>bBz5B14i@?1gXaCX)_;hWpv2pwMLx;2huISWrNx7VY zfYDFKRa@^XKk=PLGTJINeQF*SvE3aMy>E?(iAdOPc(ZdKX2vHH67e)LGRQER81RX% zTSLe-Psle+`S9qde0Z!%iMZZyRY7=iYn9kK**4d(H#fr`qv+NWCK=<)wwy&%QxjBE zqFQ-!a~MjSGPT6O@TY!Co4{IrG#;=6j(hv=)$8Za9zD5#pMoKlIEmMCxOXTK5(i{p zIr<7+QNDoh#CayI?oqxgk<$Aof? zQ&XDjiHS*AB!^!p7m;6tHWB;<_epPpbU)3mt~6}gyVpqCngEd*;Icc3CNir~;Q+bQ zr+a(lwY1vq{cxH?0aX8cXx<=d?q>BdG@AdmruG>nfX_<55(Fxdl9I9HemB!Ic6 zGbYHb93m=Fm_J86IDClsSfvCE8yS8%&W>)24OEQa)W|u9l?X$#k&RmBDvjnv=t;Ve zdSUZAqF>gqW0YjS-%lZm6j|*^@c=E*XU$FScaGe zht<_Mb?~&HVR>2h&`uYgTsb@9p^TF!$?h_uWlV!4W{0kvSImYF7EX%k$N{I~0)^z) zVeYW*_Gzi&E<2y*a*-+TugSar9K4jl_1<&;xPx*U;%Zr4W+8amZ}f*UFUEcJB_U&- z?wQ0*x_W2V-4}%eh3TlP!&K9AxELH!N;#_ZzU8GhfKgMUCGc?Fh)DT8H|@RF6njdy z>T=WY%n9&HkmsEZi(vI!S+6xoRjaJw&=8x{JS$z$pAbPkghL;AKFa>POoRV>>iv|l zI$bdUUh$l0yLGW?d5dC{)$SoE@&tS06bjD^_;V(4f_BavXD`%ePw(9y9nnGXcV6?i zAv`b;-?O~$8BY4_dq8A@EcVGd zd)FcWL|53}$OHira3?TcC~v6M+KT2eIZ-~QNWM{i)|Cvvb#AYhRgioxN#p~j70$YT z!A@xO=VXTJLS|u&8DKpiw|`S!Qkt3pJhPwWiJl7=e);*ApB+R{KVSOkr=NbfaQZY! zDfkM|kY0^!jg@Z)Q)>zakzzEPR{A4>@-!f>=Ydzw$tzJ+RcM;L16A{Lm_-;&^sjgn zlX=w1;0j%bxQbuI#U<852yrrE?pk(W2Q9PbaplgZxA^jkxjsyot}Xs7+66X?ww0=7 zmwg^QOzwdyds9vfE`_7#IN*=LOC59tV03kN(?^V++bz|uG^6((#tgn{zgd4kTd1 zr>ytXW-A+h)s2XSc%3x?R=yzb);3@%^+xQ8UcarSL>TmsIUi!IYC0KQ_c-&3^J7k)u?df zafI6Mh)pENs0*!xnQ39S)V0+GR+?e*7~UPc7ms;{_iSoPoA*|3cdAm&m%A=9H9pNX zS(RyZw_U2pgB#1+X|3nPj~9P|Z*f4k{B(&9Q@CM9;R4mFu5NZZAd=lrY^behCEG64 zz&f~!7Ca|v^bR5?%qgn_*!TC%TjT$s!5m@Q%r2==*35#`g4ZU1P}*&CpcTZ&i#Kod zBdH?*&`pqk66l#B{zRt)xR|Y*xt2ylwq!7zFXx8|We#y?nG?Epm1XJ& zqmqw1V=zfP1>7=5d;50m#n7Ny)`vb3{-8HEZ(Y0k2YoOFa=(YF0L`(wP-rLAvyn?7 zDo9(AykdQn?Iu_ob>sT2+kJOTO5MLdHumn_hYtn@?jI*QhQyWu&~C$cT7RlAN1(a6 zM*HPRUvbMM}Em_cjal<0uG zBj$9vs47tY3lDbkL{~TL+@ zof%%W(c;1aHGDPf$Zj5#5#AfBqw-2Ng!?W`+P;0)P5~MWg|U-BZvVkU`gCo?|IgHW zI7F2#>)YgTPRpW^N^W*IHLA@5j}P3N1~SGVYl=5btK^&Urx~U|!BPU*{=e zmdBcr`e=)mfgH0$Eo2asHh%Fv{aBqyHK_QooWpW~Rg8#1Ybm(Yjgb zdF?X8DpjK=YxwNAfLF`vD`pt!xk?pj-OxT(i4S}X41tA_zyJIx2n*Q?L9zr`z#|^k z4-WG3OH14XPAw!8+Q$k+u00`cwbF!)02tNPkpOvov_2(4JK}W|9Vdz=vj@gxu#q#f zQ7tq-IWaOiFyNF!l2G`pf&Q2DpCX%UYut&+366#LYcX(7c6>_7h4(w#=>#8AAbD# zr=Pj~^y7~|{o|KkfBnZle*N{AfBgLOKYsc7r=Nc24L{!f?%MUfD_z~KtS^!B4<)Pz zeuDN`C14JZgU@ns)CEsZO<@No#$UUhkGOd3D>n8y$Hz?3iHYf{+1bU#m6i21dOH+U zSBV>vp2?~q9Dy-Aj6l@MxXV3(S&XQoRmf=#D6*qL32zCMtEo*jPb+kP1e?365UGt} zUOWWT(WstqxwyvHtm%+=bwdoMYJmEnV9_5RY`k2dGK9yyKpAd6my4}P4#|*&bxUr zYe`K9kg+DSeQct7)=bb>>Tx9^}uriO<42cA6n z_iz9D7wh%@i*>GFQt=dh7)KZg>o}4zVMVaYnxtXTfzST%K2Qn90qAE}b9O`fme0z| zvO{OjUb<}RG-+JYv~Sfo8e9^iM3U#5Z*Je_E$ZesJm=YMM(U8M&hl&Ac*2W)S9&iw z%dVlJj9F^M_=BN}`RMu2A zHJ@dT(HjUM#cr|^X*M|hT6%f_Zj|VN*P<7n@qLSP88Olc=!3c^dLsmDEOgzzVMNn~oiBmKT+6&qt08^5le9$+9}a62cf}+*=)< z#|$XDxr)Zjn;9WkOq2xZV`QBq0o;@|UkMk2)agZXl?*(RzlvC$zFM+O1+4E{R~LE6 z$aW=9Cz9aHBaY$(*2jfQo;>#2NR%VuII282M!JNFV{}#`vxz>wdpkLWd4qiz9UVt* zN#u?XGX@ziqi(OCfQej`rFz`{VmP0jIkyiV+z%9Y|Ni|bFe3NFxF?ny8k(M-Ur?X1 z?Yg$F#~f}`4GTRpmQU5=a8fEZ$b`92!fxya8@pZ=6{A(vtZYKf%?_*HzyE+=TaeL= zdRfk~)`Rcht0Egk-?df0l1;siFX3y#ws3m2CF&o5h_frBMQ_xS`udjUZhDais=0XO z(v`lxu+4pN-`B2kV@6Yu-`9DRh2suCsjIX7$i5jrlT61NRTDUHIc7FTqo#@)4FJQ}d z(*3=5t&f^h(g(n_p6H$gRh~jAbE*OON8W0N=QUS7z)fV zo4z68IxUHtugijqv|z+B7n*4Y>-@cECja`uA-%Wpd@0Ci1GU3w0^K8x5NW$W5y`q? z(DZM1d)9Vl^R-F^oq5icBBZe2#M;_28K{ZZL&N>gh>*}3q9P(Dq9fuYeuoGDgQ~s= zoVd#^x5hj8%b$PVzc26;`9*&|^G;8nJ%iK$1r85S(tCDwWe3D;bYjoZFjtx9tqXDU_cAUs?@xq8Uh~S8E9Nt=UcK0R;e4#fREO6q5cfkJ5f(+*TS}wga?C$>z$APk zf974Fp%nH47qXQDP*Bg~VZ?d|1|+U37uBe(tqQV8&2APs`IWo~F-{x?qO2_YE?-8- zoyNt^<|>G=Gu-&Sx4D_{yn;DC`|BIa%bqvLClya!N~C?_F#s;|ozmbiKt`_I%oocT z5z+)>BykkM6qd)iBdom8($N9qc{%o!=q=UQdqz8=Tdp%j@WT-kTBA@EHIK45+89T_A(VcQ&Wk38yXU!1Kvf)q98oD4typmjB;K)sUIqtQ%&k?LnGsE zn(kp!({tc^yG6M&mp0RATxOC=suVYY3C%No`mBSIt(G6h|4Po010$A7zhk5%%flX^ z@NGRMDvbNu`F-+%ipHeR5h!W9mG_o+Yjo4tptdw|o!^F4et_zH-} z{Ef-TWot=YJK*USp0+QQ>(<9SAM>JAavq-q*3t~WeFl49Z$4itW_ zu$0=_rbhY%q?44==I6|n;UG9^470%(y3WEW$IJ&Un;a2+p5j_yO*AMlpW*((3;Oq( zn)34EqU?(~gK-M|{$S}5rGA%Z`jY0ahlmhH>S*dT|B$YQB^&=47GRamFl|_l!~RcYQf2gQIr)GIaW|FmZE&; z@G1q#$r;At^8!ED*E=i5t51Alc;d?qB72x~NCAp~h^Wms@U0BbgWqY8OvZ_u*-6ij zub`9c6(%`mNpVe4!MJ!_Gi&(@P=O)_Y)cu;CYj%^v%QzVlIWAb@fKY#jp);yLSAMB zkc(MT`Zy9d)bCxmppA%ibd+H-Q}{{>o}>lqsPrr;J%;002hSScgU5s=XG4mDSFiGh~ zNsD(3xh|rwWbv0jxN62R{S8u$hp`+n90OX;E#0TzP7#6vmUv0ZBD&REKb`QCkMbSq zuT|Bw%BG{%))u|jDNZ%wv?5hEHY-!dZN@bXS=q^1hg8K5LKF~DF%^p2ix&eiC91!F zV4!~h3Y`cm4LR_^!I_yi^UMOqzY(jbcWNHji(lPjLZlky8oh=A#z5QfC6*ZP?-MH+ zELf>+w9fMq=Pgs$Y2`8OVwi)-ArPyY>J5w@a*1T3hOI$aO92EIj&4c1puIuP94i*PZVXWeYze*Wz+ea_7El|CJ^}3*QIi?RO^5`tqhgUmhYpWO(!wjxMR4QR ziKJMhtLw@Y*9`!E{moh+n+h;$g_B?e3XS+1L*d+Y{RaUPH{yWs@%VY3SV?QQL*_u` zQI#WV<05G^yfBquOQX$uqzq_~g z`NOgj3&n*C;Ert)g!AJ=j1!A3?>$5fXJvgh2yd_Mh%sc zT3)Yw46ZyE-iuFMp;kdtz*bbHDkv_U3?)4;h$6%QEq?%yZjo-sBv$-8^`c}2ynErBY< zIaYt7&eVJNOZ`idir8injrc|eDu+mMD6-8M^03v^g}1fQJ&~#$5s*kE2ik~b@n;|x zUJb|=>ge@3$nrihJIZ(PmxF+E0vZv^mtAXGi;MNUXV3b5qBZ?Nsv0PfV7AMoG(83MZA2uC^#}2d{HGbjcv?iJq zQK?61)-z--!=M1RuyACc@M78r-7fp(^<{UIZ}Pkhnaq&r>MFRaib@_3l;9Q7wMt&X zBr(e9BgSkIW>@cj`SjV94kw70P&m|2-yw8Lz68ufFf}=$CML%st`hJ4&30nYzk)vS z9ECy4%bz~&?&jySCMES2=vos&i9QTnL(kZUlo0@mfXS&6GY8<&0s!`aHNb%_8654A ze&D017EiJGc8=9ihK2?P{`%{8#(&@^a>Ru^#-H#RnRe0S+`3ws!^BYx47|bz;@+Ul z?N+m}K(H3Mc$EKhfK9E7k;RO@++?D^CV$xt6oAQ&#H8ZOfP(vfi#@#OLqUQ;x z>Y9r3lRPoFh%DlcsJ6s4fHK_+>k-+r>^kW@az_=9@q%}HaNfGHwfSZL@PNp#ytb~5 z&KtR^3K5)JcQ(^3*@AQQ#pvq7RwLYDPFSZNb@M_PK1m(IhUo-5I!?E^vKO5?3hj1? zkJHbwMd_XjhwEdvW7kszYwUz$#K;DYFd0I2I%fbyde+8Nh`9|WO#l<(w6jpyHCWv`}gkVqDtwSmVw0x;bafe*I zC&4fxT{f?d`UTv=$cyT94xS0mjmX@0=lqa!sHnXFVZIuCK?~WJfJ(>E(BSZ!IZ!XF zlg%@Xpo~2uh6;R)?h5fd01&jfF5>_NT`jjh&3-erwM|V_?Qz{R5(~0O`dD=2YuIar zJhRz|3lQEIS~wJr4u#Y{lW}Uqxx{&6}&Li;Ip=w$e(UYsO3(G0(3%jMO5^LI$mP zMDD?q!Hxw#hP;Ht8dQ9UmDPjyx1XPQ+yQ z2ml_AB$e;Q^r$C5zsD&n1vSTwc191GqtQP){$_6Jt?=7RqaEjLB)blza`jOJ(Jd)B zCq{7Pl{UfzWPQXRZ3qI4+F{Pf#hA0vgM9qBv@}D0OP%-R$vpZ=R>|7^*N{xBmW*9u z>a>4B(-B>3%n^nIV_YrY&bElCh_zs?T^9)<8xBmYUQgtsBgB)bSlOne>h*+4+G6pD zF-!cX9}rZz9hGDQ^79M~L%S040jEJbfqJk_$Z5n>{C*M~Q=lIhLnuQIJ(9+S&=zUq zg+k`9S60?kx3?QVMIQRL(MUug=ov&jZcMv@prUG8j8qaF@J{bxwkZp#z|7>Q5hvxU z-uVW`BU@W^I@Kf_U4$j(qDW_;Z@OVUpuUB@rwiA+cSfriGAeDeqXcRm9HdB5?yR7o z40gy>CVMWBu!CtKdWv-2EjX7*on3Rk;pWk_WF;v4^$3`0Eq9Sf3)EI?#{qafjA_XK zvO1r24DVPea1nxS9yAsOL-P;=K@G&D*5JdUt|%cqVH{iFxQq#RE?^3@#D4GIEq~b9 z%%jt9{VAj8CJ?5PgmE&82t}oZRj*V#Z~8`OvZ#GLtF4Nz5;FM3c)kKg9>cYi*4CDm zrly96Q6FYmJSGk@VtV+HfQh?%_sKyC=dvwL(4dM>xMx9MZ2mE(CEkm1|Ngzd9*gl@ z0cT}}E99k!>%`*jd)uHTz^~yr@spWpb}*Qj#IjX&(~A03sKMRggMSq6&-`FI|Ef`?-W)*7mp3#eBvER?HId5Rdo5q|(H5$I;< zCFNPvvqG*EjxVKI2uP??qar(ppUm_Wm)KWCB0EL($xeh1raNxLM-e6&Dq%A`o2`E% zH?2c1?(;a^clx`Y>m;Hfe_6e^X9g;9g`Pz;j@XG2Tz7_s-JQ6`vJ_gk&^S!n;h~Cm zcyJhlxPm_9#c(doEgkK>mu}kd{krJa8T9m_PJRtNGxlV^(%SIYy=3*W+L1=&J7&Mv z)mAYhvx5Apbr-B_ezLvu>C^iUaBeATA~Kpw`J^|dn9#;pJqgM?s%EXwcn9l}Idv^Q z@pTMPn39F3_$0bNJhTtlsruBHPPQsls?-2LE2-rx#)svqc#oLSrl;4WZ;NB)>A2~9 zv`zRI>Xk%{?%wOyt^-B(H}by^A4o{3c#mK+SScVX(VsnihQsS;;QLSq&i5hBU)Zsg@xSE1J2DA?e^3Km;ScrpW6en;K=d=tShTAnFPI_oDDbtOg+P;^dzBl$VfeK{v9_+xVyBc-5{htt1phI&DVX`BQ+T$NB0|?tHXOJaKyOJ)qdR*f>RCB) zop~{=QB)H0T3PAD5%+?50^m5=3K;?7s4&iQq+Bbm50{fyy-W2`Rn`S({I`$PYEkQ( zm**J`Y{f}bcLHp2E>}NJ(Q(fq2TnrR@}k%As{qgHPXA!hD$gvj93CBxBFoW{Nh&p# zmV~c-kbPBI|A|bO_|>?KVaW-7&6gJOwC~<-?L>U_1#h$C)rLLehT5Te{*xNNxVX6V zW^Q6~eEcbzcMq1r(sKw3#xsT;%3i#fo^kDA^fXl!feAmZex_d>f{ruW+gh)qu-8Q z`L#OEvKEc0x&>fSTm+l$11p3NQN!Z$se(vproUXTdUMf01@^%^o*B&}i7oa|zHs5@ zO*E<}5;1&TxHf*#!|D7a1+d2`nK0B06A~-la~M9U5iU~|9D{!bZTnKrZgcsY!fTgrC^52x7*yCo2nNp7s~>biXS#*MzK#7`#F zndju%H@z1xQT@@!jX5RPu3bHME{VC$P|Lv1qjCg#F*lS$U_~JI**>Lc=Baw;812~u zF7hi%UKFqx?S-RI>ay&T=_RJ{$#&~CWZB$Dt!%nR{whD}5xBx+ZLLUM=EQx-ZT@O6 z+;lwvb)q^(LT}Q_hES1m;uk5zf+7?B>bW)jNcQTRxv}xd$&_tMygfvrlkK4lv+m~9 z_6?5=4N=ecit&cw;CJq`3nzstWseh*k<+|naZ9py&%A3S+7G`zB+^5_`#kDLR} z0{W-OGYBEkKw~Ia5Sb9BAo3=ag)RPt;NJWrePNN;B4o6LJ`DMJU-mUi(Vc=fu6@>T z%XwPkK8@C!FP7ZIamNOk0h&hLNC^A2=Bnfkq{NXfF#q~GeX5yZU0$x%A?1oZSixvQ zd5<(hQ)5GGn`@pEmt+Hw<}DlIIHD9*_>|ZJS6QubrnRk^TB%~zG6uJi0F{AoE{$4d ziIN z&2n9ASm-nnTu-W(ww7CTVt*o~TaI6?ZiHQ6)_vao7sSzmAZYE(`%{HM5D+u7_z?19 zSP6}VcAf6-MMX!)&IB^vBb!Dy6LOr^OK}{q)y!Tq5;=7Wh9nyClgtTAf-%9hd7tGT zQdTdngDVro>xwtmz>%zk!p&is^{F(&v%RfRmzwynyt8ASg-o>K?iJQM>#)O|pqQ{n zj4;l~+gHI;{z7Ea5*BM~bn|4sGAzVb3#f+GlY2<}RZ%Yfi9QXO)Pi^}8*Z&Q$%`n> zz}2#*?lzcCU$hpp0zXiLIyu=tAg@jA^jP-6(20@;%3>oXLYw{|JW4wC`+uH19vT#~ znF$^of&d>Jd^I#k+3iSfa~%Rb-`p%~RSQ2hxdR4lIlusN^7f?M-Q|L4_~ZU>nXIs9 z0Hdhbh(;S4`~@b-$_@Zp@P}60V^DSqrHQ^NO}!93W6w9>)bh{9x5wU%0bAX zn3oB?uQ2+k;DbcyyLzREdF*ZN>a=S^Cw~y&HNVK!yP~aJI!5g;ON}G#Tr51uA)v_p z7Zn(rM9x)q(TH?=kM*un4PRK@2DMf#)_$Bm&Avr%HRB@V5zIgYfQext28^3N()wNw zi(^Z%A@+hAI@hgA8f2eT&YwL3z;5nVAaUw@3t&*}w#>QT{sQwV6~o^+M3l_(I&PfW zbR)zlgt4!#ZYE>qcX=*5I&^C2Jf63ab#*ls=#v6~sZd{~zC{Vdo(1;r!;pYDv<|QW zB*V@B6tfqHNVm3~P2-;UL73W{mBq~J#_``N$_A4njKRQ|NkeixI@h&u&hUHIK3rOo z88f5pDr_7DPTZ@fEdu3ojlNzPF32`gZMIXmCU_)P0`0LX6>b*0@7gtsRxD7TM4i+0 z^w`*|m(K?7-+T0EU;s7{I`Aox(h~?lQcZu|XC3&AlZcK;RHvhbj(ezPRPm)UzDP_x zW!_=`z|_fOU{9ALARE1Z-wOzst zY6`KzpgqpEK09?3Dl084K>NaLVYhUg0t&YaTn#TIJ_O7#6uNfbG`|UT()MAj{u&et# zWJ@^jup+$|op;;PQc^;NH9Ag-1?MoTCqg#&hzv|UA#T_(|A?$&(lxCbS4=IomIE;C zV)%--7r#Tjvlh}=P;YE7#HxaI?!LMR9FCl%EQ_)dPEJk^9y~*D21|7{;Qeay3TYT* zx#i~_9&K)jYScabnIaMhNzwql`|#fUFL@Ej%hw}a`N@05s@`Y6BFcOkwyfOq!y7C`B`fP zfnTXv4EJ`c7#)52@=-)if1z=zk>wZmM0yFhOp~D&7gj!ML9_t^e;Ff8Mr8Wzl=_~& zx_ml0H@*z`xD5Z!b=WCj&Tpl2jxb`LFR_mRh!HW9aZ@_4>Q!hceO!|DChQ8W#T4=u z-p)8x*M$i`O$BWUy+uNlAwG2TGB3RjJn$ zyKN-F+G}SnT)1=F`DAy$|M{mo-@sC^8@&v4-y3_SH+C(Zo*uiYdpfOXZV=#_47Q3V zz@MN`URs2$jg)Uuy=3OY@1bTGGEtn$C>gVjn!y*y6qw%SurP+i^6%C+blKu8af#ug z2{uCoYBu(Yi;xJ6o_+PTX{LlaV^Pj){_U9kZ1}0v=ng2*Qt7Ib#Sf~xV*do>!;Qr7BM?#W5fE9*;#aT9(L1@!hbI#G%!hw1Rpv1 zRKJ)n;ouqjaz_N*dgY?R^75J*5L#F@Wl=mQoz}t(n`FyW!RqS9MwtOjv=o8hD1z`X z;ZZRcA0Rq0%;j_Rh1T#D$ybDUs#Da(pi9)npy6t(S&VwKivaZ|*AE7t*VoIhN_ALy zicaS9EIvZ!TB3g16Dr@~$=NRc{`ZSP9C%4MDx$B1^wfyT(1@ZUG-r2t z1(S{8ULXVIoCMJq&J2g9OG_Tv_z9GSHO3gDi#R{s9{w(Y)RdJ{h(*hn5szrX>fFO zk@_dS3M5{t;eb0dU|bh?|wqADwm;mvJ}>a=mp_F_=Q$u-rcVEr+q&Py#| zy>0zD1qsTL^&#qtd%WtO)ne-EIxYLEt%aSyjJ8J!LK_2wj2nCE|n zt;UEOo8BDQ;(kq!*E_r}VcnCSZ z9^!@)6&rU)hg7VISMv%B%J9`~Z72-kQz#ctWbosL#;JR-_Y(MIb_t_67~{H&pv~Tk zXS?))<|A}~BH847T}^=bPsMhMH$V+0H)K=pE%9%hYWB|)zDXaQB!6U=#CdEWy`+ph zMhwf#zGsKto(JSoJr^RlGKQC%NISA3VgdewjrC6@3Pfgv!VX!)$Hq0xxqqJV8n!9L zr#q}h>`sxT0qU5m@q2}w>WS>c_7*HPr6ksp$lm8Ru@W&6k&g40NEg#@#b|1JX8z6M z(#mppNiM<$%%jRH;zG5or7{B*5fvDXPZp)U<#XP?i?b8wNLfT9A#Hn1_(kpPQQvJZN@iqL^;VxGIGsH(EG zjFA5b$L{Ux17??cQvSdVvl|RnXj>M680%M7kU7+nlN+l)rNPAl3mhT?b+PB>EsFMj z`0)p~yWd~!Q-RJQwNXmJLt@8uC!jq12)L3VHpLQ)L1dO?n~k}}b;OJY1^ETrRxk(8 zsr)Lp*{?x;o=`(!6d34^hcFXipAa)4C;g)IkI6x1?<*{?sAVRt7&tVGo$Kl8>M|O1 zH&R^WuIU=v+tVt$&CS$muw(2VHH#vQxZ_C{lyE{C>W!cdk0OGKI~a?tO;!p}@)B~; zZ&*oy>@gHO;{)Y)X(?>{;>V9E$O^f|G;U!W_xJPi%E}s83x{uQ_L?`qe>XKYc+NBh z@k)SVjzZbc8-_;=zev?y9i)dtadP7>KYw#$l^B2S4awWFF>h?0_MPeHc zIh2kg=dhDc`|9L;!s(z8)}jdc1#}aO#yR^;x1hs=?Hwb|6*NkWW`qAog?+Qv6e2hLD&fIFE$VTJLVKp#`$r378`&JQ^%S3QW4VSs#=5NgN2JsrytXjnl zCRfXxEVMZ3978jGK04lMw`1!%&m8vF(~V8=hj`7jHcMa=x|&-FORWZG(?}|a-)dLc z)uwCG8yQVar&#?Xm=B{6d@pzDNs-;BJ30WFm*xLZI<~}1$OyS|fBQR9#S7J!R2kJ<{OPibI$u+dc!Ga)?+ z85TKBYrLD9Ty52OOSB*ZW6!}#R?$6uy0hcz)$2F9y4q46I{Y6w2dQxGymu3kX89=g zvjI_8+LdlSUtAYYR50dzF}YvXSN;-aT?Wjs+~@(Z+1*K<-l(w1vLWIwxx}cb3<^;v zZ4dO6dqSvqX5_y@UvpRQD+VjeWw9k;2recIKaGDe&9${M@t&5%mS?;ayssxJ$GLJ! zacA-=)>pivi-g%D6l`d4XmIewtAPPysYgUniR6Cx;QoVqFyMd6fyX=zCu}@<@=BEV zoSBGd;ByEL**ktmCttoC2YD_o0V08}h&c`?5=Hp@lgGj zNPBf~c%r~!dgOXkgpzc=%rE-8EFcWGf!BF zgU`k;*Fip)Rdnf?SykE1x=5in+yQ}rU&yT?#@Eei1;it6=~W|Jq7R0!$^TL>11{ls zbUoc&t!rp&30n|r1B=M(DDnuL_u$XKYrzfaf6=m3%i#Gm zVpio;I@v(oig-g6(>|zUu4_>-X~<1~9@M+};v9%7=|IXGmd9e#dqPa7h3Bzn~J(N{NxQ=AN|rtEZ^ zA}E|pVkh7ya1n3c<3bWL1RBoH&M>B@2zQu;^%{=w*;8{$B$-Gmy?Ql@|I1<{7d@Dm zpflCd5=xZ0itym(uXQ8B5|4u;!S@iyaN^M?eMmz?XXm9$H>i<}`QSHi-?`e?j`dZH z6=*a4lEp^S@?K;ikyvH*!Ue22tZho_5U^ai(&u*h(gm_QUESBO-@2KcOm}xDOqr`h z)zI;P_!fi*)s=U2NQC8VC^(Yr3g9}SCs|tn!ua?jC>k5HKYh|qt?$tA)a2aU z$}-@aq4Y@oV*9oB0S%M67aH;v3}gAC;+E!=NaY|rknARp=XRQ2z^Cdet4d4kd7-`K z9BFvC<@9#>yT)%DyzME7f zQc`}i`y8gcrl{Dy&FB?F6QJLYm^X13I5WMS3YWwxwxfer(4?ERVQI0 z0|(a3+Cpc~a4}gA93Oqr)B$*buGG1DbaW6Z#vBy-hXU50B3?$KycT-gxC#lN>rTuu z8;8>AX=mrtXYi7|u?m+gvRSBLE%tU@d7fQmN40Oky_h>r17n97M7OMiJ)zG2h`tmC z3RFJfxtSBrQE25(@-*CBv~4v2bI%kc)OE~a5Mlyc+2c0i}`Q_)IO|{1T_uqf>&8=HkuJp2kM?JW^G2#TtBdsM?l(jas zi!#KB37cY|PjUDNJVH*iRgek(slRSk`8r@OkYUWM$t`uz|8-+%x2fB)Bi{TCfL zNk{$6ZvF7X4|ngzN>CQh-nw!9#tq)fpzfN{iy&b(=x!b?y6&>AzM&w`xhX`dD|2rE z7eIIs{E#SqXo$*{v;jAO@-Jy=kWE8NrOAY~LxJ_-t}x*MX!<&m*90258k6fk=Ar#| zm5l(7nsn9w%$aWH8{acV*7L1Iw>mU~6PEM> zWn>6O33pOXmO2RMmb{spnH?HL)EQvChYvt`_wV1kPs3&UgHgfv^!f9lA%dq_t)h8j z1}a2p;KcarajI~s4#ZuJj809;kXy@2m=xTI79e~sx+ysG>RAgC(btDL=KL?OERbiy zEyOHI-?aMCSzOp&)HWu(Salz)M2fyb5&*^GyYPUx@EqDOUt)~v41u#ao9_{~+KcaBI{)3^;vlbhP zRf;Zf8qP6thkK|?Fc`EtN3K|%X5$N#YNf{ti-h{M$Ic}B%(~%IbPpMS&BdY{xUl2h zd%3}lkZRqbAqXffb%^jFm=zM;o}4eq2bYxK&FTNmd%!q*UtGhaoQ!D+RkQ@A#QOCS zBSK?0OXBA^i^$x`hk`k{XZX<3aW+tAa@S|hT)vD&Vco}@H$D9qd%L>Y+iPoe3Nm%Z zZuvk8dysK*VXh?x)kOei)K(DE6E&|aBb}2|(;nee*m>(5gjg8<*#+ipEPpVMm6t34 z1~SfGGK_(@!zYzBHDkJBKiO?Y;L%7-$o6YV2#5{*m_;Pdg2iR6gWcUvpF}mm`XRfc zBjlnUKe2zrBgVsr5AQ!9fQk$hn#C>k<>SGp*34QlOU*1%6%Eeq8+PyBlgE>j)Lu~e z8u3!P^Kcb;p-W;ZVP<&FtQxy4Y2LN$m1Hy`gn}z8E#-=0Ht?&UCbJrJ=iT|dwiE;h z9SZT*nhk5?VJ3NMsv)()ci^mfKQ=ZqCk_0_Q8e5jGQ?0~Ceu(G-5PP-g) zD3pqn=`i2#ET+KGs0M)Mt9JqqU`W~ZDq>rz%u=l-L{k&cih1{y%mg_)Hhw_Ej*E-r z$z=apL9zdD1ksigGc@k&(~v$x4fn}w8bJoT!^|IM^VQdfG+@F7a8(Mi0Ow~K`m z!6pJh+QCG@&IAuY{84whwTF6#3yEk1MZGy11m9NIq+KpX8BaH)GA5$pF0IclJ9}D^>8GKZj+5 zjEPdLULZSlSoS;jU9vCY1@VK&fdYp=(}%EjJl*-%*A@X#W`2i;tX#;3WSedqcdZW} z85x$5i)~cr?#6vEch1ScKR8WeYp_#z(_iKMWyaC#1+lH@y4@|q^Buvj z0T9CiaMG&kYRROjhI)^j8uKJ%9eHP!t6*9XYz8wVI}O3uJp@|= zUCUgvva;jjt!?{oM2sAUbISlZd>(ru^_Fh2U%rBmgC;WLllC?3^FEy}5K{a^nP|DX zkrh+XB<)VS*@INE=c!>Y;07^X!NkQeAqcq)@Ckjh%+=|eW8;A@VvaJr-})zNs^{O# z0DUaUo125Qj!cq%1;J-5GM1KBR?{))0X-OZICh;~md5h6_>*3NLo#m5%fm608+jQO zC_l`0q!*SI*oII!d}yd^{FF4!U7%665phf=*13ZJ(7W*orh|+M+F|`7(VMB#S6#v& zjE=>eEWwAhQU2%D)O-2?I$uK^h2ze!^#&DGZ=-Yx_p`bVYq!5|e{q?oh*ppZZ3jSI zRSIni{|0Q&aiKnKZ;dUHg@iC$DqhegHjify=xKmy#wVRnm$K;m3kF0#-B$N==gw*V!v z+5yg^QU%ORH5?Tk?1gpoNi+4!d?l6(1?47BzN6KG+vL*6>_pCeR7X%>7xB*9cZ&F3>HF*;LgmdssW18UbWb7(TrFq zC~GcHW3;Hu$eVv&GkZlZBIaUvVYOv#ZPrKkkjjxO3MpZJH=Ge-gN~_WY-$?ejFCSi zV@OQ!NGW^L!g_4cb(S@ztk~7a$kwb7@6ovhl}dIXvu9W|F92eKtQ>mXiFsWOx`=9_<+L z>yfgO;%01(W|F}=R3%pKS!4X^lXf>)K&KKI-e!UA?vBAYl0|$E7)E@PM<$~N z3V?-xS&;+MZML*jR9FS)T&wEp`ueuE&h|@}F#$KO_L-0N{K3JR!ZUXer}$jt4&qFE zvcs;$`3|F|VPoJDIpt_(??u?5-V5i?oNZ}I^EN;UOu`~844kg$1Fj$vj+i-ty5Na| zNz1DHkU8XSWknivdWwP^jeXoXt8RAID4k3V|BF(gS!%PGbbp@@L+khr#*P>H+sR3` z-coo(EQ7;`+{P1d$|+}8mzKF!>gI+PD@;}xxYelcn}wYKrNs&YE|}Zj|AY~lDe}9i zrq38Eb&~k~_kaES|E!+<=l}fkU;p>--~MFv&;Eh2(a9-R+`*zRuW;$2CN?7`f|1Z< z^2PG&pU|<-JG%zfR2(1@dT`}LjMLa??1Ba1BvL0y|Gf{c7LNpL3#`S1FG6$i3$T+q z;pBf))+z7=cXrjZ>Qp|?NI<$)gjYahN>o}>QE7*5%`GC1IuVJ8G(hcRH_gPY<3-4K+Fzu%au=+ zhlm+cMySBqE_5A8rT_hhw{OYC%uh^=jlXzK0pG)i4<9^uU?i3Dy!K`xl2Wr0E%7_? z6kW~Pe*5i@KU^h%@iOSzEIJHw7m{~;Y=SP#iwp1G*>P=eub7Ub#!sL()Je`r-|fU? z`+7%&9M)58>U>dC8p$4u`UHCkgN6AGZ&YPQ{FqsFVCOIR9v81w~Ofe!C zE_*7RNXAQ%;n{)JbJ(!MgJW_nd3mnF<~JuMLbU03BaCUMMIC{3l$5AFlG#T;u)iJZ zD}Jft+_|fCRR!}2^KQyAk~zCzA9G`~D5*&BCDeoJISwwct-K9iE>1U>b95xU_W*$c zzyZCaH;fD+&H5U`9o^On@;$F|aXPdVqhx1bw8;3&K&FeGkQ?Iac09CRw>@m}jFJk2IUU2Q8J^kn`OgbHMCs`q9hAr?L8yj1sqL&#w66W^%%~d;aRrE^Btj5kt zx~y-W%k-dn47E=oVG5@yb;UX~5oZ!uUajPW^SZc?>W6M;XJF&Fb#yS<0N#jRP?h-w z;pw@+3C-uOG`2NxMZkejFjmC_UAlitEZ&iUNaKTcvBh7&dHNxNIk>32rnbFJE&>rn zRJ$3<==pO!J=d>)1B$yt7By`=VI*GPCvG=y-Mo48`i&dcu0fWwf)qJUr25>s&d#>h zQ>XN|#Jo`pXNf`%VTVX#sRi0Wbp`~%a(i1dhb=DfPJ6CJ-`qSp(#VvvzjRvb40pg? zDm2VtWrC=SFeev`pl}bm@#yz#Z;XWDT26DG5%Aoc0W3InT#0AiEt&LGcN!cZPcfqr ziEs~}lG8ZW{i2gPs8WYYT(6hc4{G%^cM;#hqlanf5 zGUo7+7y!LZ87g&B^;}pOV{Raz55F=p@_K@raATVQZ@%X(m5Pf?dM`7(VpJ#ze5`Oa zZIhD|@Q<_1fF~#Hd?@dtN@xYHdzo*HwN7Wp#$Uc19vK{D&LVF@armn-b3`P;z%tcy zYh^Vs|FY^97##yp?0188@1n83}M8UN( zAHpAWh1L+K!p&&P*#^i4wLQhfpFRO(xf!-)yo6D{VLmcyZpU*Uj?w&7U_Br@7n3j; zOYR)t{Jg5lM#KQbce=fexqPgDbL-~!-+zCXM6?kUv--?GefQlRiWnSscyi|sI)+a( zn!0|?ZnI>_$U8SU!l%iGYeTfzSW39gy!<10ZBq*3Y|?;0=D3Tj7YVQ_D6sbnYtdFzGCL%Ck9D16=*y^+M}-2%akfNt;yZXBKx?Yj z0?(?9Z0ch=vp$Myos-c-wMbQUQ)6u%F&uL?gcPE4Z-#ilRKur;b>IikMKe)b&OA@S zI_5-6=f!yUZh~5akjE)j(WGL)o(XDU5D{?|Y(1d1a@^UiFmSxzn6|RAuX9ZjP9-1G z7;{y{)^aT+%t$>ruxlWCL8J<|5cHv*MtvA}j13Hc>zL9mN1n?x>2`#4f$6wQG&%RY z!eTY7tnrLl?c?L}fVfa73%Lo7Hc3|iY~^2Dj!hC~Yoo&H8vz1897fr=aUR3(=!TAu zwzk&RM4*r%QDRG4+Vr$k2J4*YOGDu^Mg!SdO+LhGr({$%>68@p`=CA}w$c(~o;uca z>F5-;rg@%Kg%#yU*Tdh?R%5?`Gjz+Zr2~0gTr}-rUOqWy_2K zbs@%{kbvTEs(UE2LM~;=5JyB21yca2I?PX3+e~*{WGW6lW_w_srU#~}1L1+D3S-5P zE;L$=IMEc@C9H z=jWD6G)m=@r+X*H9G7Okt;oOC6);FIUS{?SopAo-en`n0QKlRZN5CnosqwuiwgOq{S=4LuT}>UGSNohXVl9GNZ`?}Y=v?-!Dnzyl z?}FwV+nQO0@WE`cd8ZePES**jKema$WL)stnw^@7s3RMBa`KVB4@gyb?f`B9-at~; z8l|iRfZ1oFWqcq*bVnz0aG{o(>EdzuHr8j~OixV{|2U>tMa}+VvLGWPa^=c!+K8i; z^bQO2ro+OQ@$T2JN5@jKnHlvj9zUk6f|>O9*A{{&|N)ap@Y4HO(3Ild?cH|d4!E9DYcS@^TS`X{!r68J2KjyK`^#( zfTtp4JpuxK1j))h29?MBygKG;d(D)~^0H)I_3zbH0Yz{bsW2h(h!{lW2%b*`GaL_L zcQ(0CsV0m(u35*Nuc7LVnswryYiG_RR>GO`TKc3zO0LrL|8gnDetv}?nYcc^R~YPo zOFm7A(^=V3I)aww=hxL?tEp=2?vA=YstZ7}*z{~Ii;i|EOZrT+cUpcwBN$73o!p78 zOv$cqu!Ch6>vmj(cer%v(&ckqst?E1(%Qy{7;@sJ?aXqNk(4U8iNBdO3aG-j7YARaKofm>js_W74 zWE_S2uV3H*>YEz5VhsIweM3>97d!ZHR$EN?2hjiy;VqFD4DTj)nBI88FGNG!ZPI%o zWOK#=AHWzJ9UK~AzMgs0*%{+{BP8eQg&Jj?xK=%Izdn(n-4BTd_}TNBuFuo>#Z;F! zLLJ8V{QM?y3;|f=kLKn=)zF|}e9sWRXoyP8SFc{ku)lczl74>AabRqb#j?F0xh!A8 znK_5opv?C(-{aXcyQJgwD4BisY?fGRXeiUNr3JbmnPXyplz=}}%t%^)6s`!+C{#pY z(3X?Z`*;vg7~)%VkayC&g!I)`iA;70yau`zQr$&5V#Qb6vE8Q}ip^UmC>`gKv2q@lKF$IW%Zab1Z8%+}FFx6E1=c=VVEK{P#T^lG9eKU5gGZ-VMho*7 z3mfZkr2^*b@xfAs4!{&Q(JP+JjcW!#AU7FchU$BPmS_IdDRUWOq6-(UFfLubj7`39 zo~{H$LtqtzReYfvN!&b70v`p2!H6=roZ=HiTsg;V!hIy4LLcSECMz$fhbz!B=H=DE zeZ_1jy+S+-e}^XPkz`KvMar=*Mf@3#g-1q&8A;~b$&=CBD>`0Wym}QSM@NN)4GqRM>XrV<8l;TN zgCQ*`-P*Lbvc5oxl*_HwVSI(FHBvv+4(|dda}kX{TmudE9G(KN#Oczb#oouoDPdKxljF!e;$q}C^Yi^UJ_40>);}M?fM^kH zqh)D}Cm}Xk+SqkoIyzcqT~34W;xx@tI>)G%!wFaGGy8EaS~x;!$Wj0fA-+Pix+ZmW_qV4oin)zgTAmxXBR>{WuGW- zLJhU%NDVR16iS2pqEFbXm;5*f5ibQM#7Dgt8X6g!c)hS_QH0t056hF2!^5K^fBiKy zI6XT#q23s0EiBLymbfWukBAbr8G#0+epz=eWtsG#@~~hE<{oZsuahO`ox)+}%Da** zD#rQsTCo|!6gM=Q(a_pr&ZDZ7Ul5!aSw*zcjh92vrKp(odbX$MiqmLA`{=kr6eC!& zszzE3*hK84zk)CV%kiPu)CSh3`|NG7N1_@@$eJzcLQX!lwH1@#yUo zfg76@B5L&f!tBiR7n2kCNxEDxQ;r^Es1NxbR>PpToQ4LYQ+#-+w|LmEwp45M1V9 z2|$g;&_soYN9PE99cZiQv8&f#V<#-rGn(-Qmot&_T3E?~=@e#;g3}IUik%19|1JiTis?ZgX6{%wN2^PVQmVvz9 z%)fsJZ*P=;u(3J+CZ>kMtmBu^6P3fd4gedg4&cPVb;eV%IPY^pDFUg1fv5dXpFVx| z^jZJ20YfS_TFgHF^WJap?37tB;p!!QDqO8#c;w{^^%d_#nV?mJ>hki6`jd``!Xl$5 zuAS!snjvL$xOh@%CB@|x)m3rr!$UMRl_GzkRn{(XeIa;*7Ah;Vz6TLQ7rdNa!^F|a zd>apYH8q^t?`k?^m6eeN;j{dhPPMzKq*2KUyygpBKtM41Yo%(#2X+>zQd zaFE9EMr9p%U+4aT=ZLc^D(p;{yqxe&Z%lZ^qYsWYYtAF`^JVl450O48=4qbOQ6|QM z0{Lsbz0ItB>mHqE#dp1fe2acb-ct|>+=J!I5T(0E{))^P)gGQ*Jz{m_8Tj3^%!`%G zZ7(W5QX^13X@op7buBH--(Zi?8x0Me`#fsSC(hMP@U2A@>m_rUYG@PZ2mMbFCmv;8 zz8b&E%UV$(>t|i-mS|C)f_qlOotT=|10dTIQ9Fvs&&`Gg$ju{_F?>Qoi21eRxsg%8 z7wo}{7ySdHBLFM$o+#7+nL9pAFE>5#0%v7=7nH{yFZb_1c*xI#dk-Gq#vVR=!g%_$zh9MmPVKEYacuN4 z+`NV86LDn8UmDE~F^0i}V`DQjA3u5qC=TE(#B2)q0rd)9L0^JYRb$a(ZMqeLdgk*^TvFi|aTt4HEu+f-MkcNV;Acs8-S=UZB zIhQW-D4;O1Dzffev_AzZAz_y~es_SFW5tXa6qc(Hc_cQpz%Ft`L3|Imp|rZXql4K~>`J;K&^fLO zRcDW?{Cpe@o=E?5aDdrBf5jT^g?ohk z8F)@OG(7*t_Y!xJ{1W-0d~o6VDzqM4nM@j9|XK4M?QxXhc&|9f|hn(8zw!Uzfxjs%Y;+OSJ~up&nwYN%(2 zA~VSD3%)LG>=ghs5GXD&JQe#E8ZPf|&s)?4kI>X?1{Au5PdJZ0UZa@eD%y92AgY~Q zG|my+nEpp4i@-@=%-)3U)`jTuj0wHDll*8#Fduw#6J5ZlMx|C_byeFvH@CF3x2K3A zfB5d`fE&jG*V1yU!N;WZVY(X7q^{4eMbJx1rHWxTDwZiV6Y$CZD&cW{P5{%yh z9GX!PvCQ}U~+3#HD4N?vU!~SD`k%78@9{_j% z-k;9~aByU(VK$)gyyZ5iec3&3PzD3S32Ltll_$uK+sO(SK>hKn7a!0%>Pc_mNmfjun=CY>r8 z(3jR-SfPbagu0H&n^3a~*y1c-t zfj;%Z9i8>3ws*{DZf;OTPi?4`6P|r6JEz)Rgd%69j1&8zq*?VKxsp=U2%QdllJx5< zzz(Yr{xhP&sMywv(b8|SLvl8|s+4+A%< zb{$ZgXYlm*MpTzVKVmJSEABphw2EPZe*CW(gE1y2vEJmG44Q)LHgL)|s?5wKg{t`~ z!>klD$8}{kVL@w_^gl*1fV-s+Uv7N&-AMYj$!iDypm$4+wJ_8*@>rr=)Nv6e*Lt^A8LSutP@GC^0 zY2Va3_f9Z}hXljQJv)=p6*fVitY=n7#=20sM#l2(bReN(=Aw_SQ0=x~kIE-i`W0I0^cRfMg~-=Zw%VELeB} z{cusS43V})7nY?rjK6de^yJU#GOauy?yGgpjWeA+R0FFw7Mrs(k;Qe!2iM726ChZT zLTM?7n=Ggr#U32QtWZ!X^WwxPqR=YV#*$yyWNdA2F{JW5f~{?O3qg7#e3D|Y%hoeM zL!WJiDqZfSB6Xn zYa8+&_-)<+i@|$uc{nOhomBZYU+3VA0J2!=exJ)H`d< z{Tnzhpy0>|u4?r41TE2@PEAoz0ieJIfVQU(+c3Rj3ulEUAKH^6+X27Q~#Zc4dHsb1|fTbcDd0X+qfb3POQ9ElB} zh5iKr@ChuT5-iI1^LV2nfqsW8%ltcLJ)f{Y%5qgzCo_U0rikcU5M?liSVuk>4V25^ z<^bg3=HhonnkRZaULhM}B+}apc!RpPlmRS>BT<&97eeFByi`{BPyV0U@OKRr3fL&q zp%S5pC}TJYg8J&33j3)Q!Yzi$jQZh3x`YpIOFRawHmcRxQB=IKzLj)I zmI&f8X%WXx&*}mgqN?h;T2UOaDb(rNuG1}44t+)6X?-{~A)RfBN{5VPgj8|vi;64C zPoECf6~hM;LY|?z+8)2rpK*H&Nc^`>fGvxL^bT*{2r!qa zpJqMDs8X^P9Bt)aJ}ejHM;y&HPf>g7)^+_FF+I2MxxagtwUNI2_S4y0&yfXuk`5zJeS!0xTdN8 z#kHh+fH%Dt36y(!xcm0IpMU=DySsOt@A@qt{_8(}{jZ;Yy!-uk?9&}4L)^ID*Vog- z{Ba1Z;EaGvO~fMRG|VxU!s9u=fU|_PNR^U2VUFwj_eQGrFJxBZ=eKXgr%_U$u|OF! z2{-Bc#m66(5B7r~Da0nDg5+et{8q~%w}`ElT$r)L`g`GdH@2v1)NdyBS4HXhT3uMM zWBozcj{O6;R=lxtOMgpdMvAPEAV&eFBwo<^TTfR_w}^mK(ppuXM>oAv066Z0s{nj~ z=~Ao|p$!rnZwS|`=LOZa;&Ii`%1UvRtH^9`ldg!2wR)g5!vpAu?0XZ!Yp(R@NWyN{ z)%`ZCc#~s(_x|HY5!fzBP{b&BCj)}V*xt!t8UL}f^Zvb=NYg3MX%-L6+xjYaD)19E zpL2t+9zW>^1-%d)VX9%Ru+C;^-oH1_iRyEELUPqIx#r9EzQ_f`nPh=)a}%?!5F+cg zuv509k4O8&C{-7Gd%5)}YCRX&&Yus0g)Fu5*V}7m(it_UTZ&5pUt-_`W%@{VLx1gc zi_R@Pcn0HksO3?uxpZ7Vq=cl7c>-B2XB1@>ki-Juaq6_YqN1O`-{3!0p(ZS!xLQCF z);YMbh6d_Q6592P*_^$epKn!zbvybFM`fkouhu1RR6uQW$SS4{=TOypn(%=$;92Rr zA{U1C-oy>v`tG}%w=SGZoe?}wJrHSf5i%0p-RHY+T>t(D$B#e$^y9bRU7`Yklb4-A z*fyS?KgZxWD1nKTuxLnTkN5@4k8dLwA__oyYie}__C8^~jn3*C^UBJa2*@)mD<#rU zl7O+!HNbx5ka-TCr&m6HeD`jQICO|C--{P?#b!KyY}VoB%Smdcr_gA(Nt1e@3FBWg zexPv}9vk&VmYsU#*_lPqJcxoE3*JBLUCL+7$oyb5?94+3W`2HjOaY~O3_)d+giTZ| zFFk(zI~{n)bujKR9>CaII!1TJr%W>FpPJTC179p52owXQ1gB&d?bQ{yGdxB?esM_y zogmGA>6be&(MEY<%18n_a}0pTx%PAu1G2^6adn#W=ygMd@z~L{X7}4FJ^r;v!}nG>I9)#?4M^^GrkG9t$mvhfE0 z21@t!j3f&7$c|iuA7QIMECUn*Wlu6DNQR8V>1%+G&h6~L2YE`yceF#=m9#!=c4Yn{;nL086aGKXwh zDr?X|YpmQ+UXiV3Q{*^4=6H0lg8f|C3L!2?(779gCQ-Z0$sxuy-zydbU)jGFRcb>d z4T{Kr@e6ah4DXB;S)<@AhCgB3SX))sfwzc;AQ-#z=gB2>b+@%TXM)2JCR(-xE2x_W z#DV5~Bm$sr!-F$`LbNKVBnrtT{APG}cxZ>s=-;kcL`BUR%JC2*BgChv8a89!(Oz4J zXMD<}|B`Y|;PAp^6q_mPazWo>xAEtAKtPKFC(8b0X!2 zMi2pc%=Q#5(u+sz5pGOEE%a4g4FS~IG4ythbdGsT!IwRwFD?s2I6wd>pxmvlzKbdj zr#&sN?CvBxq{T6Oiu}9baLD5(YvnTUl@IjX(A>#2Pc)<-TXg zCZreuzzS#$4t-{l>UC*t(8Kt|>!r7876}G=l?_9!wKmslkhZnSKDoy4-_{vjP_|Z> zO=9&FOooXuG61WqYjimvl2mE7K7E>ME;DF)Axf0_pvoq*4TvIuSu)i*=!-m1>(cr7c#;lMPfe2bB0 zL4kcEFnwG{W}aiGQGkFD8I(xBNjZ2+14NN{9Q8+_U{4OMzaFU}a$KiRqvxo&-r&?J zWgdry$P1bx{z#{q`>>}8(4h+}tJc$Cb`djK09iTnO-d$an2K*YGk}U6v$HEJqHR$J zzBSA@qRP7MJG&-M!m+mwGihEon+ zIE!;5f5G+Q%qb7rYfUzH{vICQzi^|^JN-XhqXZ{L)a~q=3S#R@uO`C|AP#s=u#-VC z2h1({6No|?p6>DrsjMbKos9zS#i4c3}_e~H+dE8JhOk>!j?pq$8VMTGENOJp)`jB9e^ZM3K4hJ+H^b5TIZng)!ZO7~o^D1uyw}#GPhtG}GpG zUf6tfaPZXv>rAb%w#_PN!pQs>JgBkcpzZKWU6qlZaS~=$c#&;iZ8V?M?ot5)9zk=p5`vHlH|LC8iF3e zMMTb1Y1Um>gTOkKTx~QCsM=6}_G~Z1xd=T7@rCn^jl9kmE9xN~ibPmHnIvtTvdq4c zy%R4+4i~C~P_VG9EKveg)bi2M9=Sqjv7TDc&;*ph*EF3fiC9@=Yu2hhYBg2!w)VzG zQG>IHVBoMnk#C^VUEjL)nYE_K|EmuiMw}lrv+yz81x5}f_4qP9j9N4~nX_lT9_luv z8P`b^*lliwn9qD`S*Z{W%|xn*UV+&Tp&+F?M$hWv7@I-w>E;^1NxhP^N4=d03CbC&s z%u-Nj3R6ZgZQREbb$oZXB-mvwXbx>UTN|5Cfs)tO52@9{@-vWL!$yKV zKhGzBZ$<(&tUYbt{S*Vp;|b+N6G^YV9_JWHIl!yEqpFHPBT%&(8Xoc}u?(xL^9zo7 zALgN|B*V-}L_Q)R3nDVp;{0NFj=47z6H}>C{j~G8sCwBxItuN^OUR|j`4ub4aTbxK z;QnMmg0o2YBIH=a0+hikEhE^U=)U7;Tbc6WIlZvK6VPpTZ==nJ&9Ch$?<;n76LKKu znXP{Gh#(L&-4A;)GYbL;XQLF0R+TQ#xUyVXSr3a_2*1V`(WE z0y#)gvZmPBSa-(5Avqj+@L|6q<}Ra(A@eoPyK$OmM9b?NCnw=N_22r{=zAftuZ-mO zK@_MOxlW>$Va&{}Px53m*~!Xu7tTjT6vC@*Oo;&`ErE0mwED#73qS3O!38L0iBw!j zTy~H}J~@bz(w63nLX>DM($(Ei&ps*q-22}NcQ{{8ODgN&cbHQY5DB|bxz_fM=Ekz} zUAjl$c&LQa18r`(CZQP@xO}{8d8r>Kpio|wU$hN%3+94nBE0%jXS;fPC`X2WVJ?|T zbvC1vTy%}*pf}FwlYQ1+*Rh6QvL>lI$V;)kg~&RQ%Ss7l*E?450UryHW2`VR%h+X~ z5v5_T7Zxz_aW^;5n+#w8qCUdu-<`nPpKbW zLp?_YU+z^srq*g@#XLdCFV(MRei_VyPE=AWoJ<{)?gcHN+DlGy0A4s9v9*a^*NznferxEUHa*ls=XFuEKW+ zOLTZxUT$<}_A>Bt4p6iSCeEB`ZqcnEfv6@UGp8ALG&pex0aKrr8tp1qn41wH4j{tm z!$Vii^x`QOS?q??VPKUPL zZ+B-JXb`S(LJkR8kOAaP$=mmRkyYLEE_F+VOw~1!Cr+G*jBNCctzEPhwWl}L#9i&W z(1MxCisK;~@qf>}N`U8`;@CZE4Uqmbq2zgG8Mmxe6+#oJoFPlS#i}w1mFfAnouBV@ zs^lEfCQihg)VH)a|Cj)l{Gofe2bWBMLv%(WO7+M{1+s-sLYEw7O=9TGQ|atp<;byiYz>i?3>L>Lj&IpI>Xnt%m1W6*Yu#*d?3@B~ti2S8RYb6% zvd#6=1ISV_WvnN~d;5DCZ8@pQoc5I9!NbBS`- zhoti856eaw9wb9%!FL{--^0YQ^Ctsu_`4wPv$LkQ=jYKV=$I0`hLLp4Iv5#eqXkzk zpte)?ne50L3`-3iw~YS9dJ`Bi56>ehL&o8})L(mEcp5|i^m(+`QnFxCMFIcOiEopcv_4#wu)_D!7y)oK-OcelE9=auFbf7=LAt5@-LH)iy} zHE_paeO*OGx}N9fTp=QQVqNN-QqN?Iun2+JLLov!CDPq5CiiYcC#48b_nJ525%2|~ zu{LZBizWB=^!D}y#R%10Yj!2yBz&6L?4pjZhhOUAz}xBglt&*1a~t>q6kWZ+vZhK2 zo963(;u}0`=^?`T6YSpa6Oi!*oBym$$m^wo8=F?p#yvM&Mj_FstNZAF75J5`r^fNgEh3`7gSITzD1^{Lfe{C6u4*Rr0?Cof6pDJeD7CYXwLWi z4($0+|Fh@g<5cEN0pipSA$!lw2VbFUA=$(f6Bujm(*rZZW_)taY?r_>DQDt;xwU}E zEEy(grbft;OiULalURQmoqD#E=0NKzuB-^73ZClycT@}9f!=aYp)EYC9TG*h#)jY) z#(9yMi4;t`I3!6Fl<0`cNa7ksKR3rseZ6=si`VBG4_TiPFv}1~IIX>4@0hmOQdVAD z6MB}v8Dg{YBEFj0H9#_k)5TL;TXi-5C7g_-Psgf%^*bE9Vp(+_o%Zo(Y{niCwmh#0 z^M?wysTSgjnlOkN0_GMlFJ=;NZ|9u{(KgS`#=2AO7GFVX*st?Q7L6o>P1}7=)Rir( z;Nu+WteK5hP?_4=DLaW-c9$*|muzimpcp3jOCcG2;@@^&Vxm#gDPV_DFGDS2hI0_1 zEft63qhi(&usSF-QpJdaymJu(3dG&g9j6|XiD5`mouL4`#F|j0;E`L~OG^_Ilw!~u zfx2j`DPGxP?#%$J7L80!&d!R-6BKW}eEkSe4sIOEh3oe{BEXYts9FpU!`HFk*;yVu z*`Gu_o@3i4v#1@o!){>dgnTd?X9*u4S5)HXP?dD?wYAV7HYCbKtBvtcirvibngdb2 z2a_4FpjT2au=2tJ`qWZAYrPBIMySPYYYj}qvr~60#<78Kf}Cks3n{1>@TI?@@79l7 zD^y1hG*?uptHE(o!Jxt<&K(|{o>KEv-q-|z1Vr_~y5R7|K$of&x1Jyg6Lsc78&$}+ zZ{Ga&+ixkGyZhBm%;y@LR@)P{g1PkcUF*SkE?zh}b%y^k3Y8r(zI<_>h?Q8*Id**; zq81_-tFor1xUvh(@LFD{#?Vro{rya4ZceMwOJ{a+*9sJ`HkWhj=B-d$?%cV3`|jQU z_>aH+?Qg&Q@(T+B{?~v0=U;yM(Fuh={P4qH{_^wB*O_%HrE)yFi!zQ#8IO2SRRuV( zxjKMrge)?*a-Q4F5zX!HowI@(gN&F)Ycwy-5lPsYv*pIwCG`yrS)Y+%p2IE$dz+in z(?dg}^sTXT)WU*VvVYIW#1-zM1FHsai+}jp?d`}JMs4NN(&naECyF88qd2Psz&rqN zBPj%5u@f~JCb0c+&N-i6&4~#MlZFPBDYR;g+mJ7txwe++dBD1K`8DI>&1*);!06bg zMSJ7O=$Do~GG=pP&*x{vPK{U%50rO(I-E48Uc~(Uzyk09*3r^jQfdywO4DrkXb6ev zi%rIR*gs#%OR<)aiiMkAZ*ya3R}U;c$88AU1D*kBV>E3y>vdI@?d7H>i0`*EZGC+n zdXM>Su2&Hq{tz^j1v@TZYE5kcUxou(vIAgJFd2JQ6;?`0GWqbi1GunAT8Hx7vUFGFR$1yh_9ryU8 ztV9e>XVr1#){PrCZ{57f9E97NGCPHIv6`5*u|C4i8FM#p_ViR$0~0VD`CBohD0#)G z(c=JE|j|RSbb|$73f@)kqR-(7IHRTc_7sl&N*6=gi zJu^KrIy%CH z`!QF{mJN5}6I5F&k$7^_FdCRoPsI`S^fFH-Eki7Ya5kw+pn3|dtn+KX>?$Gf2MCaS z$hj*=M@AERT&&oe<5%nWU>A`ojPWJEI;Jij-EAcXFHgHWsmqx6x3(G*Bnu*DV+gXN zr4hjS$nBvQn+77AS6k~}8H1?XI)WWnd>&?EuWBQVblvG8fstCGm9^PyH~FxBEYPbf zYw)G!7-$pNqo?(!e&6nBr)NipYiohrjNQ#m$0{T#E0BBkaR&ftu{9~?X0a1gWp+e3 z^D`%Gta(NpCX&&d28Oj^!+U(dDmux>k1?N-f>3D4u$jA;FMfOY7$|;%?P|ej7-I4B z7m)3jz%lX#R7INdmTjD#o(hW+rq5s2$)1y7G%s>iQK@ub^aB!lZk?PLdQfIWyLr>C9^u>dDw+J3{@DOUMPn}Z!NgLcbW zd?yh|UY(0RK}_hZxanuxfe_1xi(g6p3kM;%23f{Lv=hOVJ#8uzdY5KStd(9UaEX|e zsQD%vYNf-o(WR;lpTgO}#)~l;a)4~y6v26K7fK1IL>9)Kaz32P4k29NTB?-Cmvh&d z(UPBUyc8y-`^~u02Is}-ZERu~xrPn=w=Z(+QsMYN&*=qZC)kcC0^l8o;Cms`aii?kr^?4Xc%mG|oQ$=Yn&*yKOLrxA15XBwXjPgp+~ zVb_=Sb@`~WnJI-md%w*1-Gt-qu?YH*r_(aI0L)I7k^@yLQ1Z z_&px0%srF?n@cncGr{)eOo9Z;|- znDgeQpJQ!LDeG<;gEWH6Y=%qTTaAV1i_y!{N;z}Kno2WOUs!PtGtl*l6KeoQLy4#c z*0=?wC5K094REmR<;-wz(;SNc^^h(i{>!RzU~6Q6ISR)Wn>up(ROb0q6;d@}gi%-+ zo%+<=a3^fUPac2v{~`K{&yo5y$xTJG+Li3WDS&BoaJn&ijT>V~FmmV@Yg7Ps6*BKh z`++ft%G@hCdMd`ja;blc89lNrqTl3CDk{v+I}d~_OU3)iae0M|hnmi2*Lk}hlNO*E zJlLQcw;<2g`{O8B(I)8xMmA)OLoU$hi;E7AhA49vLeZg!N8!fZ2dfElUp50zR}#AKAi)JhaL0^SX(TinKw*^f$wBS z2FHT7Cf_(ZV&*=+pH3pjtUkLHFbHll8;%uu+Pbpa|Pvk37u|nQ6!#@Ra_?cDDv0TDZhCG=)x>4 zBNBg2l{|kI6`!xz6hM%soeMQM`0=9!ee}CCw1#G6cEA=`o_+pT_zQK#;{hMBbX7he0584=t<|D!`s@D6}KVK~+D$^JRj$_n+;T*PNgg@vANqi0Z^$%e^feYHM1)@6bn z+cip5D=jfQvA>^e$hfSqu%TYx*4o%zp&(^q%>-W`^azD*2hA&Ijh z?NR5{v#u`cM=VI?`j`-OL^!;}3dS*G+uEUn{Uti*EYiOaZ;M8y@cQw?(o$jt488FE z@xL5Vh|!0a;YW%4Li*7dgjk*DmoL;0(eqO5b(dWP*jdqyT( zG$v{Ch>cRCKbXaHvVX5os?*GodFrC(=jNH0>5+Og`Sc9LWa;pdB0p`2n`(H^BN1n{ zjCjS@wzjKRFJADq3z1BJ-TR57TdU_7d*frS#)aL5cl*NV_%YX-6iJgn04@ex`EHyIwk z8y!4EWymCCc@K`^&?qfyKH`$DQ_JroJyQ=l1iNHA14`}a%^96q z{&!=eh!)0qzyEHnh30BzIHzFrjQeM{F3>R%R6WV&Iik?eGaR|TR?ZeCRAN{Pa!GzF%Lkr=$7cEmoVNPVi5ve1-wE#B6oG>YSl6F$?s`FICD5C zvrB5}`ycw5H8tLG5jSE#is;&-b{7D~0MP8(Z=rutT44=nZx>teQAq~jpR>t3aq4Or zt!=fne!8P8<#VJuMa7;%-nlEH>tC|yybZvBb16?6${ZeUJHx6-c47(bQI{X_?)J$^ zj9VB&OG`H_$6YNPmI2?8Y53Chf`w%c)p=ci*fu{*tV2(OZC_^PzgV>bcDuW~Vigd+ z8;y(23JPsjBgSx1k?ovrxkcK@=;0>q2(y5ewY3Gpg0fLkaCC4KX&r|tC|oA?gCC+2 zjFG#YtQ|w=9_BN=l_B}tQ}sLWF*@IBc2jH`oWFp9R%p%Yyaows;2e{ zkFz-uL)3dLMR#tUo-=HaCkKHWyx5(Qn?{G`s+?0mZlsYliBb^NP@i&~au3frpDx~P z>B1zbEi6}T5A};^J+isXlq)ay$e=B}E1z#*!szprF;Fcti=__QFrl2RSL%B?6fK@E zT?942jE4HlbYYfxnkUoHWf}o&Kx8gb)c=TbR0Gws5(Wr*BEs@D?WbounPn)V3`vbZ zOUA!!8?hfA=H)RYi_*BStaQx)L0Xg|>az;m+LqJeBG<9PQg7Y7uCd;_b<2p45ys9p z{2sQ;W?%?jySSd_&PEuB9bdlO+#GJFzPQL4d2pY|J#4IVlbN;54G%jBjv>z=!2j^U zue^x9?*v6HKi?I4aMWBU%S>|lnN(k;jcetl zr3Z(k$*HY3N1ig|3?J+byTg3>AP$cgj2di`sB~^#twKB2g!}uU5p47`X9KWfi?1L! zYX^))i?MNr1?+ulYIb%8J6~Hfc2SR^-h-ev8?PL*&KGM#JG!v*$Zj~^L{hvCb$x-5 z*qBKNJ+B&rh^f=Z0#SdRk{ScJw15Lo`PpNL-!zN2ox1@Pt$F+V2DR#DC3IfG9(u-{SiH3#O6fw z&sZ&UIypK)+?2?rwszq-k?S_r`>>}(8g9@e`&gijid1nFPziaoql^*G0JG90S+VC) zMW4XLG(-&a*aKr;ZKJfTz0H_eN*@x1QFV9)D>L;Cj71+Dn4X10Q&nWt$$}()cEKqr z@d~Kz%0u<&M@L)RsuxRNLMK!m$nG)ba1H*SFUd)e)7#vl%8gD&_);(>JdZ|+?4;PI zZOZ;2f2EC$v5HCX8Z$|Wk=f-X)e#*YtWgoVN*`B{@)HoRrG5ZXiRL7~a13HOd-mHS z=sgI{&exIQr|4?A?eUYrq0x~c?e-n5hUla`#`c~+pQI-MAZ%>utgSCGP9pz$2q(5v zm$@m~`#B6_e}9$SmB=gZ7O^F-ALto;BK3&(8MBR%SV{hs*Q~ed+6h88bNV5vNy}3$ zuuhFG4Mb?p`Q)aCVuS@m(BMZCOsQk}=YhOLe|)m>b0#XA#0!xk(2?I!L-q1H1H7!P zww7v%i1M%tmnCzJLXE`4PKVLeH{ZD2=IuLo$ejs!xa#W(<=_%gDTzp~*|0+}!oaj9 z^Lz=FPfql~_-bS+J1ct6+^n4^&l~ZdcB-x+vT>-#`R7N+Dl}GN8v}{ntXvO+tE{?o zN!x`=KqLU$tKB^}Z*Y9K-R@*Ff47PGLYo7o#b>a&r>ECCz0NBQ4GgSGl?yqTcfzq# zO2v5ST{%VhPn7|#0iUo)XxSS2hy_2cuByJWLD__e$oDWFxY*7w1>vaTljbx2&hsMs zmF=M~BxxLS!?Uxkt9ZIbPfH1wa(5!aYj}8Kbhv4w*v^n z(29`}n8@@rTtvK`ml`J}5#W40#dXikn@^bs*3UDSR%uIxF@PAfU=%SBs zrHWCEi}-zC@gBtA@ysibk6T2KAoVQz_-MbFIsPpVZ8xbEff3AocBH|n(cmsIFIDZ( zb!0BLs}U2(5xe#D;=XTaw|%foXmR-HKxYsUa0cp>Dw>F!L~_wia?vEa0RHR_cXE=+ zM69IN1RxkR87qN%t9J1->Q9#7HFK;&pIGgz=QfnV+64D7~r^=eb|;QhJE}*iX*u4wlN^b6obi@r^Nup?434(3vAn##esl!2kJU(wGmLE;>Qa0eUZm42 zl%hN%y$Auj;zAnS@k2ewmCj3-L;*Tpz+luiBOXD)EH06)R>SJ_RQvlMg(t}-OXwkAei8U+v8UQv$%_8rDw#vW^MH} zdL}-PkFzI$$zw5jF4hp5LSAD|Kidd|cW8K+nf}A997$Js@*(fu&CgFy50VgZxjI$_ zdW*4Q(XO9rha)xx7_itKyoRn|rVb1FJ=w?k`IQyBsid>?(^HinvG~S$o>wZgN{|zX z1Sa)gm~;?gBQd+xvT@4_3)Ch;I1q$QLjZFsM5=byS(=*@B&%zTOd{>;1p)O;r5m#)TDB)Be>N-Og>vAEaz3lgG&O0e-SiymC2>tqcb&m?>$~rM`P)DK z{(t_@KiK}x!~Ji6|NGy6`7iqGeb0$}_ucp3ef!;aoXWRf-R-(6;cr90kWz<^0sFVd z_bELwjM?}X-F|jik%iW%Ark40a7D=p;fPvVs;f2a(^!uup-pj-9h9(y z%2IM%_EU+LMmzVFDiWOBjX=gvFPFovle#UMpx%GQyW`>7fB2c^%X2sYyE*$du z^|NQp*<&+_=NGpJ51u@EJ3zPGx3;;US*E=lh{ePQoFfr}=FVPKU0M=`%FVP& z4m5I%;{df+M?b^gX?||R`s8oPym1ln=L~^Ru4M<2HpZ%md0;86D1&Y7jSY6X)gH79 z?E`?PYMlV22}*z|;sSXkE?=gSR7%luVwcnyFsIP5mMFRp?M8@$?72_zxuAQ~cOzY@+h&M}oV`F1!Np5Rt@qBh(VZv`?!mOD}4W`3B z=hxQo$!xnjLERafI$=G;AMup72(DFAMQ$E0?gR!(!sE#qgfh|4Xg>wpQ3Kx8SWX3U+u_2$mbo*pe0n{^1HzBw2*<9OnQ1|9Z% zmHJji3~`met);)MmpZ=yU55-5rVMp-v&wRp|?w=m-u3d_8pw+IIrkcC+3_KM3h$LLI^s9G8#XqOtI;zE@&z$6 zqiFX1B1t#~| zPHQKOVB{C&M~9c6UeVmtmfcXur4I4MER~rac@HNRxwht(8m^}vk5%c}1WP0Jy}c#o zYNm9a(N=sex|~%{*%Wm|sh(HTkj+hdE(P=`ar~8(o*YLkKs;c0kO80FPEWz!MDFlG z`XTGxDwy}ljZyQAd1vL!k>e6zIx-NT8${rbi8Z#jS5`lM)RG0>uwyhj*49K({+(=y z)ti*QJ$?4l0BE50+&#dLMd zCUR7`H`I4@fL>(GjF`H*uF*X=hiLi}y@)1ahNPvrvdR(bxfV+f@lb{Vb`N4pm>OS~ zmlqaT--PP<m7+dB&oxf~4$E&yuckP5Z=abi4A$^| zTvn_6WKq4nUww7=?w|Wtv7yfKxW%T#5u}5Fwew1SLpcmc1v|MLnN~r5CWDU5XL?(? zui;KsSKtO%mRlNlF$m-35q0S}V@wz`js57DK+fFcrf(7N!Tap&2yBo;&?osH-z~o& zxJ@-WY zekv}WKYunfJYhwVkI9Ki13!53)bw-^5Nb6b$r1NaS+cyn6$TDs5NTrezF^ksym6MJ zGOI6fR7cdt*ilIRB4ysfaCnLoi8&H5w<-)$ZL_dYv~`mk0PMjWnD1+B$s^@YQ~lC9 zJ)?D6j9`4UY|VI3#63O5*0EK8j>ZL^pHRQ9NhGsa#p&ru?4^VSaX$rBC0382l5UNW ze_+ia_-69a=x|!MObPRb9JOC&UKCc;&>*!+;Fuj)m}5zativv;_Mp{6y6t50S(iA0 zDNu^#g5p%_S+nUV(yKJV)$KY{SsArBI{-SMzRj$mlM_|6sw#gf)xwq%* zZ@&53Ew#G)!=~?O%Qq2Y!jk*?`ns-OxM+WWY>$W7E>CnN78uy72F%Jr%*rs5SGpi_ zPTW8A~O8{VPZ8kz7w9B*h7a*a&p;6O|FI?EV&vJE2#o z$=yJuz19cWG!g`xBd>;TLGLT>ClwL!=nBUIM_o)FE&L+ppn^r&0<^G8e=_E_mGJN-tXXBnpNvT&$6k)1d9751up7F8icgm8rp^5H+{e{5AkxBN9kqRlE{d={m?|=O9`yYPz zK?RHD8+c+mDmvCTtfqDM?rki*7X#_&sDmlTn?(QER6$o+xql$?2pN$I@O)BEi= zKeE)sU;p|u+fU3)_4vR0-b4TH+poW&5+8TsOltdzRaY0oRAoh=F%Pl+{Cs~O37PR` z<(s_HRL-Zxl~p+Jj+cCEb9ax>4zN-$1fKzD?=_wf3sliKI`Y0af>o_tPum(+Yj6`B zCk|3nd`8q8x_++Oo|z!lKQAAw(LiJ%E?6H``kPK-}jwKp*E z>h2Os)AY0Dm^gQsIxG5(?)&x9iDh@Y?MMD#%B>%^F&0zYfB?) zYV1AhwUaFeN%q+_IdpgI#JMCxv`(1~m+45&O0MC2j6bS0E znjBO;-BjMN5#52l*X753;Ba6XDi+3o)i@oSDhCGUr8sJ0*qnl_X!K){Y4N0`XfJMs^fpi(Qfs$U83+*- zi0y4{fP}S(?W{@-P5>Bw?;;i1I2(LUx)0uxD(*~n^m&({IQqWxb15#&99m9%51i^v zg3^&2F{($HQ`hiGYXwiRQ>;8yg~%#F?QE2*`k~t5&+-dY9z_h%1_$7dII&{wLIPC7e+@V71(U7&+x`PA6oM(%p2J` zt3AiWJ?4}Qv5l~S*x)lfdSYbw!^hCFZ0SMW2eVVzpjaz*774XDEIG)pz7pM1%jIHX z#+tHQV>o;)I zL`qJfqx}@QM-zJ#7LG3z$;fYV?5PsFs;aF`O{Fge-A$N;8>0{ML-P8D!%*?f?xWUU zY3at-p|W3SqL#?3&#kB>)(y!AJEx0d4K+Qx%<|WN;7f2OTC*6ny_M$Xi}Du z#v{zT9(&}fr>3CE)3Y<5J~Kl#aC>F>)8~;9a?)>1nuyJ>c)a8re4$H**=qH<7u0+r z(8bRO2I!+PG(7bB4O$1$;l$J;^9%SKNENL513{mX_2-REyRPG~A`cXc;4CeNfgpn`3*f1c;sgGPLe zZSYsi&FTTTOR9+yOI8tJ1@qMDO^Ks|s-+4L!XvA%>+b&k+aG@X8Q1*Nk38X-{}O9e zK`Zdi-|_f{jRfCUx8b3|m9KTPY0vGKX@-$Zgx-l2%v=%qs<>+^8di`Q#{)5;Uf^m? zK*kPSVcCwpG_%jMFv_(fze*zWTrQR{AD2NGYNi<>G<>$QvI`f|%v8LF{(~*-nL#I0 zcDr$-s|#$#Z%~H}NW?*Ay&hzSJe|sy&N1+smmD=wYGw!Xwdw&s%PL_+MWU*!R+?Q# zVdmJiwD^x}fADa)LLk6qt=u#6m2r&GH73WX*xVp`0)x}eJqu^5@gp4vs}R49i5)-q z0eJ;!5Laz>wkg$ZXcZF^bD;6Ue6SyEoXVn)ZwGAp-493ZfBc9o`e;4+?YD<+euE$I zqbJcTz-g**kl!BOzxNxz`IvRkU%#lht$+Wws6tk&AG=z5TLQp8oI5`%rN-RSfp{WIJYCdrft7 zUd`}uDc=A(5MJ|p*ao^Oo4Fe!KSOnP;Mr)Sw2oDwZd?x@{GEjIyU6ECg}?sho4a?v zM%H4jWOOX7L##uL1L+)bNO1|3@fQsZR+r(TD;OD^Z46k}2;bfH{sby#k(uMfdZPvh zz9G+GEOKKx)>9d@vUn4l`T9$jN=ldqPB>*$<=N+Kxv{aEZ1uHk5Fl)sm9iLM81YR^ znXkP)Pz&5yP>baBaBaA|sH==Jr)Gjh=A9&K_ST5e)io6rd;2P?a(lBl`Z%nTNAh}H zXifJIuuXO=HcO!Ng#oexEug#$=SA0B$7O$7Tca;Q2>NMY>M4O8oU2GAK3i>zL}TUO z7B$MOwwIU%)kCw>1ZY%dlKkhb5ZIwQa2|9BbTd~6C*q?Qcs~zp_OZ6B0rQA3@W~U} z9zGXS5)HgMBUbX89kbC#;T_xFgKr`H&94E`b zmIXdMw+Ry~h4dp=m>tqkUkQ`&k)nXc*yQ@vHDp~S5FSuGh1b=ovWvl|;FNP9&jX27 zB(P(WI%zIdTU&X#IeC(}Byx@H6W>>D>9}I_)&+H^Z2(@2z!S^v!G=?QKCx%(lY>>5 z!UvI?86$)r4mK#B^Sa!{X7f+5*owm=0F({|saIRr7G|s+i_8g$&11XAC=J^V6v?77 zcSfGfz1iHvGT7!xO;BCN%2$H2F4Tw0fJ7ul3w*_Q3-y>$gii>Dyok^L_)+evRU@mg z@kA|)B`nHm3k$4y`pWqtzZ2<^A7SY&ARPV?20v183w3&wo^u6T2}{){bguu<{VT#8w;c$Gc&(;Swdr+HYbM$z||MykJ*1NeWRdonFG}p~w-9 z#s{j1t6K^du-Jg)Fyt_3wTZ-*e5Hw}4EyKLsJ*uhZz%kEo1JSN+}Io(8j#+>PhMO9wD^v-y@y%lcVG}|{Mxj&v!LL{ zJo#Y0AG7_m;fD_ct}$Z_nS@|!N;^AiM$lVeubV9gD$D_06l zD`9Z32=ZmtUi3uw6JCQVT~UeA$&V9Et03UF3BCd4%g(1vGmdI%kB_pKQ~w;h=k4{4 z|6exvQwwH0}a9EY|PQ6)iSb7XNai|z3D?#Og!q9m_MFt$T zWj4LNdizCkE@p?IMnnb*WwHxHgo=twuB?D#@cMOLi5QvD&e-=M1Vs28Q$`eqETxX> z)kaQOs^^$-+{K13a4ea(udJfCmRbVt!z-zMNhY+?KM^@BmM86rTM!Q+#p}OCw<|kd zsR4kT5CQusZExF=g&V-;GdCk#O<`~+v*s%+h<<2ppFi#F;t`zX17efulcm#xXm!;X z^xyWi)fLs0b!vG4-X9wkBkh`b?e?e?Ha2F zo1Z=*BRDZWKEL2Oa;ls$75JxIX^vAm>YZ(DaJ7PBrP^$>%P8Amjnk;!5be6 z5s_g{HM%b`v4MS}UkE$f8A53LoTelnaflrt1J=yGz__UG2+$GFwA2PL5pZM^x&*Gs zmXjHy>XMpz)jZcx&O`jXPjHb<{O>)cMWXAXQeWdhKS6_Vzx&uD7}haIH>@YOZ>dFnVpXmscjSRQFWIm#Gb z%wH=ms;Vk3N!*o>WIH5BT~3XX43P+c@fwp%OoTdYdF175xiY;!nX>g5?wRcc*E+>< zi*j_JO;I8_Ro9ixx}?Zl>!tLeGA=!;t5X}$X!G2L=mEC3Yu!DYZ>IZao)&LSJUGra z#`f;r*w`rB%a`J=f!^cCgM;`43pFEA9qGYoWV}dKTUn<{0h?HGRZDd8!$Q1c5#5;d zdwHn###5Xq=k57}&45ja7oo#xwr(H~Xl+J;>x6ICMZ?qLNdmy!B$0|4N~Ar=_nn>N zobh=3d#V3FRZMb-oagpOa!xLbVFWg5?Waf_BdeySolSIivnO5h8L9-J82BZJX@}$R zYpp3%x^&Tc0LwHPl0G6>tgEBe0?r9!W@n2eY)fZY%^H7*xxArjoWoK@Kn7A>T~T>X zooD0|vPbR_8|j^%+BewPbt|-4?2En*hX=>#&BpTUYnxkifM8UC&U7EA?vpEc;evIX zY3{UWApt_h2;2ZBgbHlCueY-^RZuz5t7{6GX^DC%cSxkiaEnbZw!`YCkkr*xQq~MJ zkb^l_IFEC1Lc1zv#**#V!G7AFIGR!ry2`2;*k*; zF#%p={pf6%THHT>{*)dVkm(m_av{bI4NXouciU)kaWV9nEvk=M1y60Lq_iql8u2d6 z;V8WdEVK)b7|jzKrlFaKN6ydJhqwn)L|6o9upJ({rl@wHB~-*bQ?s1Hrk2XwuyR3% zd+Hvq>gt`3z|R3|p;bna)hzgIO)_!XvT`D3tVB!7&!Zx%IVL@EuojHzpu-mN(a752 zmeXt!{WGgk#&+qFXyxYlaKYI)w>Zr-`drvQa;O9ci`IX022f4ki7?CTq0*nNFOr9irtoU}Hu z1LWWiaI|0!8Ih96_wDZ!MHx~7Rn{y}+wfs^g=j?pvO9vy>kiS=T8hCTQ9|Ih#a-~axf|Nig)`QQKj z=fD2-uY33IM_(;8xL_C0pFepzFn}}ySm*kO9qgay)z?{v-j0L@)rIedNP%E=wW|lm z8xGK?aD_be_V%{4pd&ECF*{GuJM0aMaj$9<^H8b&+22#b3{jS|waV9CSP8wkDN%po zSP?kG93p#c?31{sZoafMjE4H(ymLHauxYp|D92x@FIO5cx${TDDL00-XRdU|oTe~f z^oqVdq&ey_(j2K9H+1iz%0=c6VTLq(9lh#H=q1W+W9&hIjM{N3TU#k-28A>{r-m?y zh2(;qDJRdrz&4P9qe6{8$Nm9r=4n+|$#J#cwl*`8MiyP&mo8=NlNoW@K762uv=}&n zU#U#j)iP|nxPbz&vHa>P=OfAUVfvh9;tl4WBEHo9u?mwsLEffMq)RY84lrK$MA~Kj zQj-+5i9T#lLVzf7n8S_r&tLqLjD2T}T+_JbOT@jpP<(@1!dS8|Fv;D$wYB+q*DK5A z8@W`}U8qyQR@fvMyvBrQKTJ-Ne111FGB!px7P8GtR0U1EmuHxhc9VKgO))+`F)=YY zIY9&}*{~+`5wV^Je~h0xJGBE@;swA}&qQ7Gw|`n4(eQTbpgn3VivO9 z>S~LPBkmEbc$mP&z#@iJ1?;%e(wytNb%7jMdpmPp{cNBRC*Y9-GC_Gb9KRt(vI&`G zuK66IR#QiZg+#oe@(U6!+{84odCN9?i3R-+VS)gdH!UkYq1U_FQIf!4I2BF@Z%#Z; z1|GWj(fRpCud;3+a4(S^k16uqWg?9f^Q3z$uFp!;DBWrW>*6Ga7&d)RBi=pe?^OVzRXEO1}JjIyj;AC2o zp8bQ9V;n|web;ZEwG*ZUUoZlcc?Q1#Gn`*!@5Z)5wKI!a&4D+rYM?hVq{8}zaMF=OkUB^7pR@qf zDehuoi1~STv?7WN{lg=K{b=Xu>^L!B>L%)=I)Z3ZA%eY;5VWE~|0?(KSp@cti=x8P znIif0ch<4WVwuTzesq-VTwi)h-xjgDveG&@WM@bm)Pj*{<{D>r4Q`wWGB0p=xC;YA zdIPW?j=o7XDeG*ii;IZ+q9Tis4~tcyunssPD(wW!qrlsGQ{M& zyXXl{X{Y%SYxlfw+)D2%_f+UMS~({ZsiRJcf#=21)1}0^oQ;j769PgUq2o~hFXCy{ zQMb&A)zs3{-+JpnQI3@;KgVAKxR4iN$6FHTjVFaMTelka2-ecL1?Iqv58_1qls3M)MwE|* zi|PJu@aQA#fw%GfITVgXSFpCafMPN?KRYX>fLgE(4WS&H9(mSJ&->qgd+}lzRF;15 zE3l3U7{?MF%bf!yUbtq?^2*}E@(TUSI8pfy)xZa(C0;Wh#v2z}`uc7mUfseinlED~ zD(MZT2^)azQ$A|xoGN%h>T&k;>UCKwQOQ>)fcUgCdUU$4F(h=jKzyY(4Og<3D>gHH= zsHX3T>EJ1wET0TFh`PKoJ2N&04^7$^{fW1>;L=2DXJ=6hLhea z!P?rT%#PDbvg#Z++Eu~1yxFeG*6l`@aIPKk6LVkJm{&4A{ho~iWDImh41agY{3n4 zFrPoIqQ2#BzDjU*bUW1He=-c$U$8PZyq7^kjI2{S5KC!{aq8=vny+@Ma!WMt-1+v~ z@1u^_l8bNH?%w_CtFP|#U2msby}?s+s#4(4;0CCH0O0!!f-FD3)cSp*kw8T8Grz@sW z*;iZ&KBr1F^g1@@%8AgRaT3&HT*!g~rTs~LO||{btY%MVz$vh?=60;U>oBx8K`tst zd0H7nKx`;+0WF0`N90PRl|1D4yb)^Uc4L|n`WDtQA~DnwtbU>!~pWkOB<{QzE6a@h9U?F3IwEA zAD>Q57}P`^})n0ypHu^6pf zG^@(&1DME1gvX|$05q$71J$^f`v(e?LPT6)5jj?_7cIS#zJFuWWqKX8LwY(o2>!e1 zHgwA>A&l3MiA3K`Rq2BS3LIgioRaq70 z1Yhkq@aB@SiL9Xf;X=%dN=O{XE>VY3vo5vj1rO0KRX-FIa^Qp^9IySr%i;d4x1C00 zgeMW!a8hPN;+%>KS?x<@4B$?)k=3B78Ybu9Mwta`n@cFfxP)_Jzxfczlj007@6K@*=jIHP+VV^XFm*2m^1c^)~gJp2lKw$li<^*nHd_#Ax$JJ9vd5c`{c=^ z{>P7fSOWLv&8R=Z%*R1&3|^yWcb1=hb!oCSJ^ns-?AG4tHWwGSGjdWLz44x%Xic6P zb3>j598@}TcgPXJN!1?F>Co zx)mB~NgE>YtEs~JK)ipC`LetpNEY?0s#MRIKaoW)OK(_7j~WXieeRBXMMcWlsk^tL zo`}TlgcRoJ)!6Lsb%C{stEwG6&J9wkEGRfU+}tEH86)`c==7w#A_?k|BvoAJ5&0)6 z9sjGZB_{*Qd96-QAnT|i_JWnvlXd_f#k|^@#6Vo*jKGa6##l_Wf?qL?C@MZWl=G^^ z?Sz)>>n~r73Q-Y)+B%oW*u#&|bqO@wA98r-=gzn_`+J_3tE{uaLY+ef;;D(Z!J=VW zjIOBt*H45Md(U|@bCq(t12mn`;^wDkMuA~S!|+>dI3x@8iu?EO_dg3fh`XIP2-0O0`P z5*{NDzSh^5(qS%PaX99fKZy20zs{7Ktg5Q7*E{K>T3at)ZfUVnF5AroXCunki(R%K zSR9d_NU_SieK!=2dChozUGO0kXRuh+Y3wcvY1u}%*dw!56)(`|Hde{_al@z)Iy%Hh zz<30uq#ejBo!5i9hYpT%0UJ^ckcoH<%PSwFjf*%sJ;R*qA(Xclny=e^w+Am?yk*0X zG2hYAVT~Q$fndi5egnv3W3OI?eu0AV=JoV+s=C`o`*yaZ8J+X-!*bLAyZgx8YQP4 z_UTbk<|qm7Yie6rosGi}UgCXkx6=$xg?KRKA)0Gzi1e77;>=(NnfaW9Lr0QYTOiJP zdAWScXuzGdzU}K{3l@(prA#ajf;)D!r4%cxzN1oB2C7P90RlnYk(pMhyCH zZkaV#OM^|&fofus$wO?DlOI0F6hso@|A_vecRrk9zze(8%C2&EhL)2CEwK79BawQe-I ze@e~{8>VPzkak+g$Lxb?;uHU21D0RBeDXM&V=$A|BG6~g#>T$DmQ$q^#A(~v;I5|x zDsX4S9Y~rvU_*cNz|3cAYEtiU+eNGd7MogZ_2KNWZ`vH z$cg02sH>J~I3%&#bS^SyH7>iP)z+oX-Ht7H#v3EfF0oe1bGJ~=^72cHSs$A}Fyk$r z3&PfK@C+K`0>I-swF$G0M31@rWJQIYSuqfF0y&eKWO+F;BA^aG!Ep5+P&iAZWOA39 znt?RVz<$=rh>a?>d5<@8dv*4O>h5+ACgV8nP(ZgMk; zFt3kSQ3)4iaaO~PyngklzyHx6u^@9-U%VV0nWn?Cq*Q;gyJJroXA57vD5}9D&|7SRkN~ec2gH7yk#j~%4r`4HAv?BMh2s&{)f&2c-31VtANkz<3^f6v zc&<#(MD}QCh#8NwvsSk`ysUSfpGTHeB$d`t&%MZ6NUrhZyPTiP_vkL_9~h-_IkgIT zsFty}Z%x_o@W23X%*5b@1z1#M1b0zytqYaMgbxCA&(6$lM&Jl)RMYLo5lux5xM9c! zc!X-~tMZEcJgsz_C)PZ&5|^4SC2`MFO*@ofUg=LtO1z?sbMU^Ysgz#J#Jcmix&Vzo^~6`ZzWwG$q*_GV zpMLlOtAsndYr-x61I!N=)0RQh1u zwN?mt)s7*pdKkN689>{w!c@1HeSICC<3uE~7>>>b2kW2v{OCK7lE*9NatywmqQ+qc zePC{mlH5=s)xXmInNHGdChDQ7=l?mtA-Tco;)5HK+=E>Lz72dy#a?GBWLh~R2^p({;OB7UkTYF z_4D6zJ@BWe@$LxRSP7Lx*bT0g?u7Op!dB!PU0|!Z5H;Km>Mh`xSsrcF9%PzlJzQ%m z2naYts-dKY4##@s`a1ghq?}3C$|ZmLO@0j6&L7vRsxXZ)n?Y@1-iiX>$-rdB5qMf_|LV*`FpO~t5Wl4nv zyT(KkO7dCqT>U$%jS#JzFzg(6@ipT3O7sQ-N5(m85~9jz?&cpkLQNLbs&KX}b8Tv> zOG*z9#bI&o@PGjsziHK>S2Y{yaRpS3FJ9{HAyJK!>bu^}7-8H62hqFnaX4^}o@ag? zzor@_`Z?O`VJjdH75Hf-4A_2DLvV>D+4KbQr(9{`+md2L5TV2{jW>u(9kIAbdU8Tc z7Bv>8<>(TH;^=RmpP_+serEr?vtqt_qTZ3~z;Q9hCSMGW_s)(aqbB){Ik=T&bPD7L zWC-Z)6pY~GJe(QMjyaDG4-XCx4h{_3G2qSXSFcA#0B#|CVq$7^^!e|y;pdU1{{4jm z?KP=txChy4*1Uf6@X_m6BUEizl3}LAE{LnEt|bQ^LgpF9I2M90?o>T>az;qnD!P_} zH=B-5KcnvBlQ36VQBar&=idHK=I9t5U7?5=KrIxZMsNs$)iOaXSS7iL47ue(brpwVYL^?V zO6AEy0yt`8&6=9>GOK1%Ple0zFLEK;StBcA_NJK)WPN&it@ZbuvC(A6 z{06@k5oZI(Tv%{|M8;5OQ^PPCtmgCN3L5Ykn1G>Tg=jB@>5RNRg#{u3fWWAA^tux( zMI8lP9#%h#qYose{uhLcRG{_jx=SO7KlkI~5?F_};Mp73X}PIWKyZg;>#d^0)fsCOHa23_Qr^S)D-QlRH-D`H!)L@^ZkMrjp?9*PNMi&N zIv$8={5EeYE;??CittYuVWN^J>V!mA=VxiIw+#W>6t`6Wx6XE5)#p2gf8^&a%1Jw4GA$&{g8kWe{jqe=$YaJhqeiyJqox^Nyl zz8YiJpC_*!fxI4G70GqdWn|%&@~F~GJv*zb$ZZK2VWk0cf<8Uhn-w1T3_G;Jiunzh zapc>$!CJ5}qQX~2Mar$nx68=esH?P+mr#>q4hD|-IvnVR1oirzJOA<5Uw--Pe^`(4 z-FHTuuDl@ER7zA4apJuk9-qy+W#(u+=5w?*yQ66Hu0gFXXAC9YCjVMn7ea`Xs@;_* z%uaAC1B#Jqgkmv(cW7v+tt}|9N)1^H73}0#2nCs$%O^^hgLX!#7r>aLtD8CCoPlk? z+u9mqlXy60cs6Zg7!&WER&o?2Swk(~Y@b7;sQ47si(vDO;01U3=AY<@jC=ry#k6%!!4x^1_fm0*|R5)DJf$0k%{-Lq(U@>6u$48(z3*0 ze7Z?EUu9)e6R`?z+u3KGS7k-9dAsq?-Zh`Oo)C=EGH0KrVY^RPBdahBtmsXOr5Mc- z(t&iQPfm_YO48r@_oZc8lO9~u;V2@5C3XQ>=&!C0Mx7mhYBgw0E$&t;Xt7!cFvC_& z;6(QCYIoP2+f-ttWf{Ke>#v2vp5OP znOH^AX~mN@<9X*Ej|7B8kil$yoSnrtHZW$gdShgx1Cj?vMn15H0$u>0Fg7l0Ktoo7 z8;}5-?s)&6pfQq# z^VO^EZuChq;@4GoUST~^I7h-@@GlI+$S>)Em5e?l3KbGQSruA8?(nmNl>9zRhBOjl zq#Xazdw6HDWNr+woO{DOu7{*HKz zUB_BbGL>OC^925b`3!s8tjU#KXNjl>k3nTv)ayhYoSGawgE^>NazN0l&Za-~ViGgU zf^)l;fpd*Q26-NvwQ-O=GfpSWzynZ|bc6bi3uO1atsL|^CC2I10pYTdzVnD=k~x+k z(Zxgp%{-nKDm=T{RKUAo5Fm8W2n?7i4PPn}T95&&xSV)J&Z?WJ!o;!UjuR zpEskrNbHTL;VU5pr6>Q>-maqP;j^1xO=(KQ0!Jnp`eq_RhGxVW=Be!+d*a*;c!xP+ zkx?_=&cG+263Z)8MX~usWASs0NwM;>dD+WvnV(Z*AeLa46m-{iVCSD_xsp z#m<}1>3TnnDg8`)ey-8&5O#s$I~QHzO!E=xP1;q~EcEL)sB2I9A3yF7mF|(9En-E7)y4wbVnuw=idWoHecy^V~YW{|SfjW9g1+4Jz2 ztE%g3`5tE&`9Cr$$$oQgHC!wI0hP7uxRu^hodiIRj^kVeH_zG;-OyN3Y2~IgKxd?n zrc(whQ^*~hy%!Z5v|DWT&=_jcct@Sa+ttQ7{dGK zXQB)@1t?T9$k`7d6-MkGs3*H%00K6$XW25Z5;|+$nt1$mgy7uPic;o9dVM`xp%@#c zUND3rSdE+m_Y{___tsw*>g%~~5mTXWYiqo5j4P2(a$zupdI5SxCjl$bqFye0=ehgb ze=Gnq4^Ru@5q$%7hM)LuJJC^mLgeF*8M{-I&*-qY0~)h1Aw?K;)(1MB(X)zIGuNks(ppO<1e7hRJRv zyCu_$@aq$dKoN&cr6Ro~&j2qX;xVW+Y)MtmMvfB08dYaPCRK%GUhxwF!1cz0FhnXPi&9j?^^Vw3F^})U)Cr!<=W?HI^NG+4_^ zwSqB*-c|2#;o?;}vHXGxBjCBm;FNpc%O3yZ8}S1BhHn6Cz@H4GnZH*8#DJ+iV++N> z7g{zkX3T38e=!Z6C_GWG;n$E}7;~Nk5Jd%HF&PQ{h?OZajnFYU=KTBv=4l!10$af_ z3JX;yb#3xART!$Z!j{d^cQKota|tk(=v#^?8f&*C@sn2x-XHlBdQ8-gTV!@V(MQ{wir+l#{I;V#iok&H%vFjsZ&e$|P%}=;B*_$M-z;+$m z&|@=5e{h*y$AZ-!R;7#n%l7VEgilbkM1Uq3!v;pt`i6!;)WO%UpHf96k9fd_34_M> z?)~~}ZoeNJ-{hx{`l%TT3FGApEIHJR;o*0qL&MWk*sz0>0qJ0b-k@Hj9*5qG;El|! z)|1Qn*sxFaqlgA0;!1H>a`{paQQBS9OydB2hAgYfOQcTEM6>`nAvm&mxqL|;o=U40 zk!7K!Ae~h?so!G2ajgB-fTa;uvFXX{>n^dbuyZt`MkDw!_dekn1eowna-n6dT0M(_@q+&(mTVR>ONg`VLbgfB$at_g z3_G<4j9PY9j5IRl9)UU9w^sn8w)PD{C0IJ0fndtp>#PU2K;_U!x&qLHmbwcIGo6Dc zPsXO{koEiT{cJQZeE5)#vk+s524)*y!@Mb%>woktDu%FNwgI<6hQi3`2pJLv$IJ|8 zi|^65>@f7q*29OW6iA&?B7OmptE!!F%;vqpFcb17ZNr-%4}MP+R^8H+jopK1VE*!g z=-`5lCT!D$lOmARj4+T;2Kt}b(pcUZORK4_sB~mh2lw%4E>g8A8OCfe zVG5w&l>yQ;{`NcmK!SlGMBoP{rCPKDY;Fsy$SMNFU-IX~p`{sh)Qbcy3~3{7Sqj%< zq{%!A$phw&*BH! zFOnX|FJSNP`e#@%nO<{&#*3hv<6_q;Is3I@f>ZD;luY~7r~P63(WWKKkCm0c}#~= z#(2s0IxApU^a;A7%6&%rCgxLb$%G%-J3J^TRENTJG~CMY^NFSAICG$`J~%exe0*B~ zu|HwsI~sT3GOw^`GXT}qa!xoL5B-w1EXD;i^Fmz1gmbEv965S>8AXLJ@SSRy(MEn5 zaW31b99yAg!EViJ6=<@ze){pppIGhpr=O^KqJZz`AAacVv7n9|TBoaYcwRgqsK^L7 zCWwMadv;n^=N$4>r_{|+h&EN>MG`+ zHW$+26IhCK5}!+`MJ_1Z+g({T&m|bThE%LGPk=;S0E$Lk{m(y9I`qrm|6u{q-+%e# zZ~w{m*Z=r0zx?Oz+pZQ7lc>p{X9Y_*d92yIoY~IdqP!>O(27jJlaY_TV@-aU_jNt# zr6sC1qdv`^$Av{0KSGq;%18PFxEndtAJEn43)B8Te;FTtJ0L4~7qgJ;el<0@yn-?k z83^nh)Q+rQqyY8o=oKqgyL%!*W zV+*#yli}V&E&cg(#=ykHglrqZdzQ!S!h)=Lch_~f)U$JbpOR7|tS}YXeO2_jB-sBBd3ifzQN}K4G($uU45(EOIqPpk-K4a1m>G8q< zD%G?*2P@SZS=H!fUf(b3ar96r+oRWwP~+z@!<~eQh{tRqG7{C*ZT7;1IeW|z>Lox9 zWM-BhUEhKgJLCY(a^~!X2Fi@rz$m*E&9v=)}t|t5L?^rV~Q`~3OQ%4UeXEw{(c)?T5vE^xR zkN8_4Uc&bD2AQAoZA)$L`w#lH3gWB#s#dBadReH~#6Wg&( zu~DQqqa{1<$c_wTF(U=7dRwa-YuyDl5yXETRjWaASo|cSMA)U~U(~ zT%M$npArP%S#&5DE@<}Q!Q0g8T=gJ~89rE0z+CI#mk}R1dKpcq0LdexxXSIRTw%@A zq_4?ismo?2iN1|9eMQxR0*U09Yadm!)-Vj_P~3hYdKVvCN4{$On&iR#}kmpjdn~3P6V74nxSsd~h34Ze3+N;o^6liK11Rsf3&vPz!i@=2uZOs1wllS%jFw63tgMK0Wasu{p5tmo;=r$YcE% zz4YnX!T#c>Ks?r@!EA^Q<=WXD)H@?1?48d$FD_vews?&_8)x;ZRB7~b8M(&}D>B-< zG~!MAdXh#sT3ekbTP;VR1e*Lf#n7m9Q&R(jaODRN=+u&uB616-M?HMl|M>S8Q`0&X z=b+$TqPl5$MWrvZ?HxTmOIPOT$V#oSOCadraC=Lu<}Pz+a!b!OvQwZ~(p+n6&d%)N zs|OM#4v$J$U0jRM{wuM1^{y7|!m2~AnvtLyqD)hyBTxSAUOsP*kblnwJRh9aWsX=}BjiTY7C@3^dn zTMS`Oog~bk2e}};#WL_WG9>G)t&VDHDlWm6ViO>pDw)(}O$IAtj8U;T;sl1?R*^?n9n+38>CQRL@)OS~yWB8*uK zI!rojyT%o;B4bS@oY~mj-Q{H#DdnwvzYY4@@>zV}v2_^zaKs{L0;X$6NEyxICnAHW z_EfPZt_YQ6|A1?b-5s;yHdq#VH<@$*c4hU;=kfQiUOwu-&!#m;PkRIn_2&M5=lT&T zMhisfNATteG!bi1onNLR=-qH_6r)mAQSDKcfa)(|C1BCGUm++GO|BTJg%=j7wgBaN zdj}u)3R?s#yyd57Iv!k4X%W4l%jrEA*Ny9y;z@2~8rVGRzuEbCaot|SfH@wXx!D|v zo1OvewCJbY?wNippW|wnV2JE952YGBENE(^qU?s-^=px00Pu|5dexh|E_cW+o1@!X z2+BbZv-RuemtKwF_m;Idrtdn1L|sWgI@SQzz)$<)k_it#aMH3fi&0 zucjKorG;3;RoM7(vT%>Rxa9OCt=0-fPAplb9J|Q2yu6bME)k?p4`mcyz?$L$bw$PT zu|CL^67>ew6=1j+a2mnU5g^?Tj^g}`+m8WY4N1-cnv^I1^l5C&B4ioGFfZaYY-7~sE~8VEvma3L-oLknC()m? zqNsRcULv2%V38{`D-9jP_Z2zxW%h@yWR649Ym*%(Pah=>q1V^d1wRxTVlOT}qkB&T z&>WuY!phgw2O_o%pOcuIt3y+}5*nr*_)^`Zl4WFP%H=U^NKR@_d^(zOs;ju#DMz=z z&7qk>RgcIF7Lsc!LLDkCYi>kvjk;6sau|g6A=PMhOsao1`yaD>m^081Jo(hnD4V&` z!McuS0kd?quFm(i(-+K7y~+1QwyPc}?`WPBV$oc-Xu6jobf zRfl;ZLHm^v!ND@DD|-17$>O)CPpG?pLn}k4hfr!y1B|z1_1-!T+*pr?uBnX26++9^ zqoce6X7LqlZ?VGE=OyO~62+jGL=ROXV6PeospCdF=C34{83 z7HkR88KwXRT~3-(U$%f_(9Q}BH=`KV0?1&?pI0x~zTIl3thIM`dKR%k)`AdmeL8LV>hxFn&pf?pU?V9$nyt0$Q%K;85mYj}xd%T{bJu;4$+o^J*TOB;X zTT1$e@uf#zS{@pF{`>E-0@*V%bK34S7ikPDen&MiXgl=(syYvbsIshGBRS_Bl#CLL zZFk?E^sm3~&a~ZQY!jFz&`==I-8o{v6Mc&!@aoz=|6=Ju^S=+~<>1 zGrwFBW*zZKPL9ph!oCORaIil=KaEc_8&l3!S5Hp8|L!-py8GRJX2ES-a>m_7thRoK z&_oYJi>$|fda4Dnk^r8Q4t!x1122F*yqzH4wpPN;Ala_Y&aRGjGHi9| zh&3{V#J<`n{e}8^dxG}$ws(N>>r3&1^aS&AoM3=+MNFrf`hn1!ETy}?z8)NxqLnNw z`v^XB)BjJP4{R^mrRD_0e-mUXDjE-m=&92zkJUf$D zG*G9Nv?mt71M}4#4SlHf1*Wd-nHwps9onpQSFiIvhTN~?sWkKA! z5xPA(URaFX^jZA+{{7SxnxD>q%Fzsmtl8Y$`iAv!+dCWUD^~eU2qI)CoYVgLjq|Es zl96$rkcj~K-o3|<$wwAc08RuH9BkOH3us)pLhXy4ec6y$m*_$p-nTvYmsjTJq{jV> zt1J5l$*+(@N)|-swS^8i8RSe9wStZhXi1Gr^j@-x5uA2n9;f~Q40g;DW)C3ZKD!z6 za+&ci&0t0@cSilhRtmIiO`or++!#|(w-|x4%Ysozaif7W<&GVwZrWt3AO-LV-sf#YMKp!Eg`}+$E zGiZW<81wdulL%MP4K%a#^f^N;Fsy zm{r|zc*uf+@B$xYA03)8EXGHU`68zXD~5HM7uW(L7QR~Hg2@ET|M|(NK&8JLTvS9B zo9gR}isC-;vT1=3JG`+qt&-7}0 zW(gZ!ynOirFfsDt)srWX6~wN|?BvhL2ul3@hYz}WfaUP^#>bbIA~Z=B6uD8n@7%1O zrN}tVbUZ!Vf}Nh97eP^H`)!-K#Tb{+#Oe(o14n&5gffR#zWox_qJQEFf5v~9^=+Pk z>md(m&NE~Y)>I3sT6bmG2r>E!Ar7AiGW(-HvTxyT>$OEBi5}7J{g1Q1{7#s;1wF!@_av)(=12{$XICjYIK< zPmiqB`i81XyOCk-5LsZ^mL7>2Y3wW3+cnpRC}IW_7Qj5y5^BBoag){1s)cx%3vZwK z*q6Z^W0?s*#wQE@A20B%xTFbYoI4e_%34jFa-MQ>p!cX#A-80Y>QVG0?i^vj4Uv*#njPoCX1!uK0tYR-ob zP2B_{Bk5)$Bg(jX^Iv6=5eFFk^|d*xT&Bi9&(3Low)FGpT5KE;lgaqhc{oI-QsK$U zMaovWujyn>#Y@VhRd;0i%Y*gpGcO`2qBdDAijCFCa+cB$5XDymHdBreX7qZo^C}bD ztjrY`VY}i`Q;SZD$m$|?NaWBwJ@~*q{Wx{BzYYBqrydn>1dgq{yRWZfTdfucbne64D0gT|1-AfuVl49JG}hpD>*7SQ$e_Aza{z9Drb$EU==Xg6~6h~ARZ zl{}4wg;gr(A;-CeM(NBY60MZdIVtsRGQX#aE&>S&mzABK>{Csvk0gtZezyN5{Vc1h zLJuRqeh#|dkOJ4&cl-8F96$c_Bcr%~`0px27Rd*LY{Z+ywlkpZoy zx32fe4%GVAo=Sv<>g;S~2@$q;O zxaQV2{W0KdcRw{*&gD=COG(H}I6Jzb!K&`WlbU@NnHj1|W;9>`*`g(IZ%p@wN z4$>0KRb;-BxlbcaklvuhHw^WO_wzXdx9J+n`=FOyuZ{6(ci(Y(u%PZy6ag>+Y z?ae#}7%$kd+#K?Su^B22B}^~c&H>5#=zV8pnT3w*3O_iOF>h8X=1P+#OJ7o2Twd<{fly6q@N79w^ABnPslh9d2UA&Lw?baY4ouB;^@Y@1 z>t7~~rdB)4+E_Q+MZqC@Hov+GTZ`uNb@8)UNpsrO#J-Y3+0kClY(TgPLVZxGxIyZU zrsisTz8XuA>8F?ZMU-%t4kfwjDnA&B-kzIyP}c#XG+w^i%PcGV(dt3!{mE|>7jN$b z-D6k*j^F)#uCLIDVSuTPrN${ZWZt>H0e_Ouq{pXqad*QOl%Jf6jfib?i}$|A+-+DE z{WbHfmH)1;t8^v0e#7JDjVt|a7{2oZyTR74tIsCW8DU5vKB{{{UbzFJCOs5uc&njq zWLfyw<;6vZ>>e;F?`nppVF{tdSLTOl^XE9fE!kx@5V0T4kA)_3jS*pA4DQEygv{qg z$z-%K`FzJm-@Z-L=M^VTuXkpBJ@V_J-PF1=%4IgOu_kp6{pesO7$am|)B=UqH*egmICNh%7!J{a^0gzw_&> zS06tT(Wb_FX({qj57?KgW3D3mLQ#>u2Aj!(#J_86YL1zW=o*o+?5Lg}GdG1GoMzJ( z78pN9w$g@XIjp~EZE5ci<&lYtFIG~*Ylj-V6-k!c*_1?{! zH*Z|McJ=B&e+xMewbmq(k*ws2U=H`?0n8wCO3~ch=7u^DWr(YsrbiweTKj=Cp}u{u z{Jdn81(yhgf(|Y=%kubS-(LN&ji+YT}=FTjej6MOq^To)1l^wVGdVouyIfBEI- zzy9r)|ASn+%^1AqJ`)Q;%9(Z#uhIveH&=u849~W^XK3)+^; zrI0Q(99FFj<`qD3i0Z_K89NN>7sKmea!+Ba*(1z>DJm3WMCHT@ zC^B64oZPIljZHdrIbW3tsMD(a$f+3nOg{@|hP;0B>?u@4ROH>eKRm4W#{a&4@nU%R z83)8$FmR9Ut@ZfnlNT@TC@?~8$g`)AaCiP48FIhE$o-2a$2Rc4sBo)FoZ9z4|MTme zCr_R|{q#w0ZkE03wBmCl#pDsT^fnk;n-zKy`8!~ygDxYPU9jxmag}8wCMnGoBXM)hb`26HKM4tUw?F{itP) zn@Sm*QQh0yOH}guHB_>i`TEdRA}Z((IXUArT0^x-LK?vErfTd*&>%MypTI4x zo-Q*{f(|;P&{KqGrx6)7T0ywGpxqUpTi@)D6@fqL6|<5S0tyB z(bAsBa=4J+Z^VNoa1P`g{av#X3WIJ9XhNb0eXUVPyz(7W+B<=PqA=kXE(kp zh@C^Jgi{f-1ZMItf37&YvOLqno;5A1sPImB2l!u*2FRIJ&P&lBFk3ct#@vor=d82c zT`pA$gRCA=0G~Uv8yvfBk-Z3$r&hhaX%yTdw^Ru%bK7pFw z$zF`whe%2euZ~_VV?Ya>K3TeVqhn+47$E7hva`d*MKM)fUq7b z<2w0m5r%%Arwkh>2l-dw)rx@a&B7f}A(B-S?vy$i?SQi5w4YWz?0(uu#YPV_d>qNz z#k*kgeFr;J$kS;B^9#z#;O<&6uk%?T$Cz-+4h{NE zDTzIXwlbT^+oXE650O`j;UE`U8<2F6US}efnNtHMHCB>GcT8?Qw?D~nK#%|QB0fAk zJR^pXO@;B6+SSKoCYGWcslr=H8lMRpf=~VOakr$qo8HI+E zd?60^+u>n!>_p#+_E^6-{5-%iHmyu|+%Kg&pN@`9``m_S&M42=ff!z5eOlP>?CQuh zkMA>6J)#3!aEa4)YJ;Gzm7&#z?bi6OH5hGE88k}4r<_w;^B8Y-#I0{M` z8Ii4<{<8ROeN8RnwzCVP_K>`F&;YV$9xXnVdKv?V-`CPo4zC|3mk-B-&!nHONGEY7 zhL-dsYpTA;NHVXb6cf1RZ{d$oolnJl(&><7b{j;|C5@*84a+5?dSdXWYDstIaagl%+w1CFbR^uB2PpWIm%a}o64pG#JBws;PFVavC6crKX zq@S-Sa(5>;Pdgzi*wh4X3~X=<1}Xv(b#-38a@7kC->`Ky0b$QLGcRN3!PIA8*xJV2%Z;gcIn200+&Ivp7{W~U z9Ep?0NU&tOF=XzcLf8sn^1`ah#L1m_12h4_u!tnCviBDrMnNRi< zL`+2b$@>pTFbDj_+mV;h$)M$@%#Vz3(uMZLi`N{lsGffH>i&cO<@o16{}ltQfB)^b zDD!*3@#v8qFdjVk?T(qCuitzgo0{4rhg0jYL+{mKV-O*|K4hEQ;&eXLYK_{HxqG?r z%-ps>&Pc1?oB#AqvLTpsy948-o+57)$q+^5)AJWs2$o1@5sa3MH8md^E0G2{2?tBd zu04+MKLo>CRHFLPTCs&ek>)s{ zUscT=Wc@&E`+H<6DFI@|(8}MwF3!)5GvOB;fKImS`Vy0>ztVB>3p1=g z6WLBsL6#Y2;=|ZyC_=O{1J+?Ho;>|S@z*WnK<1x4w{H;*K=lbnhHbC>=#P&dXYe<7 zc6HelVkd4GVGEriE6UQ?i%^ZmO7djCZ*8k+n2qX_hA^J}+|*HX=)K$VLkz9QQC6w~ z31q_R`{QbiJn}cy)9Qcn2$?xAGG$pccHv8MP4tQg-GJYeR@{8qA}fx_g{-e{ZpODq zzqPl6E`S%E`1f7iSLg?R^Tw@PH*d<#qSe3`KGezlB8L&*q?L`tpy|12X8i;Y9+;mh z40j<{nuC(n<%!cCgA5UFxL}1RTsmP--lX2Lx;gR9z$RWquw_gGqPfWs{ zjlK&;?k&A5UeS3L-U5vb#6Y}78gJh{cu3fqh=~P#_nFaPm8!3TDj!CeUp#z>W_~#Q z+{_|9os^}lHB9JCuXEPc(vHrc!Hov-3xcNi>dZH*U+Vi({Hne}F#zjqMB07Em0}s>WBYphf z4?TRuBq-9Lr>6Ks?8Swz-ieC=EFlo>&`}2a#!MDJ|1>1gbc z7`;O>0=!FSr*s?EuUxx6H0Zhjxft?ju~gsF>tFYD+Pxagkb&^Qd87-tMlSWs8amZS(nc9x#);%HHW{ zTX&fpVMlvsS1r}$6_r_ZzrdbQ9Y8({5>HQtdhp1>Pm4)&ve?Y-2_esV8~ zN8S4Df3;&CNW8ut*M$ST5!iw|R0X65FC87JaZ!&^6>1KI6(efO%}x1Yq!zhNXTo>P zygXiAC5EO#4TJ~rWjKOdqgAjRhS}T0O%lI^w`xLG(dlV$An0>`PNi&a9cq~g4r|6) zfRs#j*-PDMiO@jyFvrCiX75H#rGNE$WEijjVgNqycu%MBsi{eh36Amc340frtvdPU z?V~?XzA-8o?X|d=tWP*3_Q6`Wea*=?IucUvITf({||2 zq1?z!sG^!ciBMH56>bAPf~u;*0#H9O5m6t98_<&{=3mlHs5pH3vY^ea@DfU5vkH!g zPG(0YBw~kNGaE`4&(FjsVM0p66?)M!#+T@0P+p!q>EvF9(0GAdp_P7S)@3p;Fe`DT z+=TK9<&oP0Izp)&dbM(w>+9Rws$)GE84Mob#o#*A194>~)iD{p)U`A35%?V4JI?VLz5QwI^_vHoUWa!96rhSsSGfE4AOA7>UhN(N9>1ZaBlZaY zo43!NjgQa6+JS3`YcDNXnYx3Kx5`UY@4DCO-T)IMHyaykYLrl{EujnTi8eRR{E`y9 z^5F5)b7%||GfA~p(>Sl#h-)zN-dH4LYwPe(jpMUZ$}rE`h9*aNE){ z@h@JL&Il3l!}D^kgLSm*Ec9l6!M+SV@|O5xzn-5=q6pG7I42i87F|q~Mg3lwPN_N0 zusHGQ%xBlT;33*E6IOk|#w)_ZNz%<8Vi(~olCTyG2@J17?OJ-b)d*e$femV+KYWBn zdo%JyBdYf+Dw7vD9dFwF#_jDc$B^_4qsyh_Jg4 ztQJ-wFYmmEgrsYf3x!wV42mkc3;0{+kTy4&5rX8!!Hi4enW*U+=5fj-t2@o3vMRW{|hEFy( zj(StvKS0O3$B$**f_aPJG?gR7s>$}ebMM}pH)I+45=KhMBKq+0vl;lGS604$otlCI zjJ)KPm5mKlvb+oml-N7l5Oo~YlJW%2=EI?D>X>4KhJf3_DFQkLj#3%WzpuTWKm`a< zP9fQC$QrD6Wz~f&(KSjoy4bMQQOs;pcFQR-iQxY;%31Qq*S6~fVng@VkcFsUP=eVa zWSLr})YaA7o4o#kfuW(HtJkkyCuO0ltC@U}h*c&~Ex1NaHaGY6-r%@)9mov+xO}Cz zN7?Y2o0_PAgWJ=>=#P?_$J7-20-v6gl&XdB4uvItPCu&k$YpfQitIRzxNNc>p8fR- z3hh;o#NvbY^|A9RwQI7zWS=ymoOua)nj^A0knj^ut>pVX4P8Sda0ugz{j4Fsh1y7%__rZr|WAX2N6VKG4-#MU-JyjdJ%n~H^CjC zS}@89@tZB?N;>guZjLAZDsqnaFcn_rj_}4Y`#r!ryola~eVT+T@E%sL%$Z<>*+;9& zxe-++nh5lcsrLNDtXa!bQ*Ylsh)U`^zx_@JtvkO;8hifw>)pFAUc8|bBB$y2$o;E` zhxl~w-W@pP-)ZM5_IU6B_JTQRFJ4SflXHs_^y6d^LDtkLLvmwaJ`Aq2hD2QX1Z9^?3tMcyJ#$%y?jFCy0+Fevs13Sv5OPm5EanLvT85dIH=|mS6CA@^5XND8TDf9m;=ZR)}?P4@}r$S;^XW+D~(i=U|Ce5-^t3t zLji>03~>N^TXpc%cWrG-I-&qrPIAk?!ok+-4~78|=QpAsAGSKWoE79JFKQbpNFG$O zhuSM={kn%dAe^|7$>Xb_b4tR=A{W;xc2h*mtExIWu+M#0z!1WT#K;CKd%3AGL<^jc zX%XOMY;`$)Yg9R*qs`Al84~*Ar`Qlhg|0p@3M{o35|PmG=GOjqGmkkyCIL^_?_MOi z9dA~OI=j}8lDk*a$mGtdYN>UW&ozt8>!@I4qgPdT zv;&nn0L>g2jczbB;@*C#vdT+H2kHPV#0QO3`!o|aIH zE5tq|k3Dy`iR-$ZaRB^W$s(B(n3H4myUaxL?=aKY(1@VGC+fqiU5GZZtnT={d>}>E z(c$*?;zA%>yfOVVJU#L=M-kmIe6`Qxi;HGvtgS7tEPY#EaRaQa(nkm_J~KT&@#M*G zzoVW_bo-6Jp?S&rv@`9!fB$~({=0YXhqrG?&8L$%0YHbfzkWA5`p&+#ubC_b${3%V zm}cq-%nKhhLYL5eCnx&Nv_6fdm;9xpt4lkFV?{PmOG_S;blL5CxO{a3>$Ord&F>x_ zDw7B!Qu&|$5w5(s#wRDP?Q(E722R)4^#g;>B6lG-&z?e{;mq!VSYYeeqW~>*v7`j4 zal11OaeIp$hvH6gY%0aG)LU&AX_n1JqrEuw?Jyd_-7$la2~R1IAX*4^MAe7ZD=)iv zsjnBA>G6}gSB)9!3t+XuqRYz=mGkx;!aWY3pNe$ceuQIrYcnTTpHPb*SPP6?Roy~G zUvyx+SX6ASq{yFo5q4l$qCih2OuiQ%U5nvwzNouIq@$igzvxDA zl4HjVCO!t1q_xE;U)rzQ2rVj+5JWG0Yw&F7>0pYKP&6ofphpl>ktP7Q!6D)}K3Z6G zw(b`)AZD2PBgvu8W*RSg?z?%`a+Bd-vUq{E66Vkjf8SU7)zYgg-s9PMWEx_^0N&W= z#l;o+-5a5iM`r2>^zau7Pke3_l%dfi8`aB^HIA4JOVC> zkOJN#M=QjF7wMz1v7tWxM&!qL*KPn^)yu|MQHxqm)Kyq;jJ#=Cm|**oD8Yd`GOitR zjaUO@u}0iUdT*s@rotnWPzLV6*72Z9{oI+(usiZ_INKl$WSM+qHJ(_qqP(HO+Bo`M zz(sX*G&K=n&Qx*3z=i%!m{kp4U)SBOj_&Smi#SMjH^F7Ep20QS0SHa4*=`$3y=1=+Ps-JmiT8$(uKy$D!)RCdMh@ z!MSrUs8qNzjzB&5c~(I0<6F5txB`9%+vNG1eUu^k;-Y87(L{p#vYkhkDTuOA%a#we zHg*)kQIPL%#dR``C}ZQfIFu7V(%PyVBt`&{P6829qP1>q6KRkEhfUQ&$Xpl>(KcpL ze`&^r-kruIqzB;?9~R=7%g)7tIQn6^YA+jI%S^*8D~3et?sPtN1W5FCtE=kk%gS|a z^>JNcT&t*&l;%X;czCrY(N z9r;7z(p}#F}s09#{q7sRo z7s1Lk2MQL8bah;ojf*jC{UqvKxg6QQpuD7TjaCQ7z`Zh1Qcr9w zatcpRj8O?OQyz}{PA5>jze~Q&xzFbXvuI!=qMUfA)Qz>5l*n^Rf#E$7-3xp{_7c4V zI4UY)fmz`ej@Gow{*AxNsRV-4+99S)-wXs37sgZ6Sc1grV^X*_6shMB)7x2CTd=!B zP6Z%Y76Oo=H*FUSPs<)59B^S!;eh+NVK(ulbE^-~eGHkac4hlz2p0a0pyhHvxznVCQZQIkFP z86=T#g{WdgaB=?p;>E|0vLbWLShd?Hg3O^PfB7;oweWRq-B>2D1E1Ako8Fk1)r)6F zaXzZ9(yIPQB8s(ck&SC^#S+^M8KZ_ci72p6IvMfR|3NmaRwyaN_Nf6if9ZbcN$>2& zmX`y4r<#i2*z)dtbrjyXgZU=*fQ)zswBS4=sS9g>~Jme`4yICf=aZB1tv8||`> zD_5@ggkiBMPP*d!{7fJ{>A+BeNfDl$hyYVw23{T~I;p24!=K?gJV?WcJ~Le3S39)K&#kQ<9h(;|tFnu1*-Iee#`51Fn&T5^ zE9-ZAXG9z3=*od&Olyho@bQ&k9dV!HFPUef+a$ST`ywC`&i-x!9*|{p63X|TOWEYC$~H} zMqMCvy3q|+%inb#tH#;zl0F;^2R095f;mx|n;HQ?;ZA385mJp<2UhMSn@=kia0hwO zugs$kUF*d`cVO0(rGg_Po}pe*cgqv2aksaU2r7mfM3=YllVh#WkZ>a-GH=d*`iS)Kl z=Gnu1DRU|HsztF{b1?#Un%!YUKOO(yEASdd)AE^Bi!_5y-1_#rLM(V8C4(MYbWU^` zg}N%10(uMaJqb1XBVO(UQ>fb1aUu-pIYEZ<(b;j%A=OgxP+XEv7P_WF59aKYK4Avh zs8KE{#YS_WXw%7;!y|ioZEuGd$CKO%gaWsaC~%Q0q!w<7%4w_^>n6HrZZ4qXW1fSreBA17u}s3H1SXp)rR@ zEm>Slu3X#=b?pAZD%04t<+}U+qvBl5Mow?;p4kwD5~i#~>vYF_cc7rGn@(KgA9S957C z)S*sPU0j@>pBM)Vf8by``?F`Ha+1s$or&%~eE96S%-cI7e2BEY<|f9;)(d+3EUSRN zANE(=4@suevrcVxJ{=Q$r@ik-#JQX-9kvrQf_$kkd8V6CgG`rHm-nHuL2IdHRZk** z=$n$_-96bowr6ZVow5(Ja_) z-SNT#Ihtw|y#8NVxv~x#=i~>Gc{rPFESXe*aS~MA{&)927=WKLD@EBaEj8yKBA7U2 zdpq@abg+@_6V;HlhfQNgmL44=OKyHtS{8lz`I%VZ$WKBMQ)gM%W!80PC+uK*M_Vfv z0?-1T#LK7{l<%Be7;vU4C}vw*-}ih)HW=FsrUjsAuZ{dPOPoqFBOJkjYfZSboGNfK zNz9d006`V-jn0op%J`fq2SZXXJKOj3p}j0D3J=p&OFB8czKu=y4*eB4X6L>wuG(3P zoT2dRU>#;=zAo6a1L>lh3y5Itt8u^Z4O3)r0`U9#m2;s;O}=kk_8*U+;^5+5zIes) z+OQw(@2985JInUC1L}RZqZm04hipweJq~RxJee6>pk?E+S@g{b%~ufd0bzboWkqJf zErhBFp2~oBzPdU)+t=8mtA{(!el(b#U^x;CpoS_eMAO9D=yzFvYn(oG+KUD zwfkDEj`X#6bWn?Jl?%}nxfXX{MTPkf@qy6haDYV7LWWB*2FQpHB1~vyxp6;8TbmhN zE-!`&9@(?1>U`cILP_4m!GSCSvKf+`iSg{iBfM)&~6@V*j74PB2xkeOuo=sUKa%x)u~zn0^oGk z+D#aTTsmZULBtDG1}_s+nFKJ5kG_BW_`&_4;-KRmp@(f6hZMgw5C0W0Y~vt_flHyu z#RZVdv>j8KU4eYT9Kc%~9vX*@>?a3wGTIC15&Q>>F7PEw>K*_RKW5Q)`p78ZzBLg8UtxXa3O zVI@=1^fHYxn8P4`^iv1=Q2x23!$afg+bM&F>f<1Jq%6_LC~JPEkluNu6X6**N3Rh2 z*r0nG8j6a@L1q24B-uHBCgd1lvP&wSD^>@E9hFm>v&xF*mTr2@beR%I5Zk(1uS#SU zXY8{A=WDZGs>HKXcakdzMnXXMq40GO4#`pjDV^|&|PM=4hGG4!$nB-D}QfAxDr>Kh6 zDf3^ET6P^$=j;w zOmAFsHVgJ4FANmKM`~ zWgL~`a_Ty%`QT8F@uS$xr{w606qRH!QFP8bmt(G-i_gogu8ycaujTrfFXW_kdkr<) z!IsR|+#K`HSsjcMbtY(H-ifSEa9Vf+0yBHocK7t~=;`k6>Fj`svy~p(Ll2ag4bsfS zA`ae^H--^ml48`L@^|ur5G)|WDWA*LRZ+l#MpT7cpk?#cm;4GO-%i%6DCQuAj*exXD64aQgs|31T*Btvv=>v(m3ZX zW+th1{e<7=Z=1O>J11hbqr?)uLZdee6&<=i%CXra$`IB0rOiz9v|dA23rtMMbxSi` z+4NLbTTtjy#e+pVJxf+Omzp+W*&snN8QL$RBrG$cR7jORf)L9i&(48f+55hY4;m;M zrJizF^g%$*sAP3aO?A6cOZ>c#1I|jW4OI-7>=09SA;&efYVXh{C=)Vlb;krgI*wpq zqdaY`<}>JJwL)yv)QIu9Fs>}|$izhQ=zftl70(Cz#+>6WdK)>gAoe&!UiI)=*c45T zyc25^^oMDv&8;mZC3|~vb>b5DdDaEMDh5BS3wxQ>VCLW@x+eEn!$ruitLx%LYixN@ z6JLn6_#S*FC{2-29{D=?`D{jhJLOsAu+9KhxB;zBtO77U_x9{G(dCS8TPnsGLE%}I8Q`RNUmpNQmTMG}H z&RXWiCCqH>`Oi8Bye8)Cg_T1tz^I5Z-uj;mRYd%bkF%)>L2<4{!C*ywITX6bX--D8$S@Wx1XXNSH~ zcXx9$l8BICv3aY?N~`|DG)U>0KT9sm9zhk_<1= z((HAFdAU3=;PnK;4<^HXCXB1Xdsb>?ED8$1(QHNeaIL;tIXfqx6}E1Y3~)?d34Mo9 zn@~}wCr8GxB1*ZuJOzI{i4L2b8mF(N8qAyu7F~Lcb8YSTNWVZ!j8P(25J3nCqhMQ$ zVy%k(F*l>)M=S2sB#e6amq;vDev?l6-Y>P(8T)G=8-UJ^zRE-ZA?D_ZaJ+nJRGQ>W zox8gaA3cBejB+TnFiKcu{P^*+=l1Fhr5hLhky#{mm-GC<@sU0*A3nYqdG>r{czj}E z0bXutW7B=(C)TI6F8D&09dsj=2W`{Jth&U60n^ZLf(e)`YQ(Wunk??~Au}?QlcWxH z9Vnw_g_F0gB+F5_>gszQXtZ>m(ZShcJ50FU;aoR<0ENJ-Knd7+PSx8HUEl2=t8{XU zxy>319HWklt#(2ZODAAgNxyIqCUVB^^MX;x&ge5Y10>3BxO9;jXux|7g_=>!!9++Z zICXf9k;%5;lyN)sZ!#x$mYI0wpqPe2e475c!0+Hv@D=nMsMxHu)1O-W_}B~={SFv7 zBYV^dSZ!8_g=z=McWOo0gPoo3H4nBMD{opHTs-re0mw8$4$)#y&lL`smTa2X~<- z{>>8;8i;*}Dbk53KPCSS{G1{o&Lok=6G!+FW@p)dE9+Fu(gzOwV#Tc0@p?{;WYDx< z>Vi2kPjYDRhuc5@{L9b3{N-4ATwn3!)fI;ZyKtG^sWC#xp_4lg*h2eGHtPxpRntdmZr+SGXE#jUcaRHZwO57WxvkFxKz3i~s z=sHPMn;Yy<&-LGa&hiqPhXk(qc{r`FOU!biDg!8LE}AG*4iDuO%zzmgC-~cm%{zey zHYr7q5I~m1NsAsB;ea|%u~&LO=XCCq2Mo6v`%Xy;apJ2##U!$DTaqE!A#-P2Wx==H zsCv`rYjVfU!%EmneIC9v-G*D}wr|Y>choel|D5O#@LdO+5CwASVoNiiEi8n%EHOeT zG9H2Gv8UimKT90BxqdIAQ+!49=n6-C6l2W>t&YlKbP!6y8_Eky?w+4sp!=8~E-NJq zHD8UXzw3O@@Je~os-}NH8u{vo!uf0(3|HjeBvusD77;&Ha~(AgM47Bt^K;;ztDY+9 zVyGtPj2ua=dh)7*BT%1_y=jC6-w0kmSxxPl7A#G&3Dyn<#2j+@TNQzo=I~z%9}?s} z3SztQ1w z+nGW_Ep< zKwXi1A+(358hPDDm0a|Md)bsYaDZznLbPMjB}5A0OVG)BWZXYQ;?I-6#Y5?ETU#Fv z%b6P-2kJUNwHTTz2si~k&o&dqR*0mS(*qgzbr~g^z_Z9%G%ONHhw7X~gXIu^ZPG0R y^aL_dQS0GQfe9P7zrVta7~mN=#B4RT5Z^gH(W(F9z_%3138qy+&Jk@Be z+VWTk{W^1t_0l<-a6&_8aK4_n-NEn&w3bXsP6lEfwtE8}p35tGEzm@leLO7W68nUqd3~-P&oNm?k+asM=FuyP2n3gV4j<>Un+nEyOO9= z3GYmGBQWVFH!qF=#Yp4ETH^rlpAO7PSC3&dVDion0O`#G{Afo9M)puKi@P9d7|qDJG8cw3bZwt0-UZrH@OG0DKGHf8Xq`BvoEoPaNbJ{DGn_yJW)K(fZvrR3&-XaIoR4m(cgA#z|{L{qsL9vH5iI zJ;tpYaY7s_4^a06p5$2&2F|(A%@)^FR^!<#rKbe(Z++I|&btt2I{&f|tD@xxb24fe zOLNJ3Z}9Pl@V&Prz4x-weyL#=dQJ{{w}cc80G(++DV)q>0JKKYgdCywgLgQG#)xQ{$U6bJ72YYnmHu=E$&Z?|=SqhPl4J!C;_eHy)eO^W$kpn}6m`Gp;brS0aC|+A)`#l+bkdY2P+EiJA&>I7*2i0`;FX^IsRYgigEx<##kvcUK0m zsMTZ91fV9A7l6IADjrXhi30_( zVZ#`}DgN}hXec0uqQ}a^-?9n`ve&f7$HpNP!%YqH_f;Q)1+y*_v3#}~4493_L02bJ zb~@@&=?rLsV>@4~7TL;akGy(>N%7IGz9%_Gpl{CoIQhPJ`z5BiV&8E&X>!FH*{ndc zlF%M|*j|$IvClYVCs6(MhsaqG>b&ShWlI{RGu{hV0j{peBzY#Xu<5CDTT8?C5^e9Z z@s-O`Pt5q70|vAGHu&4}OY@*ox3aYB%l3>g>rB&=J$IApL*siTiRgfSPN{FP)OI+K zPnx+7rxvwGiXz9|gf282z^L^D{GU`NaEf9Tx+ICbc$GA&6~#{uE^*TKx~f#abZY9S z2F2b5V<}ILhyBOBZ}wj=_5aWHEg`Z_z<|pS1|0pi;%7GG?*a3}T=)rv`Sjl3A?$Q0 z?UYm=^`AIg3RrRYIX{N#c}$?yNe2GeF^Kc7V*@9wp?C7AvTLDnpGt&?d(p2m%3TG0|73K7>W!i#yBTt5UX5S6$yw%j-sQM& z2CR#0RLNugJfPr$YO<>}dB=Tlk$qT|@(tRpJ|_yTyI#ed{+nk0*Qa4Jd`=)*Fi7;z zjN-SI^Xui6tqNoJdheuq3c=$smFAO0Dwba?3H&KG%T4xVxHEJDZ!hN3H%VNY=!>~xIzuOM#; z9Xtk9nVrNStC=Yxy8tl)@j*a>FV2*h>^*qBKow!N#Y)o!dWfj~M&$go+n6*&C;{o$ zTYF((N{quuF#%qaD0*p#(9AppEX8AR|WQM}_hADBUz=#KvrB<)5f(b+yp zo}dp{ia(%Z&CfZ*<|jiLy7_L^4Wv=Olux+0LgRLqh) z_2HhZr-(}EMM1JgOH@aQIz#A?&3(++CwEN$m?-9m8uJZv<8yIQaMctUX&(*axv{KR zeDIRI$zFdUabKIUOS2cXYI$hq@IWfEL71X=P8u|Ki>E_er#1SxoA(fziz`30vJk6F4r{eN6w_*a-{Sqg(f`*g{{MA-OGq9H820$F z0o&&Ryx@?&HB%6gbZC$P7AP<;m>dF(1X1vH&X3D5v6CSQZH2`K#_{8^9k9CZ(EcVT zBPr8 z_vTg}qbRzukz(BXI04||0_;L7)~!MV3tou+xlM&-V8OfRTj`BvW?S%FD^}mY0tzl= zLFkxd7HPVzvfxaSR)`)weYgC5)VeWD%SecVi$g3R7S}U094k|i(1RAGSkH{Ivqa^^ zKZNj3R+3KYq3L1Y*_CBGBnSKwHXq(W_4%rk`YfXS{S{tA*?m34IzrWra^%sp4kkvq zGN7)ZLRu*8{g%m1S}ef$H+N<8@fK`x+x0)m>UQzxFJ{r(%v36h@!G=t7(ctf2)6BU z8}wa&L2`ZR$0XK`R5zK{v7BuknI`UYjILBz%*~^-K3w;(|9YjZU6C*%@r$=$ISg5o zaPqKt`oa(?t|-mVBP_(j_rE#b5_*6IXu3EdAgkwrSXz|)2wtN?ydZE0AcpF6P{Wla zp-n-5&P%6G|D5{;owd*>g{BaD05byvspGzbsZAR%(NSwXXcqb^7=U65O@wgQZXs$ev{x`$ zx0m!%RGDYuJ8xgX)R~$3KHm`V!o^g-a#J%ENw$Y`TdD_6OCa}<0 zDv5_AeT1%%xe={KU$-Q#Ki;2|G8I)EkRDmz#l0HABGUYD#qqrL>g}?|%m^3K{i5}F z{7i1~^v6G$>Hm%5AI@$GWy=8u>3;OU_U{1R3x}S3Q^q?`TpC`K3oRoKXoQCgeo@&8 z#H$4%YquZck(}*KLE@mqa6P0bR(Ry2JO`u`U*L!kFC&sSn*xbVqDcH%6M(3D=9Y&_ z@q8*;_RCfu*oeQIK#0_*B4|lj|5a0N_z)NM2Of0#@E#-WEBHy`MT)I z*m`>YrHm!4JFRZ@5Kab$$S&J|b1` z@{#3rh^ak>4uT;NFuE2@M1u$JkeHnboTLwteY|jp2FM`TL&g_2w4o@Q!fYs)EZBdL z0|saquVF6)c9cG*krR0A;t$I2aEwc0I~CO^dh`Cxs?Wb;{9oUfmwyc!2Gep?*-!NA znJtIGn-q7t)3laor?&Ku?QDNA7S}fC2bqHSb*m{-h;$C#32b? zQg{?y`TVf8VVsd6GoD&>mNVV})D-j+`vcAfof-&@B`yY<9z2Vcw%SZ!^S~A7i@*w% z=kS-Hu6Sc9HdeoaMvI5yMn8&!p(F5;Txbb+^OkXTPUsGS1%%>#hxG-vYUcYVuW-++lI}bKoHYDl-f!e9N$h9?7!?-%%cnGv4x`hx6wR7q7RTcov#$=Ix~V<{qFWibt7bLS9kjYOk7)A;)!FFtg&VFmLg;zDXboezQ^W@!wNTdYSw3zD_82)qf@^l!}l1Uo}Lnq zz~rCp!T$m2mQYd&pkM1p0j!=|3At1i`Y99W0Nhwmk_3m?Oj}kypCd3C*(T*v}(W!8GP*3cD zuQdzW`PcaM`f}cmTh{ziRiv+km|TdmzZjF4@8l$!fSz)GMG~CT3E|NW^#w&>hsp(| zz>I{l!Tx-|*lIdrDu@`QTn1mykbziCI*R8j#XH1|u+_s!^4X z)xRk;1oDVRZ(=Sn+ZfmD8=qUj9APd-o!@qQ+8RbZEqQZ1Hs{mcrrS=R+s`@K$iKI) z`bIx?bZoARSKOJ?N#S^ZI!Hy4ej``IW@-mI$e`l6@NA(`?SOA(HrSfg_$sS8uK(;y zTB=VfrnewVn+2`#_1nu~)`A_Qo8z7^$-MP zOH#%`P~YuMJLH0;3-CEpC5TK}9pW4|OS*;LBZ-w|BCzv$MoJ~$k0fT_jY!H?heFq6 zkvJ$&zuz!Ca=)u>n8IV|{x>lp;SZ-tf{&d)z#s{mZN^I$#x(+@+*Xd(8F;WCp1Yc` zeBVz>#9sp#Qb4@XW(f>sk}Z; z{*O1Bvfr=<&^T*ZEz?vX?fPs};>RL%h*@Y)j>jGzIuMs=R7WMKc{~s7v@#G=8N9 z3F@fbb<#1Dp$(L0nZ&!W6DCxhGA9XM?{~7$a5|%;!oxrW0n9p&LP_&_)~f+fOe91cmu4u_ zEFu(j0uc_4pr9_D;HOTn7Yy}xAzJW#f@_qGEh9$ll3cP3OD*y4tw$JZk-X((G!F9( zTWY9CNj&#`fh5V%vLnrQkSZIACl4u8X_55JibLUo~ zVsAzqPQjF+G?VLUtOz`RZ)2SlxlT)nF32|(;6*Kg#8I_)gD706Z-hGCkGu%hg_1E! z$AC4K&wliG9Q}^*d^c%f7oH@S%ngbnHt`}LWq(Q{iLKq;FWWbo4fiSGk`WYoV6_kI z5Nbg|EPiom_%~;=XMD`bO^Lkhw9;i_OGlg!Paf14Q%w(VE!HO4b#0K?+9&Vx-hoME z!Hv}LpfElVRa9gEI1~~h76i;N18tg%$8GceTv55YVm`}P)Kk1VW6c{bbGovfBStm` z14c=N7A58(GYO4vMtR;1-`~ERJAGyxE$VZ9ZP|X~jX9mwb}M*xGkvJ$YNz;Wt7TdF zr6~DXesxi6Rm6OYs-K|#M#Q^vFZ^)P*bvE1kg)PpyMo2@<@Jx!-S59_QUpEX{Fqn9 z?O0?K?C-Ro*ni>`_CB$Bf}(BblIKb^W3k~w^nOo)hQ*$Lu>#SRMVNOvsmKX#nT~AG zKbnL8#ORig?=hf{?FR*~p4*8x$D*x0irA_=gYTfpX!KJ*J0@_WC)XDX(SQJew81*@QU;SqXJR^t~zU#Z;S7CMs_%rfdmvLw@1Kduhkv)pKO zaA~et*m4+4;mB&lnOT79Y zjO%r`XZ`w{^Ss-$mfby^Tdy}WS{vl#O#I50IJZZ5hn%kMZlquR;yaMBtXM^-eL9^< zPn2BHOd8s+^xbkci$_r)8hko>1h&&ZNnp5&V=3wZ8?7W)zUbowrBpWfk6&aasl#)X~*ee{q;m3u1g982X`8Csezac$0+ zrelk^nmTO9Hso;{6!njDvn-Mgf=@X8T_O>KB^8nPnwuC2-)G9uEVdCgJl6SaFjO`M zwHzNQ^MC3vOzflf-|VK|63XSjbqd&jy__A~PnlP~4lN&>?HA(@LkiKv%zQ#FC5-+e zE-Yok%?YF>v-Xo=ScrxqLi0X-^2B^a{isLHdZtesU4)iX7zaWLJN?daJp!PFS&1$tgv?Xoas5fNG2_M=jZeYD4d2G%36$Ozo!6JcI#`ck@cft`e zZdu&k?ml-z|9F`nkMKif>V|)7jb))fRE0xe)_73?I7ld)nb4s_*FZ4NEN>{3gL(n_ zlhjL;SAaAtKk+?}@*papNJ~gQEJg}n9W3`)g@y(!Eu-2rR~#pq{`{cFl0&~=1T!M= z=lack)1<$?%_mv3J$moKC0*6>a{dw5re+dmIc2OhT1&e~`cn^^{R{d~buWFQ{*yRi zN|ra9yOVOsp$EZR8;-?zrL;SQW@UDX{8rUJ}!uDbpbtQMFUe6eyaw=&>Rsi3EMN(TR5s_2$bgfC!_;D-ww{S4tX zlax5c?NY`SG9_UqqElH48HOWJydy01BG5uZ&_}tzx&?IELeO{l*x&#a_L!@uzL{Rn znz9|3*rWX@wS(-TIerj)QvXUEWg}9#ljZGv&F|a!Cb>8!p*sA81Udz*0XFaCw!oX+ zI6`VEUXkb-6HrG|P+p9=zdOUdxe_G_6Nt?A-UR*MK=fq81pZZg-q!~UV7e^46fU+X z*gJfS$Bg?q0#98IE~$JmMv77vG(kku=RC51Gli|3O%l+nm>jsOC5=ev9$-fHUlVcm z{zw{1=)@CrXP0n`1=NAxo57xDH@`;VJ9`omi1 zpP#}^x=;u$Q4l|4lW8IgB{ zdk##8#js6`?3z;XDzW9jFS|bTMa6!8>>h#3do&ezEZ=v zp8e5r$&=stVIDV>hw{}S6O*l#PwTZM!6LuR&K4sPXJIdPQ>iU$k}?_+vxo0nwBwoa zTsjA=qSo+StbfL@O&+!=*EwuS5=)*E4_U0vMw5Q27mBQ5N$AV;iW&OH>h+e8+CHH3 z(+?{1*KLELA-<_rt+-e)S{aiVzQ4|m>@#d0#U};dlPdNR^s`%UQ|49fd49KcAXKnp zt&zoUe28;mVC)`l_adaVBM%FDvHsLS-amY-GHQdE^=}h;+q(SoBUW~`;qxzs@Nv^yP-QaRVSHlv%DJ2QgP(c^U z+6CM4hL<#BMzy}-x*F=l=x!~nTHFvPw3gLe)7CFP(8_ZY+G`_Po~?OC+|Kcp*k)>! zJzVm0uy^RcDpUUtH+M@283O1c{cxFAPwc3_tE3wa=B)NgR*)d{JNs{I znWjUva5^z{xP-(DxS-ep-2a6pUmMs*s8m4(e%fWk59v7L9S|Rd8%k)y&44~QyyF!c zXpoh*k>xzt`p7V^AxVG6z<5d2(?0P2XQgPe?ic1AzSK|+hBQkvb6^wX3rzOL!r=x;A}`J;|gUU|Hb_xb%f zO|yC?-t8Z1Wb<4M)d34kStmxJwI+4x$isIyKJq_Bck&r^*o2vveSGpea^Sd zf$n=ien}6tsk8bqe>Wj-$`TXT7-Ehg=_JwV|6L2+g1RM?b`I#7`9ZN)kL{jgjrK<> zvXPM3TYL<%Q?)c^ofk8)VhuIGNfO@vfPIB?ic?4?2{u!zA!7T;L@##oO;$kjlzk$Z z{{gasi6{^5j6;HBiWBp|F6b=eMMl%w=XRjVR0TeQ52It@o1A*0*o1m?#gFUK~mG>(cwBGjVkB!ZP?T8l6#k};;ZIclTeT&j%9Ku&yq_(bU zf$otvp)Hf>B};a_rY~;#{ZVez@AU_IZ5~&jcT&MhO=|Bx)aU*@7j2ll5IJO0HQYdt zOLcbf&iQ15ROv@a89_xKlVJaTwO4G*D*jnj%R5aEV{Ol(1(-#~hk)k4{cscYL)KpL-7E_aM5X-mv!{VLudUSwP zKJItxp2R?$FN5_tuzp`JJ|M8aEpzGIqv=2V!!o79- z?e902WqTRjhql~{L#Zpr%M}GJa*xIeHsf^bE!oc$bOZ=xPLleHtQn_I3hnm^1o6Z5 zQcw&H=PDeLZKGiuICPQ7x<)EBc89&Q0@+>1h;f0(nky~B9YmK|+#Ym|o|$}GE*BLI zcwXYqGzUl{3z6Or^oGE5^SafiFZM*ZPIQ(wxdgHQ8PqKys2QN|@;AzDpIcFXxBV=f z92`VKjMmFDbwE!<^q8yhk&H}g!?nb|kWPujLn$gBkB}aJg0lP&8*%Cq8VNdON5;!a zDgM%WHL6}^7csxig48EL5~F%PD@JG`v89mUSv2&SNq&T|eP3Os@F{J>tBig(9&c#q z*drU+p^6iX`D2c5?u82Z^`-SRX2w1U0Yz@{to7;_cD1$m+>Y?)k^u#7&)dQ)2Wi$w zcsY&G3bi;yK%fFQSEU0PUyr!20$Iial)`65xsZbDxP-ZPyF?R6FX8l^-a(@UyL1^0Rtpkd9=ct3mJFzIK9!{+sS~ z8Z#)3^}ozWw}hru0E3@?0C@F8*(mTkWn(leUzh2Neq41E#fLKGo^&!;12i(YBsi>) z5E)jWkWhvir41p(9oZ5{eF)jaIxaBq9DQMEd%jiAF}YcUNl3DELQ>yMSr?=`?+l&Twe93xNUpiw7X!3WjR;dC^Sw-8r%!zSpnUkCD|u>YR1bV<;_M$dVg3#@3atW$2tRD_ z_PHHHzN7^sA@v4516k@CzBy@ZqIAJyxpb(IMLuth1El(|$3t17Mb|Q6FLQ~|eIFCj z9Lg;3cXc+P>)GAVN8u&tMkYV>Zw5)&i)?Eci5MX^kOTpI0ju&gwMkV~Q?Y#33RZLa z(dYf1EqWED0zuD?IGuk5>37qvUWm|>V2Aj|5vj1d3gg7w36LhmQ!uZ~-(iea)}wwH zv`HJGKhV)dNo_>ZR?GvXV|b2 z+LPUhasxSnC+-FwBb4_IjyLeQKPKK#d&6w+TVBpNT>omt{I%O{rf0&2G)j|(5{HXrNjMVs7vGX6i1)&4RjjbcrSMN>Wdj>)I4G;Pnrwp2- zdb%$jnSU-$G?{Nx{Hg8SJ!u}(y~OI&nZopGcAiMy8gVJFBiTByMwrpf^HbZH<-~m8 z`gxo%r&5e8`A$N0oqB!aa&Xmui|AHDCT{_Ky}xPb=s1RVUtLX-Pr{i9OH#0t9DAU1 zy>wtwYg{~i$p7i0bKziMRQB|MFy-P9rk)W{0 z3ZJBF9k;f_1YqtU05-$`zY7UO%#f(4H+|O#Ko5$*S|jfUxU3vYMOw6cocqr*+o5$A z&II#%;xX+ZX_2V}r1A2~6C7vRgjn};!lxSjnb}H-nUm0@TwzK2>5>p80~JY{h$6Y_ zjoB8&h$ey#7I2qBLYXZ-F_B88l(XUd0aq7YA2vP59dL80BZ5NTnS1#O#T$-^vjs!% zy7Zedy6v}Dvlzp_lWuQU=R_|rS44e&Y>Bq~V&7L$)Y*7@Z_4>|iXJaS-bPi1KdV13 z<@=I>xAEs9djEz;xhmglKg3*f%7uJ-R8qtkuWZ&qi;l29OI8#erAhqes*`s3t@hXM z^Mgf8LhE^Z-j>$_!$G=vM5p~z)Za8JPP=LezOVDoioY%^4PFRnAvQCQZ4LdW?&|-v z+_!|zg8|)IKV0>r;}B3b7&SnmEQE~(c@3vwgY^Jm*nME)Eu&BfNqhk;dkYAbnI(W| z+?Cn9-cp>GV2!dift1O8Drn&pEhu!74U%FLFCez!>%(V*L9jh=AwV`LAHX93fD!7E zuua{DM><2FsE#HrBWm{4FEffF7n}Q2-d|5Aae^IrHv@jjc}@9kF=k(>X(gt$ z9V7;6+#Zo6ymBxD^_qXnmO%v$X*9jXEgm|UZNA3xn_Xg{|M-?SJPwf1$R0~}S} zvy}H-%ozo{i$_n?$l(Pkz!u#O|LsO3eoJgLq*)@WrTFw`U|n2j_6)XAGVJs*?39A{OXrC zRE4}`H&i4HgEb|GC&+DeK;hHY2p&o7AbR;17xO-9Fsw{}13wm^mL z&7YDVJQ+5!+V`CJIJA-S--_n{D24vrY}cg&MlQa;fxl^p*J`3M1swCZ;|H&z*2tf) zkzJ+dz7dr4R1ly9F6_4q_ZP3{#g|luqX&HWH2eqQ{F0BKGLK0=?GtZ&;znKyU+Rd4 zt98)8wE!15b;koZ6d;0Qcij2=S7{M}e_|A;O9Uc!-!5j1-@7mzTRARz^@{(n?%aL! z`dyPHah1ZalfE`SI=}gpbq1vNnQis-Bv<@P)fl!;EMeK7iBmHxc*$B6G{4@-=O-#) za|sBC?D`4kD&XQ`Hyiomad4G}$R$0j{ISbnN5Uc@%opoNK!1IgSDu>xOIvP=jAQ#V zud+G|3SE~Y1by3=u71u|^b-}#b{D(g(ym^}w*?&PtC-pPqwPR+zw#xc0yquwV z(B`}BYUsV+BS}4uW#79kcuymon!^}-x6pW2dGjR?25rNPpV3`Gfr zdHUrIZ!Fy#0P})tXDWb?lYI0U1u8FOAYVC)6y{sQ!~7yOWWM}yViOTJA78|>uqh&l z>CV4;W!w3GLA%vZ-)q2N#Pm)`wj ztlFB&I$p}Wxg{J!FjiW8z`=md&0pl_Y5t{B`I+LXvd5feK_`vL+3-cK3>KCI+|h3_ z9=xO?X5=h|gHZh}0lZ*-vIqt7>J;xPp2>V4Uiu@;*CwyH6pYmQ_nZk9si zbxbu3&yL?kIy?kWgBZ1S-vDius+op-)c;M=W~`3CpnP zkgY%svSe)nsRYnKsYwKgTZ0v8LC+Q>e-N~d2q-ou7x7-!9k?fn@@%(>nH2f=e{BCV zi*eBT7{0p~G_?X@Ag*hqyNZ!t{k3u@jtz^;hy4z=W+B7xikIe6!(L;vboc43`x%vb zrk+*g*Om?GalbcCG_T!Jw)xgk?fRS+YL3$*`A9vjvd$X_BWcO zK8ro%i|-dY(-v;!g=Z!1MKCkj~a`B1j-M+mcp_U&~(u49Ulej`r$-ElMF zTZ9R1eAhOE_K4bGZqk;Gj^`E=U#bG>UG|$0;m5jA*SY4|=XIRpI9iU@be_m7O-16D zE&TB$de3CON@NRd6bLId8zk>OUbb8b=6G~J=#`Y`g2!@@+sCv)N@fc$t)8#Wq%Qwl z{QQ>?_jh?qXrBhq)APfXInP`I^#s5Kd>J`l_#l88hyZsikzjHC)WR7d>pZri8iDw3 zbcit{CBR}`m9LQyDWN{xa%g@Q?#}u9Q3|YxWo@~UB@{MXCF+<&E!&T+@2e!~9~hIbC?rc|X^h!)D_W4R7AnP;$=??u%2j%Vi$|kPOBs}`=gvh zEEhK-i&MM*IE>-Yi8OADWeGjL9@C=!I*FNX7ya7FId4|E^Sun~fb*W~doEph49TPK zd(DCBX1KUPELa>2J6+k{rH|IVqRb>;T)dSas$Sq^mNwn$s&a}c8F)>AKbH~~RF@Sq zY565o?>)r}dUU^gi z94yAfZKX{GziCz;b8b>-DJr$mOCZX1NLDOyw!k;*Z!>m@a)ZT2RX>b_(h*4bqBo#a zLN!X)a=|R@(>apjFAUvDMis)JM_VOzC3mP(@G6-f=V|V)#|b`sty!gpp!lIfUCV(M)n~}28+%}_Jmg0M>$TrfRNi|!BkK-kV@J3bM8(k%$WPz0Z1e= zKiEdpr2g58PF~UV&`MMC(&5vq_E+ui+s_*+#-GgVUe3I6=iY6%bz&lEGi`XgkYd*S z3Xo@5Py-lDZnbfTzHVem|xz|dl2t{lLw{g5DWv@Ua z5uKMNEB;^U=Rd@BtD!x6K!3>(0JbYb1hg8hn4juX<3@mevpX6+I_M>Gj!_*rk5bAS zEi|+nKeJZMrl{RX$vg^5A6^Xou$!Dk{Gllc6^Z^7MD0&<7lwtB@GS40dl2Sy?r~$- zYT?((fF;;PV2jt~E>eDxMzgH)eJEd?#0MqDA5sU6l0+_3cv5Dag@uHxgfh-kb2G#f z;iRbS3S+}80m!7(0wst9|>;wRT^BS@URrQ!x!}p%7v_JKs!@OO=U{;2C zPJPNTzdUW)4xQ=3I56ToM1w8#DFw%J-gda(F#5d8EISU>;aD;A(h=R3Zn+HwcX9#(Bvwq-XxE) zNb`h&pQ9s=ST0?-ux`+NlXVmEct^t9p#s?HQ&-L8OKx-mSj^zSOrW6EoR*64xt9O; zU?cj{BtBex#@0kTA}t()yg~bf8S$<=uaqUlrSE)|=i+6Pu93qrlCt_!!qO!vsUYUj z$)r$Ef?rPzz8oNtDk!a*efe=+119nv$$@J@r86RaHg6E;AG;Y(h0TROj;2=bm8rA^ zX1CHx9L>tf%b2o)t*lP_iFC*HwzpT@ef~b+@a-Bii{W0s&}h$mUs7(%IT@}c>QZOT zM4>t=Nh1B0?0avsm&es3eCxM?cM}mF7Og7ZLx&g&ZC>C=9=(3yxMEk%Y{U3>GcUsf z5|m1{{P4@AfUO-^XMSG%sOk97*!;`(kn7)ZxvSeKcOD@ALF#Xh^&V&lC;(BTNgzam=t2{-fOWs1 ztqMa}9G|UZRsC-n?>Mvvhz#~FLE69yYXiP7j}`%}y0fY5|F38`hriEL9Iu_pIw zJ@D=0+OG9yf2?3GlG)3*Nq9*=kYUppXQup}JBBf1D->KL@h7uji#uSedCglL+dP#} z(Xf(>m1_OgF(45ZrE97TBUGfkw2UvM4weXr$ciCrY#e6oV$VF5 z8G?}0n!G|^miX~+k)Vo?J7Ujj9*=&G;TKfVO*q`6R|^>KW0(CG4;|fVsALb&Pq>Y8 zCn1R62oyjMzWYuF4kke2lJTvj53kpdB#mT;ArMQ;oJ=`{gz}x(fhZ$|J8VYoKmr)62OC`JfK{rWSH8V<1Ka#_W3Hw*OMzKNs15~&@foTj zfmnD`PIo#q^#cU~cm4~N*zkmHDfRCjbEFXU*UXEajijWhQQ;$JQQ)%Av(9m*L-sP zZIHyCyH&a~-*_jeC;q>gANfyt$U+G)_?w6R4rAGfxivE2@{k#U7$$-=DRlC4Svt%= zZsW;VNd@7H(FWmbu|a^}FAMI6{Mgd-b(Sfp+-3D=!n2TDFockHJS-rneEiUXOH9Ye z7e()&40eHgLQA|#%6I$ph8x`p-3h7#CVBW*?*kM8WNK)KC!(zl90k9Po~3VTXj=?r z6|&g%7L_ntse=a1k!KpzSVtD1#9Atcqq8!A(T2T=c^z_tyOt;93Vl};D~TXR35)~a!*w@UmndC_L6OE<3H zH@CZ@liH7IYgsXMPp(^P8iD5xogG-VnNtr&9*+plbNc-UQc#H_RH^Q$9gvL z>?3v?AQH3j5)>DCwsI<(PDAvNES=EvK#p)F!#AeHz9!r?{VGS+cz30V+y5yS-D)U1 z;&xvSm*;O2oD9c^oH_Q#TNnaj>Ij=~V>UKoh!>a90ANK=%oBW5C@ql9IN7WZl%-@G zcU1ZWT1YIO=umDc2?Yf`H^Sb+`%WHQlu`mE>5-jOZXrTGdzI1tR=CX6`2l8D$Bv$) z2toy*dM3z#o{6M#wDYLb^rCkBkZH8(Y1FE_z4>JkTXOgD<;o`lq~DSAG%CU14n*#p zj{QZgTxpoU#je| z{G=CaeHj#4-aF3Y=(Woc(Q`)L!iQ`#EYIE)By8If$uzYjs7*FkH-A~?j$r;-Z$8JC zAA1q%Z05^2c4Z>S7TaA(OL{-i^rh-SIN74QN{ETzqXf;Mas!k}^nv?{H+7U`-D`0N z+SsJ_Nwiy7k{1(>SS20P{=`y>sdMab8m)1@n!q--ox~&Y15M`BL2bRFoA>wl5h``p zs?!E7`A^q4-2DDD5}G_?3*PuAHvcB0TSAe;fPUVsQ}}6h$2breL7Ej($-`s;N?Zio z0e=CVlo?7|{RsvG9ALQFIB0qLGPGsROBxZJy)+SqV=|gzBUXZPlYGd1qak2X`#dE$(hwbofEkR+7(NB}H z8m7}Mp%7O`<>$(|tys!?CZa@YTqKlPg1gQm=D;jyJff)I7WVYl{DHs)UGvkLeyun9 z*GE1pKJe=|DW=Qj%Qu+a^-=TWl{&VD!J&JT#a?oBUUTq*Iz{@#k)hJ%?5G#t9|`Q9 zp3D;~?;F7CHy+Ef!irCxMj^e7^ICa!cl@?zBNBg?&MzQ=(Tse2>thO`pQ=yBzul{z zW31qk;Agjh+XXt+HsnMhJdyiAk49WZxv*S9k7l~i5EZpnqP|b7-SUh~z`v?sZwb{Q z0sW`9PT^;e7|l~c>_7meoQ;QTM*2Ar#{x@)THQb(j9)dNCq^=TN@*lpq(ruR7u9ucr4mHiC^64NXXjo~NEBqB-(prU})+MmtHF;o<^maYip@|6<2r*CANoGI_rcAWMG{a^Mpj;TsKeuse%Q(M*U z25D9nKkrSV{Nbj{1X8rDe)2DD!e1HP5_K!oc#R@$y;zrbdR&~rOOLM42{D$>NQ8iwU!W8S}wgZ z=nq?CbDWbW=5i}KfKesg8;8>F3xu56Kye~4}OW?1DXZFmzX`QK0 zk!-X#;Cb~O@DjBoAfM5fbg5~}i!U3%A$;famFZo_e7-uA1=rt4Q-%#+YmFH9m`%J> z?9Y>ipl6pGRQ#LAoQosz|AaPIy45F@88hoa_~vLPoh!}u&Fp?ONmhJIB>WUQ`OlPY z3H^OL!r!rNfcY;WQhLkdzb{gtA_$TGko^Hae1x77u8r!Y$6^nRP?F_kgeudTH{`Tg z6%fD|wzxa`EQTlcEtnZvEuK_n$`DyS-@+zg`Dp4w)kZ<*Cr0tBA(*$HMqhhI&ozps zw_7v!9Gk@%#o(7Sm_cPuxlN7?YsZ+&a-wca_` z-uwBJC;7#}d5m$7`x@``IlbqW1LbEVYk$oG8zX)a6q`KQHwS7VyRKGHJIkOA>GXiTG+#6>poN$ zk&hV9ysQ*NtqA3U8y3UG(x8TcFqFoS4+;g~1_LBjfu1OUw=w+D;VX-Qy$`m}`@j9s zpmK;AXAmyU!&~ykdCCl}VyHZdV_ULrttt~@3~&;!xQjGR@+6{Et3(jQ zJ-nAcu&5w#pJ<|jkOo5Y$9hEMZWDhBBUK)h2}GnnE=w%X&lhiFsu#lu`2YpU?Ke@*ZJ4)bsKW$fa+CP%0w)Oa! zshc`hSoW!XE-!KK!US4YeJ;IRQvwgbxNQo|kl8hY zX(a(AqKRq9@z|(mGi@qj9JaADkigfJ`&259e95z@3vI3+=g#yQ{WA_8yJGpeA5A9U139ZfN>UTHSF7Nk9D|?RnVox%YLF z)|-YKB6kF7mypPEWBGgEUCpzF?(sJ}cbVnA(av&tiFI9awBXzK^r)&V{zX$2H3Nof zyt{syF3HlW|HeF~+V}V4G%JA=#V>Y8!iq=yQyn|)eFt#e?iC+VpZ@g+>~3#){4nm< zFgy(`4NaQm3yj`G%Jd2Ac9mhnd1+qqep8*}N7uTgkA6357rXta8!fN|uEpTfIo;;e zN%zL+nEs_OcS)#b4Pd8=1E}sdMS-XQ2R0!f3M|e8@0em~$_^sNCu%!2r!)hQE@E#j zr)L+$>l0~Gd}f7g^j%HXX9rTLj7b5DANgjk5HPBy()Nw_#b|Q64Ps|z$NuD|v;N$y z5~9%Upg?7yhWptv>VxkBYjQ`;6Io~j)m8ELs5#Z~LLgRps9smzr#MO_)2k~1bo-3- z0;bY%!(nP_z2^2#mjMA0tP2}E&b5>0mmsLinw)4#=WK{k=K+ti0HkcHu2NVJ$MAM7 z_~lCK2LH)^F^ztWBwZ7)=A!S20wQJK+v;^_AJkDx*FuSFr<>Lb$)&Ubu)Q&aymXozAD=LcK{7?O zFivKS6ibCJzg}@|>@r*f+ugF|J$k@Ymi3c6;m^N!bC-l3UYtX8mtO8uj1pZR@`}ic zKYScL0wKi~&w4(@je)7)N3t04Cb_tqpI1juAXI*Z5Ce?zm^Y>alBmTQqm)XF;6mMS zBawxW5Gp-11@;xF$)0q7|Iju6fmJS3swP)&@P(lZk-P$vDh~l>BD=hpAtv~6wwY}Z zUXfzqG>9l|7LL1_#I1rf-@79Wf^`?ay{{Cnz~5)+R1%&}zpo-)xhKk7E$XiIYEnJY zKpkDc+h|`;xfi_FVu}7ZEF27DuBf)OAv!b_I7}I!&o=VM(~h(kbVL9e@=o?&sevqp zEDY9y=(7B}!wd4JzeWFCYo2++!ZI!x`uolLmuFjF#q;0)NPN40^FvUMdAET{YfGL4 z+&1&|-JM4~XSA{#o7sCr+3l5w^DZc!WVvzvNjIqfc>qVXQ2jz^A>YQ)9mkbS7HGq^ z)M&LCPNu12_p@xW_rS8VAlo0akhOtfnrm%N+J!PCd@My7!(WUM(@ZSO#lpOMCMTzH zVJ81hQTWF>lz%b1OfupO@Vd!E-cN=7DwUjAAVwl5g{mD4W(V^T(dOnxBsbvE#CM}N zCGhhd&rujG1_S z88h7>t#P7ZBR#rDR$1Yg72PAJg=DHRxBF-~%U~sQijr_nMi^scOg-)1u5r;w{CRrS z!i>(eymRWLREXS309QzAbPw^u%e{0nj$^&kZDx@gHbh@p%uOuZLJ#5TJ*w*yttz#& z3S!csG>M+KKXr3Kqz!p^W|A{$tuww|%W56ZGH?*SRb+qhzx*cZ9r~*G*Yee$tuyV( z(&7Ef-9eU@c`66oK_12O>ZKNh%N+w8;V`0Rzw=lsqJz%rBpT#stI#5z}<0eKho0T56a3 zsyR~pK$Z5o7(na@=sZlT$OCGjD{=miNPXM8Lr%r>7yfz3mi{BMLgrN$iQ~QASg4Z%-tZ4+N0W=OP#*7NM}x-c}LRh?WZwA^)y;VC4LxJ4$OGHQ6(K< zNZp)U#2K}U2~XwZ=HXfqjH>ozM+ouCYQ-t}K$sH;uVRB}z-QsiNHRGawr67<|7EG?-*PU7s9td2RJLw%zN_gQ0JzYMt79f8Jz=|N4IZ*(&_c zjY5TGx;ux?S*DAbTaA$)bgKO;stbg0Dqd&4S!rGgpu*fv;YR^xl5ZyJtZF`wIT_L| z{Sb6c8|9*!=4v^VQ6Hz2xpSD@>s;pAkXZlcVUzkKagRaStqTqP)@!TmAri-D3S7x3 z`g7U`jQ28rU%OQ|t?-a9y_)6jrbbq3k^IMDxqp8M{lm!}lmHyo|8jD1SDi(+SBasi z^mvSaES5|a1c3$-MizASWS@GgmeveC*jcXS9fu0%kiAL*LJU`anY4XvEl2g2>iNZq)8jdu(Mf!~F zkybB+wbNGCZ;d?7Pd+0oKn&F(OU0pRcIqGdgW_kVb8($KCc2eW=W(_(SzN0wKnBx^ zjEV_THUWQdsV3yOe#TNHBausf)?7Mae)fScy>Z<^RQYt%0-DzXHc49yS!G$bsE^+q zr&p`%&U!;#v(asmDZDzINk9G*+7weAs#3eA_8(nK@}};C7f> zpX2GHC)BZFt;s*f>ZSygj_Mz42Y986N}NL%q+XtW7P{%-ti~0@mU}dZabRR?=+!pd zuN7E|*W_FdeOBz|*Ue-cSsPmC;5l_HUx@%Rm814sgi0B-9cm*DAI(Uz{345ibTnM@3FVV4o@tRmKwe z_#o0%Z{9&4J4*%_ONrfHXp#9UNrU4qqvt^-Q*hIyPJyi;+Oc$=KyNcqWLTQ^A~@ew7&rl{XcvWE|>oR93;NM(v6GJ#^etvDe6< z3l2;CDXL?S^ec;F4wlc7aX7h8uVi6AHTVL)vJ0n7T~)JeI?6?!msrX=rmWq@7JG;E z`OOouWo2DGm7VQ1Lqa?>=z7Vks$Y~b3ZHWB8LFw!SnDqe2n-0m4y&#+HVOZBE$p_$ z??3X-uMYos7TD$dde8Fkz3&5|2B-cRi8nQ;c(c^knKL>*hUO);I1qK{xawF#@`ERWA~|_j4wdcI5S@w653H zrp2y7%JIR?$rMvv;z2s`@~2(~E(3ipMk&3=r~EUUZGBA%pz#tL(TMkDUV>7B!&P*2 zHl-Zo3{HD-S~S*11aOM_c)udk;qz#oGzs#kg!*8etd`KqmSqz5j@*|8ZQDOP%^ir_ z-4%;>e>y!R<)CgZ!D2Hj)3`@Vwy9c54@#s=&DQ(Hvib?d(3@nM5JRbqN1pNyx9`(Y zc2|krjKcKKsHaBOERH!!t+G^o{G`O*>6_m9&3a#=Gv?REx9~@Qn4F(XTYfn2Uwl=! z)M(P~(ft1IeK#AM&I*Z^yx&jPV$03FRjam}yKer;>ZRv8J!U36zg~CDcnxgy%Kau) zuXwh=y;<$B4s%}l?)(n@k)}##p$@~3VkAsCa)SI@er5}8?`wbrc&VSFAvEHry=lV8 z`ic+lbfu$bSGu?+yJJjEnNZf_QgKBxZ=-TP`M=MyUJ{y21la3FoLwA4QA&%E8PEvy zOdkr$s!Ymi528T^NTkr((u(`fDmO1?=Af8x)H)#IzE`B%R77c2v!(%wIIJw;3``cY zt@4Hcz^}r<8J=#fN?|OdT?>&#acq^Te%<~QC6CpFh9v3MaZzx_@=k->I=ag+$wm23PjofH*T_+*Daf-E;vVwOp4zYLXS2gFi zwcv1H%^6j&9Q-yUP_bU$D75ma`5I#TB)NWRSl~tbt9PQyt;@`3-5Y;e zUwpp);7ru?y|BGk{jbzX^OkOFI}1TNdY9+K-GO%!&BHhF{atM5?QRTz2IitqL<0+# zUB-lljM@IQ17zGLt$|T!TJb7>eY0s9d+2pYbbFS@)%!19K<~>0Vm3UDE9MwO)>yPd z^1nH;1?1)tGLMeQ!*b+vr>98BHOT^9jVc}q`HGU`$I}1K!TpsVy0oD#+P^}%iwTYS z^nhEN6-p`C`G`O&TJ10T0-Ko-+Xb3SNTV$NwS#vT6g#p)DnN|~Ni6>0}Y>h?+0=FAvEdjaC+y?D? zi_^P2K)ns$;!F(2?B(NEyo`Qyvq33$Mt8p{Y5=?$w^|f%i$NJGDM{nB)g;JcXo;4# zhC)QHr7q_ccX2bPUC%TL4vmK;BDWnM5p0R>Kf*BcB1gd=*_94uqU#3ubS48}3&@QC z72`z9^_)}t)aam?3Oc=dzt_#|SGc>IMddWga4ifsdqck8e*ru)*VHx>a-09{dY?h| zguUhZ*y^%pS*)qrA%x|PU;~0QM!sRz)2MOlg!LLoG=n1003R$7Lw>6=a&5y z)5f_+Q7Z59GO!YY)by;RsniGMI!cr7$lAW=LagV3{^TrM^d>j#y9M)k-$LKjfwPVOu}dQ?F4f zd}x!RC>qRFFPXvXt#*W*=-#*=f)7j;4&=T?$9$}F)k<&KM&QBg7poPGyr$)jVq`k; zl$}WzfBeQWq@q6f)YrGQ89HTr_w?c8cR#mV{?B?Omm>fe){DaW3t3L83yu;YC4NK= zA zD?`1FuXhgIz2_G;`eXLI`4c*|k7jIe7AtJA%FJ=)%ZHKVDS)zKTnVvo88sKFH*uDC zQ^k}Jq-Q1TVM45iX4ipYR`dK1%4lwuoPgPb?c@y7@eNB_KT;rZl)4CMxv6wXx>!&# z)G3B|C_Z7vHfReRthV+=KnZjLA;!E8;vyDP$Y_4%AQf92iI{~jy%OdieJ>TOT0kc8 z+1LQl%iN!FE92+B@PpUq{XZNV-u1qc9~P?lJ+S|!x8AxlG+TJQH(DoIH-WG1tm zw>Eh__mrN(_F4MjZ>8EL8`pO~euz33$w7_UzTGrksc~!V2uoF_HuVMgTz}ik?8zD9 zsEu5e%|>xZYPO~SO+Nh6%e@o;U@|TVwZyTv(np!_Xm&APi2^)CTh9*i<3^*3IZX07 zn4Ke-#xvabc#fTopzfMRuRPmgAzk%hu7$`-K{OmAP0EF~?OQRs_QUmt?}sbXc~6q^ zkFCdU+CKh{FhOT7w zWj^zo$a4cYI?Qv9sF8!WxlIOnQsA+0In(J+<*F)z8+xM!#8$YMZp;jMpNFW^Zfd76>7^7FiqM7U%j!uoZ(_=mcx}*tkuZW&!}t1 zYYzbuUA>Q$zqXGnv2D6Xcr`q35A029qrcJ+E%6}VEuBffJ--{X`zU5fu_C@-yL7s! z1A>o3o*})FmVA01k9a1ndRSk|!y7W4MSh968i2wD7CA_{D4bzC>$9u51&F2QlK*xf zT@orJ1DHc`F!dLOaU_rGqVk+h%I{f|M#W-FcyKCoWY^$c)Ca^9>macJm;`9(qUS@x z#3Z0pvX^F8&;#=fqc_!=}ITS9e2b^DcE`zMwSp|kg`7#9j>k< zVcb-`AF=y_pakkSiapQMzK$SOr@YPC2sfPPQu{7$W}15oP~> z@zsBQxg^xV^OtaXDf*y=Ho6fUCswI2@FPgHE({H2#j4OLqX=c#>(Y@bmBeDsyw0o% zD1`=|z`^gbvQI6+SLPN`VB%J=pODcK$X!gxy)#?Rt7$uFCO%-#X3ANCe}I$bSdV8{ zM5%hmTF*R7#F;Fn298%_RU^b`7i(nUOf6wbew?ENF4fZbhjR}~iupD@;9X|w!Kq0)3wHU=V~!8r`*%WES3(_;e9e^zrGv=>{*4sV+*tyYv}&<>-$o6 zK;Fmqlan_S?!kn4-n~y`QZXjmt9-pe)0(a0rh>@4CY?K6H_eu5k3-GwUH`-^TE;FT| zHhwa-75gOK7waupU7-FV&q4_t^K|RQmf7N)e=nT!q5#$xo#2Sqg?QL3_I)tA2;*4n z_e-B0h$?|~!~L03@_12_l5ldrM+oXsb;rs`x9Njtu04q{U9Ow1RaATAo1l7A3x#K| zKZqZff{RI$-g5?7o`th-Z~*Rb$O34s^o@MtAQ`MKK*N+rV1ke@Hy!!#X>+Z?#{v1_ zU8R>QKnhu^lk1|RhkL2Ay0EGPLY$(bsqQcxFYg?!A)}J*{B`jCtVZs5NAr#_(lw{L z2xi=#*yzVM2)>_lva%6-3g`qc-bmfZE_Nv^XZiVn=S3$0VQ)RV>u*mza>UKls?5kB zKU&S7tZlt-ZchMHHuf!p+DG5LfRERtajvfq#SSZ#4A0jg8bGfedtvvI{M;fv^NKWY zkID)83qGQqSU1Ldjgm*m^HXXX?bevHAGE#WFAUE<{iIti$E-84c|BewqGu1oqtoGc znf8LNo>~Q;O9wIkktC#B;>AlFC;tNfY23ksj=bbSDc6kEr^jrh)`PxTIyVe?o%H?5 z;|-^1rD1Kx6-&x|nYZ}_4n&yg>SNgZ-r_x)d7JNw8MKW$-xBceB#srqBGZg?EC;ik zhpfL)#iVg&sQg&aRr3_E?9>Q+d-GgD>&J&H<6Ysu>&|~{lPcDNRM^c z|E%6f<87%}mlIL;vu6Vx4&Ui#A1T0u=+Q4;Wm6fxSLIuan$~$*-(I!0cW5F~*|pQr zn8%wW4?BhZ)G~jlnQg2HCJn!1F;%bEFx(AZ)TXEC=iyP4;ArKy5!GnW|9A9OHTT~^%bWUNwUKIkl zUw42DakUjr37RiN69GUCEUewPx>W&X?1OT!}j>W&*l<`FOu=si$>{?P5&y3igp3J@5tmN|wqu(C{ZRm9(vi z+#tJ(0L0f>LI!omNsP9I3+W#;N~QAHG~7B!ezUDJ$>d2rj>Q`0Y#VN;-p6Bc^G*v{ z#Uf$jTa;XNIFN6uRs9vdztV;yFL4BMe5-wn0bw>p^h#ZztV0%=kxcnD;iN&+Dj^Z$ z^@yy2lAqpmJ&JI{igNdULSU&lJcxB>G^f;q6YNRq6V>Qw(V6#I@N4*hqgRsn?vXt{ zMM73uNg*-QDSy@Y)7S_n39jHJ!}9IClO}b@pSQ2XN&L|lv$GvZUyJ7mxWP?8E@Ux67)&*P zI>_F$v=KN(Ad4y)1tD4%M5ynZ+#jbNwNf5V0lmv+Wrn#Rr4`AUu1^}~@}`!FlFE>%SDP9bV`Pm4j{)pj9_Rp}SEHU$-dlKe#gtJ7xYL{6@y>(PtuF z;)X8aR&E>e#p0@!##a-ekekfIC1kG3lj1#=+A7znxLKXz-uPdmqsw8i9XD#~6*y5$ zC(iB`1mroDs%HrI{&)4~l2AbmzybP~E*FCqS_xrgrE^un0O$lwK%enw0rv7?pM@cB6WjPdBv%&zu~>JSfM9Y8~GB-avBUUREm)IEYUu7jHEJ17RV z$Vu~(#%M%UO^=8Mmc~;ThfB=nucJWXDca}+gF!@eRd>}Gro5?Qhy$EZ{s1Cn9XD2P z3TxgPzdCWJc?IE76UDhTFM?6q>yh ziySvw5+jqljd|p$+Yiz{qN+5ja-O#;UgWBoTpLb1roRccfBE8dEG9DTb}x$|c&6IR z)y@6dos-`VuTF*@3%fViJ*#^{zG`!9ar`=C-gs_m2jw#&;>LEjs(x{CH-L2tlgprSXzk%>anfj@Sf6HA*pua=~_+GzlirP2?<>k z1@l~#`h300DoYxL#1qZJc!(pg0oHCVzm(}VWy#6`n0o-QQ6z^P-aA>YjPw`tT)|v) zL>K&f;S^*cFs#4x1{Vu<7aZv)aV9M zE(UpahFPH?Q#(x-j>OU+aDr-YwW2sMfZtPrPmcpy(|LUBq%PnxZ%sQ)Ldjfl5OJLT(_t10nTf z_W)EWj(*CcvZFW@!ZiaY`o1_4mnb|IG;JA^cul#^lFI~iJM8vGFWdgsPmf4!aHR`U zHS-Zj<(Z9(|3rFn)kH>(ASUFkpP4|TlfXocXF##RnpKR?QxwC`U`J+>h8OXRHa>c4 zHw91U-D;t~2enBvKR7*(Zb z?s%qabby+FD=Pf}GHirwV@jGnO~0`b;GOb0UC!S5>)cMjkFagU(pX`;WYfjH(9E;S z(VuhLnLH?=^#^U?JWzS%o#vYPvF-Qj_{_|M#ngti>`>oDx!WdcqIX5qibI?vUwGXR zPj2#%W{ItPE@u*`m*XUv&EKVP42o6p{2r!(mC_0oo#ivji* z#aM_7T@D=wuvFASLxqF}HH<^57@0T$q!1t-6I?Dkl3Er%Lxv?-!yk)#Rz!SedI^xL zj$nZgkzA~Z;weo_Xx(4-&iZ()u%t$1$ym8WM3lQT>DzX+*y&@Y9@Up#{n56((kG^ zW=_L%w`}~a*BwS{(yaM_v06vr^6GC${u*Ng%SMCSX-{p;zs2|`oE4AlDU^?erA4fi zXImz%Wb`)L6tAw6o?R1IYX%zol4ZL(Y4PS{soNrMpR2%#L+tnSW3BHZY%HnUqlH*s zKJ1&fi2f3mr63-YpJhJ7@-X*!%C0gG+{s6s$bH6Nz<0m-wtCl({-!l2dh zGpg#kZA1`KAs7u!uO^OIl619dNSp1y%@U3 zpJX)Diry7nViBpm?xD(OjpgJ@KuEYs=q&9eQ~>EH#Jn~AM1H8z$4m8r!#ve4|7(Y3rS6WUy`bS{)RrvEJ_nuEO5Lyz~Ul#ni}iGROWN@ zrTB+qp=P$PSG1qG8hsBHs<_FFt}$3ph+Wt!lZ*=8C&xW=abaRwGV208VCt~Ja((m=8Nz=e7%{6_q&*S;7C~1< zeCX>man)HAEVnH%JO7PLxx(OQW!Cu4Bt1|t)7+ik4@(%UaY^#Qs0=!DF>l&=FgKKfkdm$**@A_=^wsm9{Ha?*m_(FjZ?UFO5V5n zZcfxy|0wl$r}0o8KhC}-6X`TP$AwqaRaYmPfrHL)@vOqE`L19mw1GO_=U8qxz3f9v-yZ3vFIZ14j7m3;s( z6UFoH!>KUd93gCqgCGN70tjAgS^Xm+G3QfrH1;aIAt63?$|xs;JvI8R9KHyoDG4sY z7c%@Ty^;UsZ9zM5(($<;Z`<5B#z z1A~sy@_NYch7=+XZu7oWRY;EG(Xz~es%0_S8z@lmZt(2zq@HW=168eSD;0s(VyTnQ6k81)UsbPbkAxr4AAM`?H*$w zbhI-LG@Rh5(V_i3?-dd){BA*ZYLi8FtljHb>znuW)2$E!Uu+l*P?;IIv2+Rq#>Hsj1ZWj*)Sk5#y%T5|m7$WRt8we8(0WG`hZ+st zQt*j?R3R1OW}Sahey;n&rtVzSb-wbcXXZd%sQ#w3WvkEDkUM5s{yl$<-L;Pub?w=T z(b*h3rUA?}e?rX`%pYERzsxnss4)AY)DU{N6PunRI?tae6Sr4+aaTWo97%0A8syM4 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 literal 0 HcmV?d00001 diff --git a/Signal/Default-568h@2x.png b/Signal/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0891b7aabfcf3422423b109c8beed2bab838c607 GIT binary patch literal 18594 zcmeI4X;f257Jx&9fS`ixvS;&$x8J@slQFSel)6zJN=?13FB7H(lQjRkSy8x_-S~tvu2gzn1oS+dLcF#eqtq$ z%tf9TTvX?`)R@}3uBI;jzS-=ZR-Td&MHaS&;!0?Ni*#$#`n*~CcQK)Q9vAQ~TUpnI!j)a2biYK^R)M~A5wUDZhx?ULMX z3x1P&qt=trOY6P2U67L=m=U?F|5#Uj(eCueNTZaHs_ceWiHeET+j+tp3Jt9g(ekqP z2WOvfR{qV+9r+o4J5?qK>7;;^+I7tGv-i)es$X_D=EoKF+S?zsyj^oRFElP}c}JT< zd8SUs-?O?}2YD#ngKbnHgzHBcboxK_2r9l(?eNCl-pEzkJm}fY?WC*jnS?VBE4EpY zO$fEejz6fU;W2Kl>JeQBZBl-%Irg`obSlg*@4QB;Dd1H7^Oi5wvt4d{RZ!8Og?^aE z)k0$1g+V3fd(gdQ3d&q2q-FL*uy#}|bc^=VhFsl0jBgUGJ+-s3U8MK9A!YJJMxpci z5hJ%|{DwV48fZn0{n5l$N_KcSb#NKE4plB`9I6Zt=Z!~-zw0{9tg$L&Ju1F0X)Cy8 zKF;(&lJ>x)Jw(=;p~sF(Sd9VWGwFE2rnyS9!f^DZ8+aCLq zQ};>lcJ1GDLqjm6Hd>|Eabno@P`~Bn(~6^aD_#yoEH(a?Nm1S<;S+hSxI5d16^<1lEM3NPFi zkqPrpL)+ zgnseFikg`gJVBha1&7C4;O6>h=dt~`ND+;Zd?W(4v2JIb7Pt>Td42%M-Ju-XAH#Pns762L}K3 zDhvsRqN0Ni(1UrishD2YvV?4*h2iFj$+&N||Fn$4n|^NSU+o?~jq`0jVQt8T9l{7b zXiwwODFh2V!Q6sqP9S>WH$oOf$N~=d0-bqTlD61!=`&0eAP-F>XN?*|gtOXX{ zQVTWyYo4ZK0GAw!GHf|pz9`D;-bbb*5LBX*{bnz|+)$@&P9|ORM2o?95{;ejvo&r- zq8cBhTN6nn)7~W>54U)%-F_-b?YKdfk5I8MHcuzBD5)!;yv#Z&R&^y=@=>VTIMy#r zX&U<=BsPkdqcMe<_}2+>H%XKyrr5ZR8_KVe>ZqYN z^=^~TFD};;rHJ$U;{~w^hYojl4hRI@SH$^K{YEo=sg)WY87r!*7blQK&qnpDo0`Vn zkl)9u9g=mCh&ZCJS(L4yN3k0kQ zuvg$h2KEEk51T+O0JQ+r0`R>g{jvqM0Mr6d3qUOZwE!?PI7HY@CE|dr sfw?Q;rAv?G4&^^8-z_>&sWXMxvD*gPOU4CBe-*@OtE+wfmVJNyHv)PfH~;_u literal 0 HcmV?d00001 diff --git a/Signal/Default.png b/Signal/Default.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8ca6f693f96d511e9113c0eb59eec552354e42 GIT binary patch literal 6540 zcmeAS@N?(olHy`uVBq!ia0y~yU~~ZD2OMlbkt;o0To@QwR5G2N13aCb6#|O#(=u~X z85k@CTSM>X-wqM6>&y>YB4)1;;ojbLbbV-W^iFB1wa3^zCog^LCAReC4K0-?R_2{6 zrP*)4+_uWUy3w5N52M3PW_}MFMP9a~>YLvVZ1D_k*IMQ2QT^fwzoOb(*3gH$%aYWC zkHmcab=va2<#X%jakpJ;<1@F;k__#bwtC&%^D0v(FBh9K&$sK+<}2RJS609D)17$w ztdQP8(eLM8Ka}m_IQ@3wyMKP)l=oM4-?`YS_*P?4V_ORLPxsj&7Ju#kH;>6^Kp?T7~ zl+q?{UOOqV==?+d{=)5s|M~T1mwtH@+Z^$G&eEO9JNP^AX@3jZ*J*!!>lc|1-W%fA z@AOQpXZ_Lt>rxFXrGp*zLPiW@uo_c7C{As>j zWeX)wi+LTp_)@KYZCX{j;H?|1yXT4DnlS(Fr8gyP5|uaX_gLvaW0ScZdnG7o+u{T6 zFI-%d{ls*WuCDa5UJ@|RXv&ejZe}*BMkiWY51&pnRPw(hlykSzvj6e%mYz-GdvzBD zF10?szF_~!jS=?2HyQuPCvARXAe}C}WP|yQ*>5~~=*Nxq8+HHW1~FMDRCP^TcacKuk$ z(U#REVv)D!PhJ*ecH-ELFUrfyV&*)Z)>UCOuS?yd^L@Afk>ihynYPc{^CRwu+JHX+#$@YsC4c|l0tGigsn@jy) zXD($Ouk>H+V(Mr6NQT0S9BFM~V6nkj;1OBOz`zY;a|<&v%$g$sEJPk;hD4M^`1)8S z=jZArrsOB3>Q&?x097+E*i={nnYpPYi3%0DIeEoa6}C!X6;?ntNLXJ<0j#7X+g2&U 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 literal 0 HcmV?d00001 diff --git a/Signal/Fonts/HelveticaNeueLTStd-Bd.otf b/Signal/Fonts/HelveticaNeueLTStd-Bd.otf new file mode 100644 index 0000000000000000000000000000000000000000..de6e718344ea258f0ec4670798fa1f2ccc40a973 GIT binary patch literal 20816 zcmcJ%cU%<7(>Oe{yE6;3x+>!!?mD|GASOgW1Q8P|CKORX!9WHDNg@Jb!VCx)@60)i zS;3rhz?^fu^VIXyQ);l+fodw1{id*0{w{PFI5db_8)tE;=Zrn{;-^bHQ~OKGTF zN=dnP>DJ9uHOX&0MOm9Es@*T$1N#OQwqMnfqI$igC^oTsP)KKrrtsJ6F8*aiO2W3h+IbqE!3RQb%P}?W5eWeh)F-WT^|S zPFm!tijSz(?hB<}qCgRf@BZI?I-GK+J>|TN{P+E*{1K3S->#kgs7{oL zC=?eE*E!`}+F-KarYHrYxPVzqmKxd8+W+A9#aOJJE z>8NqlVTNi){ahVpse07)>aaDXrGBdpYpD7ZREM>cBV8M5LaVG1+hp3KI!sd@bXj#+ zL22nX)nO&om6oc*s(<8RsBA^M>UfrNQS7M>TT=}b&#S{4%3bkSby!RFP`Xx!Yg5gX z+124Xl$CO|DR_9CDK=qPLPk_l(il@*N=#~OT&yWMDrR^>N}MSj87e9|Db6%1H9jLN zDm~5=pPFt;OHYj*9g~nU%#<;_M_f{7Tt-4nlqn!?beySouqh}bwsUGytecCgySI3bQ0K_*w%@4?}z88N9TnR0@Qm#a^5 z)QGs$jChx%glKn{)-J8vv~u_UPpSV#);l33HDgR#99AzXK7M#cMq1mJEwi$+T%uxA zqvKp+Qj=TC$^UIa%cQtrQAsYtGm?{#i-M`)R2=%TR01^&T?Q3JCE+s$GTqSEkRE@?EDAj1#C zxC|_-2j)tmGVwnH<6{ucL~0` zg0=qv;X-*~J|7FEMqrt8o_MTZ5|$i|b#$RxTU;Bem8Hi2yIS_PP);s$4AN7kuH23& z#1M~^%)lp&YD=}mC(H7?yWppdwR9;ZtK&oeTR;n#&zz})vsIk z9zA>c_wEzWH?Uv-px}@J14F|G4IVNyJc8P~edn$Nhfkinc>Y59rOQ{YU%htY_RU*& z?moD8|Ka0jPoAc&ii(cU81>V-&5OoQ`CL%+ap{+e*oE)j|Ni>Z%vogiys?oBKE(a` zDlvPEv~9=H;`yoh30b~YE8xYMnBPVuC8vx`ADx-AWp~M*(z3n#_8&Y%9XWpD)SL+u zb0`hK4S5sS4SJRe;d_((&P7Ph`751!sEqfb#!Cq%?uy1bP!#>hJ zuF(aesH#FtCdAMLF+?GTRn%I<;7j+XBN4-R#Bc^N+^xpIo9s*tOePBk@BagaI*6hE ze=x)%2D+-M>KWyWt^vBxs-qND6=~^4sw#k@{85_ zoLn}orEAl5Ei%FnRR8j6)xTbUybQzdXeEASQ>e|B zLL5EPscATm&Z4rZ<J)XHIzgSJPE*UMpQww}IqE!BPFi+tdx} zCUuM2K;5ApPKOHiI!nDo4$7k@ zQIn~u)C_7OH66!>d>k{aa4b#05hD}FkC8Y&yrr0P<3lo5Ho zC5~ZTs9sb*Dh&BC5$iSyYdITfz8q=31?hKyBDByoE-53bZ!*qhX$hlK5?ot#?&3E( zJ@xyHo`Es$Zf>ncjZRLE%D@RYEh;@DAu1_0AwE8CR7L_u#>L8c+}*s>qhfK=PfAG1 zh#Qt3m4uUP+{n==L&S|5WeG&3VVT*M;4qXTGUC!L;e?d zao^v%%AR`{@9!8|%WtiGy{o7G|9=D(i?l(d%H*t%!$t-&;XG;)l59P)-4SG(*T}Me z(S7J>dKjHbkE3VM3+QF^YI+ZSh(1kUrf<;q=oj=?MIA*0MMFhXg{Q(_5v&-hh*Kmh zvJ{gQMT+%`QXFjVDV{1mDr+ePrMt47(ofk-8K4YOj!;fj&Q)$y?ob|6mMd>5A1R+J ze^HuMDwUneLDgE-UgfI_R1HuKQ4Le2sm7=#sHUi9sOGCyskW*1s*b73RrgiTRPR)u zRNoi{W5v{D>M;$O#!Pd@gK5Y3F#${v6UKxy(L!6%Pi)Q^z|FQLM>GRS4breJXV40y zHA~if3umAedlFhPu!22K(DP!+&#+M-ec0EqIF2K3Cc=@H#Nz`|fframJqp_0MpB0z zF?s0l{`z(idtS{jzEbTjT6gW0{_5tqE(?uXf6-o?mzgy`L*FXhzuyGoId<)w`AgU9 zSFf5fd96_+IE&3hO=Y+}rxD(>;L=*uv=aG;jw1ig9ixaXOO(W(0gBbMn%F9pSnC?z z8}$2y@}&p%88qh2-@>I`U}qMf3%my{c@Hh5nymKZ>}{)#>kpSs4(Qjm*kgiG^VLBv zpNMn7Ivi@~fdg(YGdpH;%YP>m&p{G{%RbpAKe)5DSq^n*t6I@^jxX|Z(xpPnvFM=DX z=)n;e4MaeD`UJFBo`BswIj!(f>gvT#_7BeVFS6OW#qJZ*0ywLj!dgfhY0UGH{_$ zvvL$O>*vMAi}b6OO-LStVNxSAm zLs}!{-M|Ie4Edv=hz;F(Chfc)n%@Bd44SefOLi_>T9Cigpn+~zfdMP9>IMwyW+dJk zP&NHs^2*X2M_iByTJ_CKV_jW)jqPmIMstTTGK;>NEow$`aQrzO2bB%7lH~P!Q&3r$ z(VEz^8k-W#R{>8dG&{jfL)Oj8tjx`#b0tx!u~`Goq%JtaI&jv2>*1Efk+^#Vxf*4P zXI6%X$rKM2PlLTu({jXvCr>s#e*Mc9PpnrRsD*862|TH#fzZnEc>I#==&Kla-4}zy zK}&1idcEBWb&}tDeAMt1{~0sbWFVionFBTC18JT4wNVzWvA`?@XQd`oyb6sqfJC%8 zhy2(AnrLj~zL0BA!^{e$bXYSx2eeWlXypRm$!%@>M$>9MSR+H*X=n47K8aTy?~+@Q}iN=UP9B$DS8b-LdwV+Ih9N)_z?_tm9m#L!AY6w%90b0&FsDUh#!|G5<^5K|*a|sxVvFARH0O zg*!N&*S2kB>unoh8)aK)yVbVT_Jr+K+ZXlddV%$J)jLw}e7$FO3OkiuOFLh?K)V>b z$#yI3uGu}d`&{3@{>l1ZbiH+lbzk%g^sDrz8hAA*6TQS9VuTnat{3l!UkvpPK89Sw zZo>_u#@N=l-gwZ|#N=&SWx8eht6^Njw1zniiyAIyc&gz``#LCOH?{9-A80?wKGuG@ z{YLxa_T~2X>?<4;4)q<{IrMM{a!7F)<1ou%tHW`Ja)&=1^^T1l2RY_Au5&DPyyp1c z@vlZ)qehK7H0sqTxzX50KR4Rh=xC#djovpRPC6$aryfq>PU%iFoywiAI9+Spr17>U zrY38e)^6(Eba>OmrW>2-n$2%sqj{g^In6gWH#h&*yvmt&Rys4papqy^-C-L49SW=RJ!k4X*}EWR{XEjikn=c-8uu*8C#N?x;CEsn0zsjL|y zGU-+542i-nILAORX+nb4JIOhQe+ufpLpah?65-a&ESskl_bd1`2*$a|#PU1HtqL^+ zz^wwV)yhm5^a^UfqPIvBlv`ks@R&^{gP3>h1{lP=Wz#I-^%#Es%9mXVwJpB<-prEL z%#yE}1;*L!F*}CTW}I0Fwq1r<-?BOQs%@16*3C03HN{*3*poV7|LV+R3|PmA{OAH0 zCoCpz&)PzhTr1BrPt`DZ;Sn&N(p{bbN;2@cVa)8DSz~8efuEY+wr@w|fI=hKvs1v( z^gPilw7MJEK}~8*B`QB6>PY=RV*f}pEX-SwH#g6U#HsnxfCWSL7v8d}fK-9s>c4p8 z%DuW1`!?(|oFTV``=d8z577AsrVN^H(1JAnHcY)eQ2v{jWpn!@*nWgw@;^^e{Px_+ zY3?GwHWzbw_0ju4gCgcvQkz&0a3{3kW4^F|*8PiTialbZW_0P}Kl4VJ zfxnY0xsxX{kcS26i3RAn1;|SVDnOu{2-FsV+9S}tGJ|#xjyT?#k|7i3zQNH|ABh9G z0JX8ZGo|IW(3vHTp#AQ{tIp58SfA8lXQWI?9zU`rFm^X~o;dYb2TZHegmQNEZ_;N| zd@?V|~Ff0o=h|2Q9aO+bw8lK!}eBp(4G67htm&`a)A3xQ2ndH;4wpvA0O0t;C(U z>qyJt#LbtO3|cZt(q}^BEJ)6x!SjI!bUHf#) zvHXzl@4)S1v=PYBgY!i=Bap!`*hB`C!6v}J3Y_2vv+|cC%OsEjF0xp0gDr|kij10S z)kXbm;k9$W>a>G6Im=nhl01>qjus^wY$jn@Qt~aA0hZMkiPB`SrdQ%ro70(F310+K z0$eLFfC;!4Py*`+_kx%RwyPCcX8?QKn=_#!uwXTlbnM&?|A-Y~G3vfSi=Uq~2gBF2b$#&!3#z8`aZL(Aq1q zPZX)?-K1Lq@wAeDMj`nLd*sHW-B%A;T|IR2^k*H2MD>W&CGA{EE#fiTfYpb@KcGi3 zw+!@z13ko`uZ1T3C&-3t!qwMB4SR+~_s2#?(sWC`k;W}^d4m;hZ8xc z>e8jFJI>v) zG+Vla0?95Z-CPHp`KQt%dAv_2cJP$tuaf%2qcs^imc&?rGg}0GAHWi540Wxvt;SbC z&Ku}gVY3KFNd~|B;UEwON6R;5(h8y5wxBE7hI}>u$vZ2oH-0hApJ}f+(Wvc>N&<`j|N17co-6#-N}JE&kTFM)l;FgGT5X<9!WqJ; zCE3S!ZN#q?6&3VBsYrPcKYXdE!d%3XTdMB?gqGY8+N>l_3{fK#AQXqCppWnJN9H0$ z7IP)gcoE8l4xnEPZn+|Km$HTHg`3XY&>t&F2?$BZh)pu`=VFqgW2bpog@b-4l6n4_ z{p-p~tjf0R+4W9$w=YLU!T zqI?Yds;KGDdfelm7@3R z;SI&JI1E8(1^9s0Mi#;6p;V9JIsf@8N)+G1;k_E!wAOiO^fKbI$66yVo!_+W&@~;@ zZ@Cwz1k#<93JBX+ee&q|?tP=vgN>vWIVnIWbVL$f-k;LhHzcc%QTwjDNbmIJBE+^L zR=F0AncbEc`-$(0MQOlzu^QJo^$3nMH{Tbe0iaT6iQ0}_C@1}a6=^Pp;#hMKtLhDQ z7jgaox{HR1xAot41}@djMNpUnr#dT#hHm5Vzi-1r28qNR0MY9 z1GaI=G*L>0#!4tdDv*&#g~n2T5er?+`3%%VUn*vb%*D_ct2&Y;?&gUTIPJS>BAl0j zMpuRhb8Wb8+)k)4iL0@t^O3AL5l#Kk4Q?3YtxgchOCd>=b5=I*A>6#{{*XC)Wy`Oh zbj^`am(MI-f56}XQ-sd#F9$G_a#LnzgmBRC7+R+4_>Kt~Im{@x4;!L_p*YFOWZdG* zRd=)-#=EKSZ%pj;vvFSk{BFy`t)`2qy9=M>?VEmM^z9BStcuk4OSWEkq64b!OH$WB zvdJ9*3+kl4wlld~m$+m2^rQOJRn zIReTyq@)sSiZV_$0MzQt6{5@rOTRWEOVKK*{pLpyAOfyP0)0+OS4v8p)QFBHoy-CQ z)+{o~2K>-A$YCw&`gKs-k@8U2>4d6I7-~9N)JR;wCrhr(6-n$MYW=u@9KR3x!`DV4 zpWP6{jkqjsgb0(+=5c78$nxWmdG%`kP@#o{a(0OBlVc4JQ*Ln&n}1IUTV@BpQO{tRiy;*cM}Xvc^! zM9zla$Ko{62CY2MOLjQkK7?Q-fi8xFV>sG=xUy`+s~O$;8b}$Y?@?b`T>jwUoc`Sm zWIw*+?4oOTgeba%M29CE&ZqWU(@WnYBxyiAcDpt-O>EEM7k+mf+DfsF{J!2?G#4)J zgr7}Z5+_p~servaQyj;A>pKihun(beXQ)R!{e#kj6AZ{YHsq_2BYGwKxEQq-kaPv$*Y z`G(Dfv(^|usm>cWzF?ew@WcfNjWGXDwKjBoACAwKn%K4%`F&0tsh!KsmgXvU?5|fo)CxoWME5 zpA5m-v5k2V1C7}i@JAeH%H?KCTa`L=Y+Cl?!v z)thErUGlpQoYf_B_8c^5&E6#O>p1#!g%Z3g*9)e<##OM|R1xcI9|?N?gP#fqK3=`= z-riq~Fi?H&?5Yw2|N6O_|1hHO>dQt5RR7X`Z@7W*M0K1!I4639kvOZHULCt>h9OU# zoYyVGOGiA^iPK^R8*t$Cu86I$xh%ya$F7tn!8>7iMtXdVJ}qv=l6WH@xil_zae}_b zu!yM9#@#N=wk2zJ?A7nzku#>m$gdo;rDWU={rN3>OE(#{#4}Sm1ukd$V)%9U%4Dm>UIaH|tLB-hTSytQN7w^6HLfq00->91Y{GTp9+h4Ox+H z^!H@>viI!IqvvIe)^+aRKWLaiJAgx>f4uXX+i&1vmJRB_&U`lh8w7qIF(7N;n*rS4 z5cmD+#T&7mZ_udK5-wV1et}#>B@V-0xS~ZaGS1H6z%EK*h7vbtR3=D=3d!*e1XoPW zS~1sVy0nnLIi2_j`5RUitkpx4?H53|)_4*wFn_R1-Y`vB{^eS*WyTOIXgYQC|ytLg$^Y)ql}{n&$zNYAii zMP=U#o1dU|U!=Daj>tUJ?qV?*-IOMy%?fI}TqRs1Q(wYFEl$~xj2zr!R7_YGUDFq# z&!Y`5q8}v(>LLFILan>L5M zb{pF4_Ske%w|n=R?FRnlv6A>fM*jI+GD}!9VP#gbZgf`S$ceeLil-V#kKdWm3nndF zuG_e3+s0zUPlfZR&YOXX?#Bw)RzYus+E56!l`EjBa5CuRX=cQ><2k4GCr&IY*ROCpd! zTA(~ahCGmXx)j{@`*Nl5x+}Mz)auG<&5bS5)p0*w2Z-9lLh%7un<{Lc;#Ace2dH<0 zIh@%ytB2z%X8{4N~9bCixyOhoahmE@SsNsa;p zm@nYE--T4n*lIm0D)Y0s^%zS+u!BQT8@48WVSXxy>Pi5aPXbtNA8}Nr_5lqeKLRd7 z!${Ir_*l6I6yNrI{Hh}RSz>gGBBq89x`7c#4+rQ5mGlj$RH74_-1m9^{=tLy?|tq! zY0|D;6N7f4uA@$El^8X$rh>U0&+j4B(?UZ5D? zRd#4Z%b&=>v+qtT%^ZIT5-;tR|Io;K^%baI-IxE4%YU+=)hf4AL_;e)CGDisLND&s zQlYO1Tg{Cy4DW4wab&(F=DlrSRHsNNSWCFbXoGsfQdHcVf_@z|MzuTIG9Z42_T~(s zYe90LpMJoIjpvSUUA=2FD)75E@7Xoyxz#?RM-%bq$$>*hMvkzGOpK0grt5H)SV2E< z{OwP0C^l$^bM#=CqXgQMlf%a`eB4#UV8A(>q3|ca5(c_)eB{N448O7yvWOtPE962X zB_9cSD24MYaTz!gmw{KhW3E6>J50V2@ z>nDg{TbZq_{EF=SEgO}KY!oA26^qEdn^7;oWoR2I6bIUUU)v#Z%%52Do2&!*u)K6b z-j$GnvKuN?xv0RtM)m&^>JgU^>ZzKS%-IC(xt&-Xl+Q3*@zXgbX)Xnwgt$vKKYax5 zE$R7%f@c=f{7`Yr3HCyW?U*mtV2|J43DopoIgr<~tj_S2?~20puRQqiPYxmF+8 zDT`)o*rJ2B>f1hx{NjgA8=GoK9h;dpEy4=Rigy!|rbhJ49yxF0+ST(n%rUH(wWV;; zLaXmec2+2t3+}>$lMG4xu5ROO3&WrZgZ2nC#h||#*C6fGK0%CD){M9QBpix-{3|o$ z#GRC1^?u^-sV@eW8=t%m+w0@b9O~b3L(8dL^9>%ydm29hD`f4Na^Qfjjd-doD`}Hq zM?YbC^t>U1b=CVhf})mhN;YVv5VR!_btjRiCyDSDshTP6IWB}qvoQJ_QtvKO?=DiW z0=3yZTssNPS09e;zO0WPD_EQKAcM#tv>$bXVK~*Ezp{3Jsc{Bm2>iBY0X>@&-9UM% z0yKxwoMe1?DZmY7*iDk=HuSqqFQ@>aLb*WNBiLW@|McgTi=RJtzvST9y_dbA!gl}i zL;LnGA2c)~J8Wop_Tjy_u(D+Q8*q39+BfpDcNC8GS)IAB3d9Aidw~=2^$yJ#5@R?% ztkaUddQy{c;ly^jyvB33sBFb?{r>$ELk0{eAT`GrTTk#vBb<(~Z(@GG`)uungNELF zZ>QhVgX;HkP;4-wNE$kML~zQ6yZjd3x%a;ZtAb^3G7{9B!3bcp<}P4fsx^h zj~?E$sC1rT@4O>L>sMK62Z}Q}7$PldCyqf8bG&f*7(;e}y=1S(&_{KT0SqLfpnYEs zX%oj+bj_4}P@Kw>%?e3-3k81ZjuB@rGl~07}KLj^E;) z(xzEl>cW-TNWpEWSt9K+s%a<4wDazYbG(0_PALP18P3N0uIiyDE{$>bjx%Ze7}wi9 zuHIXFVY|_P_w7+P^kDsKIjFYC1R9c^IBKB2P0XoVMlzYy6Fk#@y?%e)%ja*FICtxm z-O||zlVPNAu6XC6>-yU}B6=31odlPn96z~bLd7fSR?MBmIjj%p?NNsbKZz?A(n9u3 z$*xnI%BOqv2~7)`I<>HHilK$NaKVDYpY{B*Cp$yC_81(}ef;>sSrd$Wdt#;Lm(5te zA%DFd_*b|TR{L4xEsy2$g2M8XOLv?-uS5Q&&f=bi!ST3ip(;eqn=S3VO+#Ot)B3`? z01<`gqZPE&50~rvy%#Frthvx6HDt9e97vW4u=_{o@D_IBK(@6K_wiUiATNDzx_tVP zl@8jzm0VHrb|h;YVGTnXm|fq(z9RMu*~eUEzremY4vmdCHCjW<@nSJt6w1j(ILD&J z@f_LRla+W|w6eAY6`LiZ(WM)B;1HKT^8svq@a6U&xKO402UiXFHfoY!w>qytj217 zVh+b2MMI1g8p&gz{k4i~xcvr_aQ_V?Ns&D{ctu=Ap}S%a?A=vXx<7Vj_|Vw+ zA+m1d4#n~#t4F7$mPqbm6I)@<6HKHS)g+Q9d7g!?XX&GtpzS5)QR#xm*##J0&P*r6iQObBwDJfrqPfZNTe+_zEg?JhIMj((i<%O z2)5FW))o^!EV~4XOOGGWOEBRvxIPB@Bd%wC6-f3N4%g1IMsp;~@X;1Jj1_VHzGEf_ z9Su+);svwUW9WWV{gHIXtzxK5`ZMQeAKY|T_e`=Zta)z4>3hKA0I(-RLr(g4@@T6Duj zI%s1o2A4iy-S8u@r|brvA+`D>uSr}rY(@Q+-8M|vkh3AH{w4Dfp?S*VM>jX4`D$A` zUwODe#7m_l`l6JK=3iVcA+3qWosQ5N9}m*{&K-ij9PW6Bc;M3tLmg3XpAAbNKn?5_ zn^hAd(#~BTD zb)Gf@+n0Aw|FOiV?IpqrPw_m7ZYcH=C1+>O#lNBGZ838}Cls9EYzwiKkDLg%eZ;wz z#2vB}Wdmul7{$Mbw94NE{;x>Uz`vI_$r<^-x^o!qM2vDtp9(qL>y|4@LvbS@%6IQj zzWeOX1#(U#tfBaBEO#PPs>C@t;w?WRLPTAhL4*E)EaTvAUgEm#(76a|0<#4C>h}}7 za8Tqa!p|fcmoB<+lFpg?0>E__YsS+jCZ~wHWIf=$x8ufs~qDdl1C&gnCmRUWlzy;Wxik%JB!j_XU@FF z+|Gp7{|ksxToWX&#x)^|{|hQwi2Nt>a&vD>M#e`pcl@5Qybv==cHoBP7P^V%67zAB zSRmrQL^RXCgN2pLTOv70oo^k{TK^8M^`F06>wAcB5J|OzL_2UjM5%`}2ZFx?7cDoo z9NaR3#0!c;2ylx6J@W36 z7NYqlB=l?9emT!}PJ-=}f7+#r~{EzC>fV!z+7eFy0u}#Y^0<8*FF2y0sMQ*AL?& zEmmx~osesu;UGrJy3R(d#Ai!0-^tDV+=+WgDjh^Ld6yTXane4dno+TN&&<~TBLCGM z0cCTz+$`uRXCB7Qj$f3cH+95?+Iv;Epy`J&7Roo5u#s^=x1S+MOce5z0JSL8TLEj);`$i$?@Y#%=g-% zZ8U+yt)UicDfGsU$|upX#q_%||D`<0WjKj%eEwraoy=Lxr~~D?K@2G$B>pt>gER_6 zGzzYl-ts$Oer1$E#(cA5I!kYFt4VsLE%U;R3gIip4Y%fPw<>0Tj@Z;@ zNr;uUG3UY!MYDDr+`VFrla3MUD5XNFjc|?qse&=LW<3Xv&rUZk@?z$$+*o{2pHGx& zBshUsTj7e+w$E!IdN$B#UnHB@uCqfIWEyZya_Z)3dvEDv6JlL(zfpPvx18A0&#S08 zxs+16S?M8c@ef#v8_|2FhYm1?4N3GL>thwlCUs$MvISFTj?dJ^$S-%W1lmPxP@x-4TdDJg{89MDTnjeUsmtme_g+) zY+UjIw2?>H|H6@U-wDA^JhFJ8u;j3(6QwvA%}9M?3ONEws^@xtHK#GXXNSJ<{$2eo73;#-tqCMk*tvF3&3yL zzG=^*5d*sn^61tMrJM;9R=~66@N5;m6`D%(psBL*3A`45Qo~Tv6TCHYX49& z6u&7?7=@cYxRXzB82Yik{w_lt^vyg<%l(bh=j6?sr-Q-j;-bQ0gH*gu$S;_lXD}DA zdHJ)8WE=MU;`wvt7%p9WUiw!5_E~hF3&y!qi>FN1X}e7R6At|Wp`YlT*r(H=rt(uI zFRWU(cilPths(H!r0$fNV+y7i3)pqz!j6062BKz8#FNnFsB@UW?;|eq7sEV!;YlNx zehC>#XdeG&BlL?ub>qds`&ZBJ9x!6QvB{(%MAb>Jy(K*bh6WqNlnS+!(u-z7rSAt| zzO^LGhi|@6tI`)zzxkp`wHa7Rw?5;7=1gQOb1WLdLP*0ZswkJTDUmMVd~^&pkcP6EBjal#gGV7cew#RT*z{p}qwD7vPG3Ex zWNJx$2e?Jrbzk9kYa^ot9avw1$a4DNHEHKHWo0sy34ipsl`UrRlXJGOdK^Q zT^|`fenhGifrKiN{ld=}CMEgMKqVy4~$8+1)tlhp>U$S=G7&HWLOx?GB;%@z^ z17&A78zYF55Sf~h6009EYWXtUBO^ns8qe)mvu67~eaV_}*_({~!jXG7jNh$4y?^hS z%`)CbH=w}{+_Dpji^EcTfiz%uz3jhw5K%Fts0*aCfusRi*t__1F|MaF4gTx72cG8>!<> znH8iwxFg(CoBP2i#tXMn6F(+jZI(SR_4GK*KLGQG4EjU*?IV2{x5UEqLBby_43Zp3 zh~&sLVaXt~BO}$$!@Y=P>h#>n$uq{)pTXuWn!0-G3ZjO5J5a;Kc`N3un!T)kF*_?~ z-l)0RR$AOvI8bmY-d8?Rf~tl}rxI08sup-^sU4nL>ZbBn zg{Z<*Lsc=VL{&PTT`ItHOUqU3Ri&y!s*|d7s!OV?sz<6fxR+hU)L{&a6Vrn6X4*5I znQlxUrav>7iC~5^Y0MaAGBcBz%Pe4)GAo(20%?P)3>s3v+g@HYthSuUn<-^;$-9kh z+Q=%4til+yg*mfwW{>1aBGRcfeBYOhA6muO;0{V4``NZbXDBPUC~j(Fr{ zvdA_ZhO9>Tth@INQs87!O>i@j3j{+Du)jfwd}ay-W`9nTG@}(!4vk)T?C9>rd*`Fk z=y2hhl~!6;j$Buhx;s6g`PuUuPH#69v)Z#bsm}V-W(O=GMY!@f8Wy36z=ppGz8?hs zC28OUOK!9LP5)i}{)9$4Xn6I{3Q(RQYEp-I1h(uGZQ$>Af<+&QSghu6oNoG6s5?$1I5+8#-uu{nUcgS!qR9 z#9E!Vabx~Q{qKjiKE_WD#ldkX4jyX63Ap{l1&>|5$0I_+2_EzzBU^EJ;Has1@nI+V z&=GMdX9tZN9-3q5seb(P`73!Ztu&!{o&wt8Lh_;S3&{^%aWx%{LBZ+?3s&du)|bd+ zFd`Y+h}9c>H1>&+9eSphJ~hBTVy~$j)>PC?S-XDnCjFi@OII(!<2NG-o^>L_HL%3o zgCU!7$zZ)2mPx%C(uKuCk=uw5?r_oyyYrVFKdIlbCT+N}U|?{1%*ghG0-_5VTEX)N zB#zy>|Jdq%8?E+j+P>|A?q%owZAh)au8wFpsvL$Vs7Uwy33E^A&YoU*`1ZNct!Lxn z9*YeYFOjCGIU4+ss=A8kFR6ylsTC7C=sJ6i4aQ?Ky6&Q8kHsouE%nOMkcI~Jvvc?bowLr$E$w_qrKq@TBx-7Q}mBwEQ963S`Q{$pV9TX$fZZ}?Cmwk=j_&04I0=&`CvYLbkUC!9;?g(oGN38RO@qdoI5gIjqOoQd8f)T)7&MKKb$k7(?B=_7!|oE=A+l`? zY>1xKbm01?aEX>p5}GgS!cilOaZ%B#e@|CqjHgN9(bumUs#6^tH`rh@D!CGT)*stb!|U%4sa6U4sh*LQ@mVx!E6q^CGj~yoJ1S7V#=|(+CL*%EC&xwg)Vrs=yme>OufLqxk<M>+!hUuZGV5$R1+v_Y1H@NAQlrPx9N~(q==@_ zuy(RGf-<-!40qva5^>j$#*F*yG!{J#HKRisrU)B`d^pf@u1a2r!DP}4myqsfAm-e&m~aQ1aY*+Tx60kJ8~Nyg4%;@>5tXZq8B9KvE5E^;{*-(yS;(>4M?#HZm;sA zoaj`l2KIxKg*FPTH_}SE+(N%LbRN~m(#J?6gztd;p;V5+y2|}7_t&`ZVGAv0QyvKG ziRq0IPLKRh+mcVFeJIrl<7_Q)GLQV9oXj&azx>0={6aUbdWr5gbkBeAip($nbQXS* zdF7u@=9OxGvG9e=8~@cg$vpB;hjc(bLAolCj*6f36v`g&6LL~qrs`u{JxiYl;P=DfH(5Z{RW!o1Mo8aA zlp8vCMF8HzB!AoTw~GBNmnrkuzwVQeZy@3Do|xg3vTL{A!Bkzm17HMYN{Y%zp`7t< zCH&zH8gj0`!+1Huf8rICaztEu3RTzgue1D{Fja>5=f4c2z!(K$+k-bNnJi_nlr7Z& z@59N%n>gnEfkFY!hRin@tdZ=-*&>JFCj65nJ9IhXwQwCWSq?|#)m z44uQ;_+o7o%IR`Vl_ykfs9F@>$5k~4pS9>FqARn6HepD17>{NDU;nDk|H%4x=ts=| z%D1U{|Nou8>V4Ju?|I}J{vEPFA@u(UUxrWUz}!q|{{$ zU6#wJdhmDN{|T#H!jIg4hknHT+yA><#E0#{o_Q~aunY|SJMVx0W~urAbvxy9{>Jem(N;S@y|Phs{8L>xqkHNKXG2imdjMAZv7A5l)2QBGN)<> zhVb^esyTm`g)x8rc*8#XM|nSD|2MrNQ~z(c|JxAOSiUpG3h$EgqT1kXDe_%!4e;F_ zZ}Dk?w^X(IPfB;ZA!;BMinqQc@lKl4)ET@b<^_JyJi=PxC}EF} z2FqxSZ!Pe##u!Y)hq%1(HljB8s1avd9EZB&qd*Kncw5Xsd}<=5P^`-!d{_%c z1>!8ka!L@N#)7ei1t*JeEi71TBUDYqEk6v7Ahj^J9v=={)BthFby8z{T2pqG8rWKD zP~TF6dN?k3MtoiIQQ@s$e%O+3_-HIOtAnFnFG`^L;$wqX@&#g@`r)I;`@{OWv!VF1=iZi&v)lA4y5C@n3igZK9oU`=M@LsN6`;qii%`B>&p_|&nq zP;F^pJqkx;%yStZ72el#9qV%gM^ypu>bZk8xr=x8uy`NOBgFd{@9VL$P((>l{|~oa BZ2JHJ literal 0 HcmV?d00001 diff --git a/Signal/Fonts/HelveticaNeueLTStd-Lt.otf b/Signal/Fonts/HelveticaNeueLTStd-Lt.otf new file mode 100755 index 0000000000000000000000000000000000000000..1b27e9f67dd3c10b48e7696bef0cb0dd80514094 GIT binary patch literal 28120 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 literal 0 HcmV?d00001 diff --git a/Signal/Fonts/HelveticaNeueLTStd-Md.otf b/Signal/Fonts/HelveticaNeueLTStd-Md.otf new file mode 100755 index 0000000000000000000000000000000000000000..08222549943d74fcf77879f640d6db2b41ad63e8 GIT binary patch literal 28260 zcmd3OcR&=$^6(7H%r3pU3ge*e>h4M~pduimm;;zm1XN7Ok_0446eDI)!JKo>Sux?6 zGvEYeG8m{s9Y8K9S@4b8Pm;U(}db+1$byanDRdsLo4+!YbRA(|61>@Vb zi@$fLkp3qbX2=SLVcffQ?`|9f4F@tzbtA)c>g46$t4FsTz49;}U9A;8eEa*eR@HuH z82=20;p_D9AJ{eaeS*L+z5xuQXx87qh16by*F^^evI$ zrmzQZTG(USGjtx27@^cEj$!^<`0WrGn>@Cbe**)<^bN-Rzr>oxCNST8F^O%1FFwu` z8=kTMtZ?|=9e z!z%18ofOCMTftb!vG|AOEUy3lin(ez0n>+BI+<&#OsjlWWF>b5}CaZZ%3;EA7gJJe}W{kZtkNK~QS66Qg7^y{NTL{B*{FY&6EI5nn z2)~iB#E-hHTV*VUm!UJ~z_*BBm0=6UirHNiR$!?kRbflUz&xl5bBsIlb5)pUM7B{? zSjnts+gF9H7!6xc6;@-rAF9F{#=*j(Ds02lwrGgBVlAx@lx7wks=_Setca=#TQC~M z@~W_c>8#jV6}DuY6vb6xj)}CaQ5EL@6t84ETBcRSTQT*xqg7!wx zlX2qvRE4b>8$P95d_sC+)Tqd0<4&WiXPY+7@a^ehez$C9baQcWGj<7!4+%H=rza(c z$0iwj#f8QvCd4P2lEcHCjm9o9F-AYRe3H>GJSjXeRgN`Rg)MV@zo)T{C8({QL4)bVb z^a&4(N{MxKc5(A;Cv!x*?-}KMeFHF8bDyy9k$*02{9e3&KzCzh6~6vP7Z(h=xV7sR zpBOI3I=5`uF4idxVcA}DuCS!74a<9~&Bzk`$F19+Pg2i3$ymO9~G&CPya5r;Lg;_Kk{*Pfkz3jz@(gniA8EJz_(8 zHZz*y!i=$|bR$wDF?>`M(jyTIMa3CI!xNKD=trj{MkR$sg(gSE$0a#`uf;#v6cG`b zoSe|MMT@kwG-s1cQ0LJ2*cNgl|2CmTO!z2MjB{jiY|KA(U#5mk3#J z$n;|3m{3ej#CGJj$($C(IAfYzG6o+b z6^=E@FeGA6D4>J^h!0>k*$S@>gu789qA$W`G*hcGR;F4zGwtPG{*l+2X^BreGpuvs3mh_I5QS;AA^`p z!uDkTh(V4snxPEEuQ)T+WI7}xwG*+`6f|XiZGADWQ4FS(*mC~^W%(Ze~d9_tScVjYVo}%^0@tmK?8)v?A(i)ipJ0TH6SS}fM>gx^026pxijtv_%ZsOFmS##$WF0O7Z z-CKFIZsXauUHc9lJ9VDWwVRiB_Z~fa_3qQxr(b{H0e=1g0|yNb95Qs+@DV{HnZ5fD z9y)pY!qpqsuHVeNb^G3(yZ0YGc$ojV;K|cxg)d(eC2TZ>L?kCI-MVY##A(0G$tqp* zw_n3neER(3`>%87(*=vi2QU9A{O986vFXyj181_A#AimOby2N{*Ox;-jE;$o8{R3`&KheOwMMokzBT>UxH6t$iu{Xu7r7M~iaNZ=f06d$?2FTdC50~v3mK;HUE!m``-N8u zcNZ=xTv*VYi}@E0@sqiTRj~y9KRLN>R>Rh0Yq53yXy>1OR+cRP^=}jostq{grZKyi zxlA;(oGD|rqIR&6S;K6>g>WLX0*7xRGXrJM`8e^cV^$-j#xwg+{@lYHVD>WmnK{fs z<|uQBIgE1X2_}a*#vEr>F(;WHm{UwHbA~z1>|nB(i_AIZJad7$#H?kOGB=p3%r)jF zbDha!7BIJ%d(3U-4s(}z#N0;-_93&K$!7|f$IKJd6rM8cnZ?XYrjU8T6fv)uP0VX% z9r z@lbJ8)*Jryk3|+`SeCW@CxM|-E|XK7Bx^=$$x80k7^#(oby>&w;S z=GCajEw;W^&&|(D9!PLGO94j+|hiZLZ% z{;}a>QcN)@teO3o@T4SjU{qo_)}2@x89y#OZdAAohD#ThO3zF7+`4(nUdt}BhYhv* zyD0v@5XApd4*yOC*A zJ|a2Igz|Yre4??EN>Op6P+ATr#sHLHBTS*;Nq>@}|Ck%1~lTv9}MqO1p`MI}eZrzD%< z3Ll$*x>OR@k(L;hoQ#^5nO##&G2bD@ej@h%dj!C0k^hlarexE<65&rYATmZ$QQr$Q znv#t2*pk~rY9tzCOsE}VvOIng{|#~eyDDQPiT+t>#NHrT%rO21dHySfjC@zG{w+oR zXY@ea{{=<}z&bZRVRijrL;(;7mTBdCn%X`z*R! zjIhYI_`%|{qME`+QA<%z(Oi+PSflu2IofiA$MRG71^hlfkAJ7^r2MR; zs*$SIsvOl>)l;j+R{mCjR#{d#Ru_pCsYB|ME+m^s>Y8d7bu0BS^$zuW^=EZiHL+U5 zYTc?uR-0IDVYTJePE~tey>WH->RqcJu70C>p+>9euIaDYp~=zYYTngwuaQ_It;V4m zXKH+^>0Wb8&FM9>Yrd`do3+37ChMCv9yUcbuY?9dcCAje4%NC+tFYFWS|D;_TQNW! zB_@hl;(YO>SRfXQrQ+|lwQSqkzN_tB+qd?x+6lF1)y}Mau=bhSw`xDH4Rx&Qc-HZ* zGpx?qx?41YX@ncYhUSO8dk1x9#)o3+?~r zP|ZPb@Nnqh;O7wMkm``_u-aj>!)b>Chu03@9BmyNJ4QK_(c^U@joqKm2grJ+CJ|6H(^UZ%Y!!kx#` z0&x$$#$D$3z%>p+sI^ikn)xm05uv>$FWAQxmOd<16iQzC1ia|Je4_c0K*S@keFPC{ z>|SY-LYf8#MGt;0*m55qwYo;A;u^8<{DDuWwwzt(4|hnxZ2}f%uY{+Kz$D5kRADZm z@g7$!NLJitLdKvcTiF_Wx7*=HUu&f z*jNLdsT4|Fvcu2Ld#!?6kSq#M{a5;J%G2IGvi-hZBaw-Dur|*}el1P2d0q_8#n4`U z3&%3brcE45g#3*14p18d`!imb?-!r&Qth$~t`o1>II%b&RdPs$^+h(jfln!gCZ+Sg z2q%RcIA5j}kL`#Y?iUgp9A_A_KWzU=?Ww)Hj_N;@PZHx&VguXggp<97<~%OCz3J#y zgOCpEXrTy3p4Nx9yHBsVzUW%rM*PeXQ{$(lPz7k~2sw8ST)FZ>3w*1KEu1>^bnw|X z{J?O%aMIJ{*{7kFYCj;1g*SHOoz)}JrTn)<@)92}J$2=A3>i5oY}gFF<|)aIAaIEK zi><*f44OeZEwsylrUhWHcjjHFaS(NFq{Q>lX_=8D# zlnv&fS`S)HNiA|xdUKjF8&VQ7bCN+c zKaiAhBCtFhM(k6T*LY7Xk@l6a`;nQJV8?bn6C1@t4QL1rw?mCDU$)a4v>|O6Pir(X zlvTH+BSNT&wxlhOP!o)R5l6rTEum!ynCJ)ta=J9N7*uI2q|6~_rKuuxE}sh4Wxme* zxYjPwP3NgJ-g8T(l_l&MY;I}CT2aok3#x((`FB8}EglHgv=eplpi0_8 zzl;2sz3p+a?#B&XhRiW!QkIecmtM{L`nu1dN|lso?g7j$nw$zVOKf%l-w>-#K?38T zI3YZNvG7DJp8tL#Wwq@F1a)#7aib_(e}K@2+0UAQhYpYzoCw4!h?l1UlBIs1$c*NXsX|AG_M(GnI~trz zJs<>(@4rGl(0cwv4L$?n1LUf?cZ*g+W2mXSd@#&!ftj==r7YB%L-t7}B2@6yfqGB{ z9X)}LQGqqiO$$Fke`Fg^RY`f064|O478XrRh0J1^C?1G~1YkU5qQI_vv6J7a=LX@) zmk=WtT)KCxW0M+ncG@Wx4%yf)X=^gRrlCG z_ghiu=9r;{jmeb_I=QT*z^I_&_>OjXrp<0EZVYs{38Ng$K}1d+x-!& zdjxErO>V)hB8c4&O>PTkGTV~XX11zt8y{QJ(Kw^uT}EOF+=cv7nh*UYOJK^U@EWK)uCz}o>@9(X zr8YT8etDM5E$c5;6Y0ff;Kygu>fRk`tt^!%DSkez`EmnjcxW}2)-I$jIuG*d>X!Ul z24TXw)?5L01lxIp1M@%{`v9!r$sF>)7X3%?gg(MqJBV7-1M~?TxJNWA$S9(58&_Ip zmBy~XJVWOY$y633QtR@`yxZ8JZ9Ao$e5OA@t;3bn_F7|T3Z|V%x0-s*<>_)qrt_sg z!6f>D0~elNlDfb_ILJ|Fp5CKRIBd^asX0k(r3t{$3h6602&*@wN~fUp+%%hqpqA!> zS~&jD7Sz1(SUQ!ZgrVi9xUW16l}>S4<)=W6(01i%K=}nOlcz(Z3mo;r4o)ZRJy633 zP%EYq%@op*XpUf~x0OPMOoXeIMA%KR8`VVY*0#-r-bOTQ$Cb8CEz_3DbncEUbX*E9 z6(=Xg%^aiiB+&c@)zSLh#&r!EXmHQ(k8|)uPx0REoxuF4jV2!C!sQh^&+3J-Kf7GL z&1s%sgC|QM$;^^ZfyY%r9;HZp5k~TPm(yK32ZyycP!n=Od0iQblhBtJBv5V}Kx;?N z4O7P!8I-q&Z}<^i_n^@~_A{Um*>->JhJ5NtzSQ1jNqe@r6++)gcO@ASIDWD&`{zPCd(rqqR z%F~__%XLGqoJGyRhFYVNAajeU&V6;u`!N;KXfq!H1a zA=kGOAy+cNvN1%MP;N(;i8vt(x%aY^J@!5tcQ@0VlihcLNbBK~xS!BwZ>SPHpl;zq z&~89!V69xsKDicX0t?0W2TOi^eZB|fU4xw+8;^YbMlab(rYD51$>c9acH7)rM^SI^ zr2T1&r#M%&&db|!8P_mgnBWae}m5!5ekflJm6WD>8UUyIof!*K?prXr|T3_s_0 zDz-r(WjjtY&){IO*ev8_(aU`we)@da)2(HAi-rc_6D*WEi3f=A%qvrQDb9BrHrmLY zR>S4G@oicY+T6Ij_u_6tR(swfWO$1KgO}w^l*O;=OTjAiXC1VD`xsgnG=s5qpJM2f z#>Nwc9~mC6&xExHh~^jp7MP{vG2!1>0-uiaBsCTHPAv;b1NIcTj&L2n9wr9PN;G9m z%F3F6qs~<+9M4>}GIOKu*CUs{8l+27f4eN_dTS~x(zkL(j=zI=&oyI}|33RGvQ-V#OQ{Pj?f^2{R`=Lrz#G ze#m|Eq2#m|wWQ35E{*NO-WB7bFWJFzu@EWaovFM$+Gk5I9c|s7)}&3T;V!KW&2Qe@ zd~v@ai!R%R^xuXQXa~&1v|VX(XN9YB7w7Q6U%UQf^_tn4tM$SPuuuwD=S`WOHAd&% z!#7}(49Si!My2Kr>3~(7m{|;8ir5bkILmm6FX<(|Ro2E4pw?~KJU?rT;k9zs`0;bc z>jGzOK52j*UyB@|1?6drR!Ys_iH8so&O{h{oM?=5p<3h1#Ab<mgZ_N63m@ze+}t2Mpdrcuy(Yx#HRX+I^luZ`E2WAnG+HeEZ31e02g#4X z%=%F{Emd`25`Ct$pLx}Ry%~jj=nlaG6G^*6*juavUqy6HoC~SyHPK*fE zC4{eE9bpiH*Mx_yiqiEQHPVz~5H{>@%?ZJ~R&PFVOn2hIxb)o^mA+^9gaf*3dyXC5 zDGSY#6Ii1;Wdj6l5ToG+=gUXa8ypP=G^d=MFH06$YK=sY6$w`eqy1B*VZ}BFa9CO8 z2sIFupZwv0cI%9dQ`YIBMjkdo8}X@!5>KDd zK01B$>ZiTETFpm=6FlcQJ*UleR7FW!YCUX?|IP?QUtTkhpxWt~cRLS$-b-+ldP4m? zTK_h_8}r}`Kg$CHS@Z#Qfd}$J3;pTOT$A2VzXz?4Z^S9>SS)!L!N}s7X*P4Ec_^={ z;BVqvzH@nXE@)~}#wgwR@mbm94HI0s+1s{eZq*gt%X_@a@IbQ0HE>lC=g4nC>(!a1 zYc^catz3~gdnNM9nqp3PpPiJLm85g)J<2V`Fq*dKTF8x=ipypf+iV1;KQJzc)(*fM zff;~fB2Rb;WpG_QAG0bsP8*UqCUSzF_AccDcg)(mN_%k2?)^*k!gUmg>We_;w7N%m zUDJj>M$^i;9kB-CO|N*<@GjbBZw9>@sec*ta#UCCfQaNFdf`FO=;L>18c+?dJHNOa zXmnpoE&+FS?151`4Z^j{>rY<2uKn2R2hXGWHb>j+y{6IkG#%pn^}19D7B;+=t%n-3JyDh<4&!n2`- zq$@||mu$x@f?7o<AXPWC}ZGw0KtKO3%C`cJ!-^MV9V^YJ$b zXM^EGe-UeXi#0vPT%<0S_)-F1}**6au}?%g`{ajY+$f{WW>nG=2}|w zV^`2XqYt0JZVsyd^t6(C9SIT^%0H?CSO^9^j5@FX~!q4i` z2`_Sy*g*3FAz=v}E(!;N$?;3vFuqsI5d~M`q0H(V*2=LV|8g`*KFInZr`Iz2CMZz zU*_kBrnf#+_kaxBd}x$j8Xy&lom+FK13K+;(G4O_J6fLUWoYkubX0y37kEBD=B;it z`4snh@GZmBH=ITqHLlbnj(C*X96?nS6?-97%7u7Q*yroh8Lh5O`+yz|K6P(^5seAT z3*KGyLMp2A)kUZCGA(1j=YC+65x--`;ZJ6fU}bP*7pY zt&svBOX)|{%w){Ns62MnE9m_S$t)cfhs;h2=%e$AJAeB0jtkeW?(`pGcx7Aaa|f4P zl}~p^$7?-sWmH|eU8+ZmL;<;hZNo~mTaQO``@;ptu0PYgI2GVI$54)IuGpj3sJ_Ge zSDv|YZ0+gA`g2RJ%-*sIrH|&~z_F(2e!AwShfn7jBsCA6mvshP>ZSAPlQtyWa3$1x zDJo40%7xLo(+q>j`3&ElJv)=HuL`%{IAW5uUR=c#z6yUA&%cQ{lI?ho8#|GkM0>iW;5bxZvjD-am_ne6G47cLb?d8 ze4zpD>E)H^6Q;iz(QO0L%*_oq78=vWPjJe2e{y@{gM)@H2k)dl(E)jL4OH7vfb-1= zi1X96Fx@ViW57WeDZV;7u+0JkUBf@fK7He{j{a)=Hf}7w-G`2^GnS z#hvndrAii$U{tXU>W8w{=84ndS|R9qE|C`Vf4jK(#I3#2J;Ej=B&F-qQ^Ti)>N+ch zwTDj3OxtPLGj?O_P;HNiEh$OQn3pw0FLXeKa&6|ib#vD0puy{}pno=`p!HJOTDb1c zhCSDBX+hZ_&q#$99jGlXQ0vg70T(Z^;QC6jUwSx=pzM61l$8dRDx^Wch^25^Htja# zHQkBCn3qPM0(pb*@*{<$lLgn!KHLC&R=13??Q6w*Sk>r z(i_~haD4@nUO__~4fT`8lJYgQ(KJ#vz5JR;qxcnvFKj!rM|E~@?%`Y7ciuUz?0kDW z=wILu6myM5Y(E{fu8ygsTWAzi-SR;0$3LZ8WI(L2q zn$(LwLKB4C2HajrwOfNx6&Vb`+l)yFs@6m{lq_BviH|ZW&veeGN^O z+%ACc1@^T3R-BfuifwN7EG)crtFW-=tu}3X_H3iSV0-51(KBa`ng$Ox$?oVGz2+G) zTQ$(~qqt7}E0y^H`|l^`WfIA*eE68}eS3Fz){iGHf#uz$X=e#MekJU!BgEDzn{Dpg z+B$OY8Y?k|_W7fGt9xSqV(G6)w3OfoER{16moksr6-g>!e= zt(9bc10SnsyYvphp!@fQZ&9q%};+*c#-m$@k%-)@%`-k=m4%6Xz2%`1RJrx>5N%GlWAwflQqtI?IBNHxG;l_8 z7e5{LXw}NUO^X(xPu{*e@bqs6JFrHp*_oVCM}s#FuA9m44hmi#prd*ZoKI>~msivR z_hbh@#Ek-P5Cg!f(R0Ix<3n@phK$}2xh7(H-A?_sPTMhQ=cKw#N8sMeCMlRbk9+cv zj^;Glm3DpU4{oUUxl{L-7ic%=3g}v0z%AXDxINLbl|Nej*1?*)(BKj%?w%z=DVlN{ zO1N&7%E<47$S;{mhF9WVMp$7U+{pVNqw95nLRx3(GHelX?_z&O<>m$M^8J06V-P_(pF>lu z-&jLH*AYaB&qxpDg#Dd^$?l`jAqzJ@td>HDx|%Ko+T3%;lQJ-E0Er~ePmxU|9-;oN zBY}sFQ&IL z5wz}``&DJ(7oJT;ZANMVLk5y5G^d{fd$g#35`IC8`X{ug|59mDhr+V%{mDv_5BALo z#34xn2%H#zF$nQ(u--bKY{nVwYcnGJ4*9-vs)LE3xf%PACZ?dg1FxVrYP1Qy=L~6Re}0k;+@kSy7gj4_fTJBqLXNf;Ih4qowX% z#QU@?;PAp{BWW&uL=O4fhIn5j$6rgV*$UjMU>&AT5++N{VUW0%>>_BQwttEzD;j1P z{5o;M56B0TWH$IAnCv?N)fVPISPw#7O;l0LhW zXc@^UFnw}F49_X+X+|*GW)VC*BQbwInU6U5Bz(o>Pdm!On_xNWR5r0Si9Ri7KwAYO z(2<~W6jO=8H*%jl5ZEbQ>reU;dS32CR%Is;njExx#vLa#P3}idQ%u2rpdJ@DpTuBQ zConnvIA)_mmfu=!FAqpP8?$5RbW-G67R^G3Agi zsj^5SUyUP@O6IH0bD!fz1BYgZ9nu@&zz$pK%?+MTDzoFLny1sg*>hjliKq<%C)`W$+#X`+5(DEmRbW!RpE`G(8H{;t5pOl_x zSkaSPv|;=F^Saqo6HiBuqMYhS{=k=Q5WfJ_DriY_`HqW5EFG)=k)OU}`jIDE$dNZD zN^r9xKMy*Tuz1==$|5fpO<18?9WiY2P+gy)6Jkah`i>mdeq2+PiI45g-QZ^@ z%uC8nQbo#N5BU}D$J^crR>kC8AAeD|YHim1jfPG0*Un!tf5W0p%Qvi60jErkA2Tj4 zLzNL9H*2(R=$z5ZVzxzYPdqX6j0)`*JM%a?fuFNEb944K*pO9cXCBSTTy|me={?)) zZrQPK*(u$=jWefiGVGeLDP?uMN<)Py&&v3R?603dcT<6zhMr>3nBhYQ=mrHHem}=> zP>G7R`YCQ=YqTY%eAia(deEMFx<;9pl#m*iw9UWHx|QqJtXZL&tZcq0*AuLCPwpLl zf673&NsUCPedzF>6Kf&{cnoOKu_a2_3ns0Hm+Ro=Ms_bWK@&g|McE5@FD_N$@t1FGGB;8x~E^o;Fph={2PYo5 z7wq9Lu;oWZX_>AGI#8pDTX=x_VI(R;4Z$haU)zp6*q7{s2+6v zh|B6MSb~zjHPyTOk50r5PVa9HVz1yEFFsv-k$>leZ}@TpRb~vNRvtP{q4eup7HWTn zd7l-y*Z!;c_FF@jzkM70xoj}ZGkZ#nms5%K?IpWOTC9+EIf*6ZBJL5-f_pR(PIB4s z)64R7ZzW{X=Xk2`CD%)dW~q_X@wd_J9w}ELJuiDIdhxXTx6LzRW{#efR(B3RYs0ji z(|6$R+vhs8dB3#-9_|3=@*&&{dB*{2UV_MrZ0=R*{1ru63>*_n`rdG#r*{wRJ7~Be zZ?OLlU3AXfyEkoqzsK`u=gwU_V`y{xZ1fe~v;6B{4dp8-BlZtDaq-H&+^e^b_y^<| zMuv{?>#u`yRBfPDxm-1$UP7H820bYy>mmSp;@%uY`ItZHP1ofh!ZDHV-Q%(cFP8TlXK=?b$Xy9gVC~ z!pUtDkLWJvNCgd?E zwof>$yK*e&!gjfJ=PS_QD)4D+t~5d+IZBJfVe11a&(VebU>x6<;to)|{nyL855>GAhd(bPh3pw= zj6&K9Uqu=y*>P=oI=tKt0&#;150+R+29b(%#f-_*;%7~&JB!a;K7He~^;Cq_b?^k` zlEv#5ZJ58NZZRc=4LR+>#GJTj6gYWAG;|>+t6>1M&nPVX0UVFE$k`w9D9>}z`kHVSuhqFi*XjSEaq9fwWv@i6}Ad{MKeV^ zMTjCwF<-F^4@#f5R9ISDHn41j2c{=lPO+S6x!v-b@vmy6^Q zxe452ZaKG>JItNpE^=47+uQ^0IroitKwA}Rc%#MRZCSn zRTouX)d1BH)d-bIm8Qy4ZBiXool{*`-BP_&eNz2w#aY$1YGCDP)x@f;RUfMWt01d5 zt3<0wR@1EJS}n3#Znf5Gqt!O6y;jGpPFbC?x@2|F>V?%Ot6!|jiGrv}O(K%I#E~>5 zt#O;P8|j6Twm%s{Mi3JTCs8DsOeE7uCa&De$!fBd>?C_h4mn9KkUa8`6q0x33;BhV zk>Aw{wNkBC*9ambiM|0*pI&l)&c0156dE*mB(uShhG<vUwyFd z*m3>vC40u7(5>6QXyF>eQ)TAlsdK06QfDmNZcs}#@8gM+(IlD}94)I(rkKrgw7DlS zsOJznqJ|gJYcU(uvnw2pac=K!^9|}A#062>n5gf$a(FnK z0ektxvys|!?|98^SSr#!VC8~auwG#G7W(L=5qMh0UD)JyJYGf=ZA zfWw)6z z)uCXhg=fp7)gss-)P=kY=Fm*pvTf={-To~rHmotMUB7h4g54_31A?a1`c)QH^-C1O za$eY6EZqtgQ2p2KrNSHVc`i~rPc-Gjf~&mn#yh7MZZtI3LX#(;MBPNViKpvoQTJYs z-Gk7!-w)GAD=LAsU8nt zfpTKgV-0l8sV0(gcF$v9q#Fvy7EaIZuky|AJT+L?Z^-7GGYsl{TWKwtUyW#fEia|z zc!sSmelFwHQ)I2GAyF^!oM330;gCv2ZNnEs)R6=w)2b72>PkhS1p;|z{e>U$V@Pn? z=#YtewRqv=sq+_xpY#tHHrzkp)Ufl0eqY5sJGSrMvpqg~bbMU&=(rub^%@lFAGs3s zVA?SiyzykgGdJ=r5IY*BOxU!4>Mq?0nHl7BFu8c#K%~q9qEw^Y--@VbZQYu=O$X|2 zFWw(8;2Eu>W>L6y4xu_cC~yw}f)u236B>x8Lp$O@#1t9@&1ow%um$2CTN6mpx5JeK z@os|xPj@s%7%&2bQTX-k(R8`NfH09Aopl=UYZC+a}* zwjEJNETe8mGO|^HE2Flb*4}@x;nK@%Q7sngQ4PoTt)F7!BCXSt+IQLC1vJt69~1H> zogS;&k+Cx+KszvGAhp1DoOv%d?>D#&`z!J!C{TLwYeJxv*p$e}J1+70xmWUU?T&1l zrN@JQJRp0iysl8o9=?%kSS8!z_VBP?>TW(7(+_u)2gp0hXcpU0-knpgl$DU?M2+Zv zhC0-zHUv%aI0$I4dKbB{;P}=Xx)W;oyxo8jxB@lirm1nUM00S9b&a4AZLLFORzFcJ zAMhFSmReD(Aug1m>ksmDwwF8#hl%jEbHfpOUgd@Y+en6vq?qcS3Fx!YjD(3sTLiSC7qge;vAIF z0%~x8QQ&R+n3S)?w5Pn}f!mE85K;ywKz5@vOmP$se8D=N9-?2kc6cm<-cUj=eBlbp z+bGrAk>Lr${bLWF%+j~xowCjj09N<*VeaemF-HQI$hdw^?(p-1Q-ViksQM|Nu0DTd z-g6bA$e>ieQ7%XylQl}$rF#mkV+i(8szYSmS#1-KYnVaKgHUK^;2MGZMbgG>9-fqM zADrk-ia{0)z$TXIEN;4GH6K(3aBauzixG5p| zq#@f}l4;HZqh28oo>5p`3k8_or{Ib-#2%d8Y>b%yz34=N`Sg+36vTWC;RhhF^ z>E*8CnH{y$E2TSh7SXJL8n7E4%2FTPvw#1g=sjWKQKLh{_C_Bvs7qkTUP9eJ5%p1G ziv?ve`tUa|CN+2M9`A3UeGqZx(*;9NOd4W7U7*Gt3|zcmjM_*|B|TEhf)T|+hT|1l zO-DO=;I^QPnqozH1fCWrr%HUWjXE38+^N|sVEu3oGLmc(vQe)hihOmsEuJe=Z@5g< z-Q*$gjL2sn)q`=rSNfWE6bZ%HsBN(?XbL)l)Y>*bwJalzO_7WW^#pm-31<&Hzi6Yj zSzX{&a0INz7x)yYt1Lol`FJDNC*xy91eP9ie0J*B!|J1;IjT;TR;Juu8Z4pQ_Pe)S6e9 z#fxg%ypI|;RD(p^Z*jzNEN|*lJWhq%8v7i1wX_lkvxtuVgp1^F&S|VV3TvBSUNLbZ zRBuEYP=;6MEO>PJ@`BF6YMXP&ek^2_Dj-WeW<@G{LRz3u+uX;42Y6;#ZNwSyZt9(4 zwXXl*P~SMiL77YN1ngt%vhZGwXX{wCyuhoaA{^2C5RZ7|MD4vHRn6etSPW}xc0Ji# z#^0hDV~5TfZ{qT^n9Ue5%?F(;Yg-|)iHv}EXu06GH#{o z=mKdb2%)-2IIF9apr^V%V|o+N!4Vwgp<3W{L{oh`arm>!69Y0uuq`^pq%=YjE7vXfy)UoSh%*BR?GU|dfO`>;gBY)*&o{{wj>Fhd=O&c&vE+oj+tA`LjbFF{jBq@`pqI_=86* z<}mdwt|H&dX}uUF@RL2KUY2RzooRqP=E|ls)(DRiI#+aV z=vrc$JLX~Jv5N3~kn5Cr3=I?vL-4-2sSF!qN{+*OHweQ94D>THTD)na0`Gc}f6Q%x zx7%R|zgfJ6gEyICl9`4PiKb8n?e<{p& zKk`jtQ`tHHRCWRz!X|u=V_CT;Ds+~3&5#At8(|v$Jthug ze6W5CGza0mfvkL8qI?e#%XUZC8N+fftjsa)7=z>OdyEBZXD(CkAL2Bu9`ngJ$yAoK zU~6KpmYMH^nTx-`mz@QxL5R}-DVAlcnc-5H?_b%5waE0UhIH{oD0!wP;@uM8*7(Sw zP;(Jo+GnJ{bFUPWY!;g%g%2eK*YK=MN zRBMc#jZTIcuM@y;nGb7Xiu`3`e#cNlp}UFhPQ_m4F3!pK z(0_`40lMcEZ}vo9OPKyNm7~x+ge`KSkftoa;`-SN!^lWxpak zPb*Gg=nS^r1zWdJ%#82n$i`Bym*Yck&OVX)u$T>Hyhh!GhpW>@f6 zIsS88#X>BLPX5F87Rpa$jM<-DvBn&`rD88~&42j&pVIXYO8%WHa{nqF#$it?3M%fI zIrHDPiuwK$C;zrURRZ#16&HTT+#77oj72lP|3d{bRsKWr_oRP@3|ll~R>rdIDpugP zxu%L;a-9`WvFm%`|5o77oQbf@S4*kzLaEkx9bsF%?TW=&L4|WtBa|9k@gl=NQhMNJ zQ-hg6W+<*6ad>lQCZ0&8fFGnXqqmgY+l zd91A!&U>x#vBLJ+;H6e=@u|-Az=y>a{BdO+jE@Ri3dGrEC_dH9Ewb3=JgiM_tGcC9qZEIBjDUo1Iy^}se^r~$JE8%*kN6A+pM{5g}H6c+_nvL#sOQhemwcn@Hz+(RYaY<3g#+`^|i z-fDIa;kl3ZnrZPyvwVc)G0GO!cx%~nY_||^HLGpL2#@_^5E~5QTfcc8p>sSrbK#UOM3%5}*srQrR{Q?ayskFxBIbD3HzmA7)5*>fgTXO$Kkrq&vT zQK1$r>#A|7#;Yn*EI5vF3Fb*tp0#ABwzgm`H~6{ux-|P!OubF|jC!cA$WMQ~1TqQN zcKPZ=`TEC>^5bIs$NDU5UJGt}{MqOy&yr?MEqzz7h0BJiHI}9w-Ft23 zO&wBi>xiV0;)rLPU8;YFleI)}rmS-{xYXwr11*VQJ19Ic%0FsUoG~CVB`Fzi!%a?$ zPaN%9+eMT!s0Ga*8Jfx0RXMvhb8)Inu>T{$7!VbUcmF1vViV*mxBT&fU%af==ocTK z?CS2~R++nLzy8L)y}ESm)wfr`5M!5a-MoDRyuF&qS8jQ>HvUmnWg9N~+SNTq} ztA*vip|z{KxVyNzw03LR%CnW{(EtBCFlGIpan5p#VoDa`J=0>!6z0CO@u$cMc)8@1 zdI$MK+ttUcs}Fwa|2XCKwU$nY9{g%G%>CyN3x2n%cBeo$^u*0~zs^3m_UtUjj}rrH zCPk0EF-H7$Iq=sey90xkS$=CCVjVc8-t{p{9@+)Ac$imuQr@{Cp@_QH`|;L$hLsiW$-Oz>Ip^E^+xPs~x2217DK<=! zORLB+-1DKY{8-znW|;;B>9m$gf#LqzSfj+u2czdhofej)D|B9B59G7DvRb+G2Zj%O zC7xK;I;_ay06wl9zDb2RECvgc*l6BijlB*#l-)+Rvjg{So*XfXsX)3ckVbQhNWv~- ziSt!vTE46Qm&C(YyH@vGu*u5 zK~K-)rfUru9rr#tv?blX_vlF=Em!6Kkyfc&+mkLMElqnjm2`Rd&r`_-qt{yo54l=Z zw|#6f3N+wm6%|@PBXd8mJ2k5NH zu1;QvNgntCBdLr7#=wv$1*w%lki=pr>ff3>z(jvV3lI4V7#`J6))jeLbE4MMrp$9B zhs~tX;UD})$dVd*cnTnk`@vzy|HA<}^f%?vXz?&>i9nGkJrvkG6N{#ov$ zX!U=Qdt>Y9jlHJ^h8_k!=q$EZKR0@NcEdj<6gTR(hR)HC@+a1|Uwh++*Ssoq(ly`J zv$YMg@nGJTOMhs*Qh#7ne(@n;N&UN2m8!cky=o;r;pg3`&a8m_Lyci&gw%O5DRK_g z^*&h+L%*$CE?;E!KDH{{AiXAjXkY2JkVU~}>KMSsVBmQ1W4y-T?hV|5vGlBNP{}tK|fZkZfXpW=(iv<_LM|<+Lqg98Gk(Yq&GFUza zBXK#7y|d?ZDUPEyN0t3B;ddpC!jyFT3kI2qWSgF(7Nbn!nuWzUuhed@(l>&DRx_3Q zjJH)s;xlHqycm7z`DRFvv*Ikr^x3|TM%3oWecspl_Fr(gU!&LUw52Wo0*{Qip9r5;borH|9v>(^douQ(QUX8lBnX19@)HL0 zAMq2)3y^AN02y&CI4K~DHp-(^LcBbAaYZ%TwBL)f+Jt)VgRcD2${9VC)uBmOb^6j= zXWmrxsOjGphX4k3ZA#_^M&6sOv$je^gZOz^qD1lR%tVq*-GMHL<#hzTZh=5C{8*Q5 z;|raS*+#1zmi7P2I(*%7u1$y(ms`we6P&nIPp}*Pq4!$%J>Aw8&PnOgPfygI4m-+( z?+1K>7G#25A{N-BfG0g!T5|J59AQU$%HR~<60*Q|R5H=>&j1X_&B7O|L79LFDMGA4 zZ%-8}0&TQOhO9Ql1Xiec#~+qKp9k#Fvt&sY zE@Pgzh?~oJaW_Rg!Z=Y8sKG4468qiyLfc{JX9~TDkr)P@3Mj(s$?~Y_E#eY_`m1gT z*%&SEANS4vzbO=I9Q6V@7GbyT`U~d%A7;p6!k%`IM2?#t-ikMqBU6?urbxI2&nxO~dKp(Lao-!-mZ_o3k%TeXMrW z(tcSnatR6*<)2o#M=;VKDJ7Muy?kG^-@{OM4)wi+#XJ43A~%sxz(S#brA)@(lqxKl zFs2u@#`Hp=RiQ;P^J}$URD+>t5@{x9M^rQkOFyp=&Lor_yaRxj19+K}lm|pMj}HaK zg!C}@y7UNv577yWj);mibM|Hd_wkFcV7{uon9>cn2>ccagZ9Qzku)SI7CIY4&t7PE z90Co2*+Dd9eKdS%3VOF>Gk&)uloFp&1`f(yQCN`Li~w3aM8}ZYRK#IU<2JxMfDTs& z&r1iq)Pyg}kRnbo`@gQc`O}sE=c)>?0JD7Jj#^D56`>6K0!6CkZKC!C+Qdc7J}b+X zdUs~|-TZZ4+2)UTW!%2M|BPd)YeDE;_kbkDSDXRtuYxBXrb7u#B3Vmrl5+jIeRNln4vkm;$YG$zDN<*1opNV7z=}vWl+b-40 z04hOwYtM2Mc3-WLES-<{f)weFMU$om@V*}k#41eV^C{aJg}iJii={<78-^IVBo0ar zxHke+#`hbfar7CJmI@ElCmqQOH%cZJQVD2HT!^R7!Ykvyt<#{#8+$73LHpUrdHd$-8cW5sOO9Y7DXKkwn({C13|bWN zv5O&REu0t}kuMnyJXS?y1cNg zS^_J&B@=3G|7lD9R#9Ke-i%vT6&M|q`qN9}&zF-AH#^rn%{B2T=ea}}>8`C&jWx5m zX?R^GGQvZ1wPDDXhTrc?dY862{d{rLIrSI3J0ayA^^_6MGtB{mtE}Iqs~Pv{Imvix z(-$04H*|N%#xsW(HIaN=N^}=2L3Wm6kr7`5T1@E5R4!|fu*5HW<(>^?q23H!dwgK7 zX1_V!Z literal 0 HcmV?d00001 diff --git a/Signal/Fonts/HelveticaNeueLTStd-Th.otf b/Signal/Fonts/HelveticaNeueLTStd-Th.otf new file mode 100755 index 0000000000000000000000000000000000000000..3217384301c9a18b143e6d64e62370e045bf3561 GIT binary patch literal 28124 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%0DDNW=l1Iyv z&5n94eUI&h&{@lWv{Zg*)nqZJVfgJQB` zp`t)>KvAkVsW_v!r}#nfQDIh8+S=IK+G=cD+WOk|v>j?2ZX0Dg&Njt1%XW(G4BNT3 z3vHL!ZnWKHTVi|4_Nwh&+gG+f*#2z$33gXGq(myz5V<0E%U=EgV7DJY~<;b zcqiBn4#1(3qwEhjo54S^Z%8&o$X+-!LIzlZdF(5~aUK@8gqpd5^l zT{(}nV3NznaaZ8rV`r_{H5}4N+dx;+gG6TIKCd8&Q?^3nHa9SjG{OCzJUa4l2Uxe1 zq*kvW!psGO*!N2>9s5y(ZFh7^Z#O%Kbe^tL^6E;E)jol!Rt1vUClJ&sK~DP+hUn>H zr6*EKCy&D1kW%gntr!J+-efmR?0fmZ(Fgq;%#~^JBZD%9i&znNaplUqyj9v_E_eFO z1+z7=3yXK@l#%Ev)-TKgX?u?(9k&2q5_-Lm(tvQ7Ll9E&#I6Uzj%y*bpOviBs>Rt4my;xq3J72=gUO7(M#go>FIBg5t=8P}cp*9CBb4W>_sZqPh;}eZCF~M*|>XZ{J7Yhq}a5T%QJL2Y0S(GyQd%0Ts%}z zb~0v*Ve<63^Jc=HDRl5xuzy|`Jq3#=n8o`Fmh{RnFE|65HSU9^@7MGQthh5;#(Jn=8-7O-R2Eox(a#(B>+q6>OWl zO|xUen)R!7$_ewQCcyqg`i3=WI^~WfYj*F{Y}-0@>Qe`5V4oG_oi+_ctc;LJPS6q-E2R^DyL7Hm7bfSQ1PFyKKtpH!4I8X26k|3 zakax|uoLl}M&7>r@W{>QFHC(pbsyQY15ga7U+OalI3jtW+{}r4 zx@z#Wo(HgPf0WRz-4dHPzfS)?TdO4Hcpawcmzb8n#JBk5?I}e?J9ZQmrEE`5o-iR< ztKvsyPYD_32R>R78V8-R-FWPYmBvLV7tSXr~{$T9Cb>1MJYaYRDQGBMig)?k^EJQ4vQ@alIOqbMD&|x2@f|jb>jXn{3J`+{I?7(Fq zWz9iTaU>ixJq5@rU2<8=aI*`X=ThR9vyjr_2Zz6uy{8u=C8)z-L03+nkCbpmwzZ(# zhr7V!y!iD%Y^#C8=DV>RQysQ7*`!lCj}P#kqbp_~EZKeRvF4F+OnCqPhW=4HrL?_I zI*EC5(g_UQs>M^Lxb84kDg8c$`F$BAi=J2zQA;cBpH6`DC-lb4XYo6aa@bXNIDPS3FO(@N_Mmh%1mBZmsTR5#b7Y+xmLAVTB z3FQb%qGyx>Hs!H?yY7IBjJ@w+8|t5IK} z6j$-eg<1AWX!C(abIU9x7&4fXa;0%0bU#?9?3G1$a*>kAVAZiN7GSyUj_VR1!VKvc zI$7C2s&CkQe+4cpg!5N^n}FZsSCyg<4+kFhF)28nQ^N1o1{%eXOtHO@hC-pmmqJ%A zUy^Cg$)cxWB3Bk-*Fxxt8Ahef0ILq^!{T0eF$hXZHl9pojBpZXGNX*2Zd4jLm3@gR z-6VE0O)x3pr!-jtG<3S4gzX;q9fwk?!5iQt0cS%=SjAEs@ zE(Htnoji)H_yDbfI5S^<)O{zffJ|&EZ?2^}9|sxWWd@ag-DIddUYEO4bnK9Nb7#1Z_}M z^_;!|_ohHt-iY#-(&2k`KF9<0^8*@Pp)D!t8-2X$4_R5&AK?FPb-O8BdJtSu@SGYA zz8m20C$&4^^9AQY?W`IOw3;EzfNKp-3$z78zjUSNP(1)vasd#gtPW@gn6{Dw+(V(f z?of}uQlCMdYBz@pgj?G3YYgSpLbS$1os&n26qYI zWZ++b{4?b-RT%DHt3`jQ`U(0(XQQWdbO2>5m+r2ywvDx|8cK1Xp*_^E1JL6owS)XQ z(6+vc$h%Wrt!YD`|GHCQQrOxSOQ9{H?IFzx^3?%;aJ0fnZ5s-GGl;Ucrb#^VcTVCN ziC_NWfL|I>yQ{8)`wZ^;KY0acm+1e~N&F)5%3qztD>eKg@x|XcsF%bS|Ky+@G{7U* zz_t(v&=!A&$ zB)HE5QFO@AAZT~E!J`W9kbz9l_cl@p;xydu!A3+Rq*F~}QX`@$xch8e1l8L5?wFh! zokVp{%ScHB&I2Z}=B>^z#pKpI_u%>uYkGCbEZ}nh-fl{@r8>iHQp3P%5d)WYNOaNk zNb6l9G(8@!_<(B=;kW5A)Fil-WNS?sg*E>En(s&9wv(sSM_RIgNcq|7@3dcyUwx|| zO&hJ{+R%IHgW&dB>49qwt*LUbk!+&Z)TAkD((>sAf2Mtdn?`5S3;reV6grYl`7@2C zrJ5+f*-(kl0{wxe(SN2SLCPQ~Uq<^#_Yl(3^^4NoMl{_UTvrH7wXn6Ow1$+gjIINWdhQPz+R^qxI%iZk3e5^{nqc)^~S+ zlyU$MTjPP&cWrocK(88LongM|4t0~T*Z#LO8fcX`y)Jx_%9Yi?N@GO}v-7vHV!iRl zIw}B9ydhN@KhVcaY~m-5bJKzh3pR z>J_*j!9A(E4AlSc{_OvE;1V?8SyZ*?Kjo|14p5RiTy?o>S(WggVE_Ib}XbzI~$&Lf3&d>Dg|5Dr)@gRlBNo{a2+(IR2||f0tf$&sypeShJzs zBnK-tW$i17{rw-*{lBfnUvpOdF6H=EGSxuBApHTBnoxE8ukZi99{-#d%7;F$+9cKf zo3qwN!hHFE+9v;0FX=)k1zZEw4rI0VAgj@^7As)HH=|lw?-Z;~?*r1;a4Hl=V$Y8?Sbo|_Q9Q7=cx;D3)X8GC3PXE0&ekb29FH#w}f%z4UY|!-v;osgGT{){9q)u zhsTcU0}l;l2g7O0#ALo z^D>BPV5QFD&<^{lkYthCx#X=UNou?2u@DLl2{cCZytLNTDS>=!Odc~0oNUvq3XbmVow3`89Zu`=s-@l)=*l%r8P;KA8jCq3~v8PPEAgP^|40o zsJ^>TnrjtMbGmAn6~21@wyx%IFY-*tE!|%%d6T?m*2`pVTg>e{7uTXWS;5SD;~*(Y zM$_IZFFRII{YAzwl-F1mxwEtlJ+qyRwkZhm3iR^$OG=}y+MGskX_)j3hT9j@U=5V+ zER^nD>?}QA`hPCZrcR4B<^59c7d9_-ozzIpmDm-`ti9T6rtCPlCbByH8{YO?G;>6w z9b?kM`7uwodDZ%pPTB^@nX$=R=hc{%54AyRhhc`)*x=Z4N&1k~jI?yPhc-PcIW^w9 zz85b6DAlTO03On1QJ&r&UM;FWxP1Gd4~b2LJ9pC~5>uoLuY%!XUAQP#9|ZcgcWW=7 zYH0U>!TN#yx^?e2uwTdseYYMxdIg5`>ggd}c;(+t|E;KM9A4V`wfx(8wef~4eWkyV z^=o;x_VV^>=i}SPzm5OM|Nkp6WAk6zoMtHbj67)fT-l5n)E!U#=kZhEa>p5s1h!=PzrI`ghppYsCz_F^*M9suU)WeSugT9-L+hl)O}sLJ zue=cYwRv%9*eaXKmXUQsXEZ9Gu;QL`Xsdfy>oK$29$m5f;Gq8RK6Y^4Gi>cNrws{n z%lh|O9e1#>?LCWa%R2}C*URJ}uKbleU&~Etg7NMRc|3 zCbv|S(&AEbi@eVnGnTf`=l$dTyl*p~`EZ^&XP)i%{r0bvJhmuw!U9ieyiz}lR~Scw z5y*6a=3ac1je3OmLWeWdBM5ryLC<0bXpIJ4%XnfpJ=87)`jbI7vMqQJ0t{n)8ieLU zL3rfrl}g}6`zpMwfW=^|SYCcX(5V{wk3l~*lpBoVHphI40dgqBCLdx`?13B<$i>kD z+XMg;znDljl;Q@J_RRr$)1f$=aU??cV5U=12Rrbscsw8|^R3&~&CA$eT(hT0rL2rI zwQNGFupoS4|B5l{TChrcC!$8|FA;4XKRCTlYPw#kSj)dOqs6v$?bMRyj*gaVRW(U} zHhr+Y5p%QY!6R>Gy2iZ&4RU`(#GFU#ukGBJ*XFv^Mk5^zT&aJ*k2C$;jh<`9-iG{? zoa`C*8JTAfiR1B*5YYreL=}vMJ=38h$N)(WP~aZ|bRT!b%TSUvz(1jvD7c9+%C;;1j+h zW=S0bk}6md?`{v!fK{4w1@V-z9qceuj6GP7B1x6RaPK&i&ZybvnfmJ2FgLICF=eD2?N4GKN9)pO=~p)o zT;2I=Z{L0I=GI(m?b8D{^=j_OsQ6E6ST%W4&{E2!#;VRGr12N!j&U+MZVfjO|GgQT zDsSt&(rz{`{OvyZhw83f8fAB>FSPPH)}C=vcko=1)>pfxfV|6w8mDM`uG%Zbw(sP+ zMM^nlZ?=@JHB77s?~Bj>*>B!TGi?M2Nd$=2brQ77g>6_K;G5!p=2E}ILx%ePkuCmS zQVz^8EY^(CDQ6h%+5aoF8v=vDh#5bG_Ae$}2%iZfhaJy4LPSXhmQx8r1d%x(#@*I& zJfFzZnyksaA9ZS+PIg?P^;yH@Xof{cOucb3<)^v1ME70o?)jaf6%A$@)k%Ms9SBd- ztA94|*sZfql)AW_XZj?*$5_2CFy5o;W!hQWdld%l_6r(&V!9|#<1)0dZlcmHmrQ+= zW-YA*VRy;veAmZ}{Fa2q>eUyyi;o1|>IHM%@0tbh(7QWogy9#R&lz17tbY1LXj{y& z%>HMVd7aByFZ-ooe&HXZTdU`I6`!_#5ml}E?h;!dxN4I6A##P~wnWCgYs`?Y*Xy2G zAMvTMM+)5wmle#KfMtyx_YXg3yfBV5`8kK+vLQj&gPH1VvY8ScY@L)aWezq-G=g#< zCZ-Tlh6O~Nb7OG9d&IQ5R!Ev6Kt2T zz(!>f{n7lq>&0Q@$i}$$qj*cq0^d=}#M3_uupl>!AGii(12&`xsRq3{T7XE@h#Z{& zDP099;h2@B%XxDB(Rt7a71IY3@FFThYm^16Sn&>RQi0iknfO^W`ZUKu$63_LaVWW) zqONOrK>@hYEb1wBn_UP^f1%?kbOJ_0S(rLd6<^O#!YyY}M?c)%bc3INu(W>SH|zhV zP^hpofIs(Kb8}{QmgQDQw@ou^3yyzmmZ@W@I%U;HwB~20JYU-TfLyI#bV9Y{C$g4X zqurED)sGFeZ{I`>G&Iq+2d^*1upx+b;dI7x&80SX0z>PY;J%EGcygL*-CP%mUp z&z=^`{#ve=pQM6)*-E zo1+}1IBKsLbYu)b4MY+|l_XntDw5lido9d=FPgzt=ycoDiv%6d@Spo+@89{LGeKqj zyN$X%Ma?^+Lr+?VH2;fPwSAtWj=Q0D=c{_F9%bI9r{y`D+alic96JL~GGd7`-RsY{ zji2AmGNO0BnacuZ~%nB5+p#u%ID;U0KVm=ru_-jWu&>$|#8UDMDUA;AT5*4*LF zQ7|FF0v1gZM@8xX{elKT`7?n(Iuw=w_2J$TG1}09ECQ!ox=JQb#+1Zq)f`O6%-J(A zZ0ToHZkk|n0oAv)oX#x_%vG-F`*b>mh>Tt-voE%;*s004WS{Z8+{>;utw-KiW%w*; zKYU1U_cnu*_fwPN*wlAbeTGc#ADW??x~#ln?VIdOCm*ENDjq*7cZXncDC1g7+rtO_ zK2Ka{%*#qlh+TVg*MpqDj&APte-Jr`Q{+5HTgA+X5P>$ndl_*w?uyUYW6O&&R&IQM z$7Rm!n3XmcJkx7yi>q1#wO5_I>g0PUe8TJ)j_ADAh5Wtuf;98U3WScxR|xz1YIIqy z*YiA;nEk=){nro`G^mRTdJfYXUW*Q}vU1H*mX&`nzkRvo&Q;^z35S=2TzsZfS3bUS l`GVMYwwavN@T~?~%QhKqSINvdg{2jk9kS#)Y=FD6{Tpng$@KsL literal 0 HcmV?d00001 diff --git a/Signal/Icons/AppIcon29x29.jpg b/Signal/Icons/AppIcon29x29.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14606e7acdcc2b458142c634d6dd33e50c4cabc2 GIT binary patch literal 7042 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 literal 0 HcmV?d00001 diff --git a/Signal/Icons/AppIcon40x40.png b/Signal/Icons/AppIcon40x40.png new file mode 100644 index 0000000000000000000000000000000000000000..700b6aac92c2325f45f4d85d060982d5ec81ba9e GIT binary patch literal 6718 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 gcWq|Pmu`J%8~o=iI?oe}I;$~1*Q z77Vj!PC_TE^>}wK+lW4fF$!YyKxqiX)FOySclTimq1%|AERGqh_f{bc%3_$oHlnQn zD;|;Q#aa`>XWEBY)7(RR+;I$;g*ntT2oDmlnL;`=i0#V};DgLynxX;cm?IWwMzZp#a3fr?5OPu8=G6;?6)lHT*XP;M7`KP3f4{7B+iIg+NFS0Nt3F zkZI8ZS}>1kY{wLE{rT=pasX)N(n)UwLObTK`TPxW5I!!?kvvO1n7>bn~-3V=t8C$42(Ho2z~%84UWbmFnA zq%7l?EM_)Y8$JtMcXZ$BILH{H3Cfa!0xS^#1`CHA_6CU}DKw(mf}xd|A+6xN(d%oejo-=EV{~g-kZj7YqqBkS>6_kHwV9fNl$h zTCv={=uBTI#h1?a=RtK=d5mA^LTy<=OkX~jO|XBmj+yXZv+{*T;Ie~i)7iY0Py&y~ za_4d;Z5$hc8PDPs%f^084oU?Dp|nZwkVc3Z?421unK~xlIR6^q>G#jS zB4%ToX7Z;eod!4?_t+jfzL`NcvIJhx^(?_mi(oqB@3Vu_GpvSRyknTz7-vs|zanO1 za|VkgD1AoLriN$ZS%xse;b!pf=h$ovHXa_}OC3t(7>><{Em*^O(FIHabOzEH;j`%( zcZtYl^XQ!58Myxj`7OS&z)hc#<6OTLS0>$8=rse&nxBLJU$%@#4C>D1%``*5LTWtL zWc;kbjDJ;Mf2-1dJ)SK82w({+01JreYB+O$f3UDbl5l_}2CP6SL<}5Gvc#cDI06n! z1jq;^3Qi`Nj_dVhO%+MzW995vNs6LiEG@An0D}0loWz>IDP%YXM>Zi5kwhGJW=RqO zV?sn?0TeJHNx>ipXe`zeNx`CVWXqW)XDeZ6mZX@NP~dPA6kzhpS^@=Y3E)sTG6L~0 z*8WoxXF@=eaVSd@A_D9;iSXaoi35vm3Xy1n1T8_KFyw!c#9D&y7=TQ~{+c06G@1yi zMN{C!zeZ*HyqGQl#v4KW8gSp`@`EShY0vbViY#=z_rufO$9h^b*hK9Pn|REcrlXlH z9FwwvUQCeiUs|-EngtUF4+KxJ`L_?=qz`t(af45se4`71j< zpZ6ptp9Kbp!UvnO@zVnQbTRrJ-@u~-JY$+(dDg5P+ug?v{mU87X8&G;7n#|+{xERK zYb%I$ak+dEbFg|@N<&I1jlClLd6H1O1BGPOglPA~GwcHK?ADH#Ukma#m&Dg#kJ{J8 ze|W5S?y-{YI#UrX$iAUJL$Aw6kwl?n(I(xua#86jMc0Rh?gsw&b7nNs_G6vqfoRO$(yE4_4W0o zrBrHy5T8=-`=aZs?0s7$a>BAbbY5Z0&>oRJcO4z_2j0~#HCLKy z$@6cOR#a5<^#Nzwa<<%BC9)ABySBQqv5@`h+R>vbwp-fzl~y%>8_LSc`quqwV`PRb z;6!$FTYQWV8oH7{RQ~d%8a6F8RqIms_ZX*h+Kp5%0z|S%#1_Jio_{y&d)^ZdF~RB{ z1CcuQn390rKeu3t`hFX3ECWqd&#C0K6|LzII zG23~#Qkzh(0x#{$0ZV&4Y^8~QvK!FLmUv$b`9YB^)#F-6du-wZPFJ+LPzot>1B=WP zO14F&xu`YM^h)v!`VwkQk`L!cXCvbNSY0C>=aZ`y`qHW!^=D8I2Pu;4GTa}L@&RZP znfK87RLGY}W$vdS-vn%}$S{J~4Ho(aM@w=GPIK^zE#x8vgSE{;33Ug zL@-Bv`^_zNem+-QaUG{l2Q6PdP-Ew4>tJ=?R{XxL?^A>3GiP@rBo3x)OY{QqZ?r=W zCGm~sjBt=u-bFSxpK`7HNJzqkU$x|v$Nb}aE9*|ThI$afuQd2Zq|4vG@JK+6} znv$L?v`)>}T`}yNCZ16~y5D!KjDG&UFb0rPBCC3q>Q_l!tlek!=BZKi*KXU9+UWJB zhG@0Y)tkQV)%4AZ+{^-`3Go~A)Bot>9P2&)b}&oes{UDc%P;rFf_}KXoC=}V+NOPL z#)Bt?-f?bRQAMX>X>ERW<-Lu^BT6#1%xX6EOz9V;DVnsDCEXV!|@XjqjG`6}I@jo!u_*&eJzO?ZjC z<@@M*$3|s<`a?Ijc|+pJa<{>YRbP~kfl9{{t!!oz3!xOW!sC`mjwV40hOuOywcCyju`UW3r1mWhQv*9b-Ue{!Y zci4T{-OMP-IuycM5p%ak^}Nl^^P;j_2ChU%zNyd7eegMJxGMeJ>r&UDMEv^4H%qSv zyfUw64ptp$vpAGlCLu>KuPKYdb?>_5#I$!{jaKeVNV#_qtN-SH<7TTcRQ&L%A&o=1 z@r#U?Rt7jT99H_{nycqOGL^Y5_9y>zbePYkFPE_5ooKb@ayUz8iN%KuJ%eFiozecS zEv1~#id~2L_h8Cz)i>$-$bU5dBUyuOO< z>Fal01F^LC8+Kb=ExRu4Tuqd3LfBian8&(VKaxI0$%?osKECi(?4V3pYrHMbnm-q!9fKqehga`vwtaD>Q88%TW; z21NQN>3kr~t>M5ro2%uN`laOGZYvL2R;(LGPhxG;hXepodOm)H)Le>!nrQ6Jr)}wx zq+XxwFY9p&ug^>A0 z=0?hXwC8ZfU{9=qVIagHQl#m`?d6cH)5U|>?bI7GH=$+vw7A!E-#n+QI)q;n z*XTB1;cyReFho`^pgq5D;FKgWetkyo9!>#cdf`#!UP1lI=_w0aPS zwyfLSXj=Dm_~hIa*I{?Vhx~>MIZln|u4MXu;>eUXg@25xw;~K}4#%ogY#+XtnTKER zibI!0isYyOs4ow(i8_^lA?jn&P|B`E_g-oWeXP^&X#>~ zd(hJK{bz%$SYtgUv(Vf-r9J}>)km`P{7}dT`r)4%3eIf!c4x1tGVZn4+8)-jb9#Bn z%8=tM*@}U+v=-Hm?|QX-ny<9h5h0e(lh|K4=zO89mE@E{2S=Mi^q!6mtFAm$@I`sy z(^a(Btx&e?U5^NvU92T8>U}2%zgNIo{PSM*=T~0ave+uT=ed)wX2Sk=Mp4!yO?YaC zvH6wHXK(ReJYALX=vr4l*8A*b<-Eml%Bmt7!YU%%ue4>TH^3SCXkcVvW4_vGgy)ma zT5fFS-9U4-FHHHb_|)(RK6-BL8@?RbTCqyyyzixbSGT_3pP}$TT1Ec$U|QGF+WJs7 z%DRO`my-;zwhK2sx}$^d`yr&b;q++xJA7(`GmfBpp&`ld>BUF$3cPjH#46O$7pu(_ z^vh@zt=Qx^nX2q_w|eV!{2i#OYNY`a~WKD6sm za^dTmf|qTpjz1l|apLq1+5FlCF@TKT7R9a4vi~?c{2+0Z7Q->0k3@>^S#tR{?c>|< z+e+I%ZjLKon;nlz>hMs%^Mq6{EJ%HTmfL)A!P9bks-NCN&jq{pn-E$x4fiO@ho-)y z7boWBNv;^}y$UHbd-3p?Pqs>>aw|$S+3=wMk(O#jn`^D3=!C`AYwlQwm~VDhleV!K z%|97?=I$_O>%+wQTXiD?*5$TQ>XL0a*6rpk_IcUqCFqYj`xVvhFF2smd9Qzu7-u+D zJEi=RFhT?d`Os3G?Nvu#eW&kKvVImsu}HM3c;}`&clR)56wcxe+kCBcn^%Zawvv0y zmEMka-dWXXdSGws$qJ8lJ{PBaU=zsX)MRT{o5V~i#6E2ri zK5#R-UNuMoA_jznDCqMO-wM7Ctl50<<61bRTHM<=E%R_o;0U+hZ3kZN{_q!WP+>^8 zqVqxVyX}a$zVjoLCe<&?BxJA0mmJDR25cEVtOeS)K#BY*?8$QVz4KnG@(qnFn(lrS z$Cy<$F8=NXu_$;ocwn!o;e}LJ@ex#NLN4X4n9=!i$M-jD)5z`DPKrA1WPWw%NitGS z>j+gZt-kb{eoFDlLyK_6tL9IreC^1f!$+RipXjtH_j47?y;?^NiSfr`tbG_O7~#P4^`b8+*etH`SWk7=&K*X3 z^z~rpB%e84s;}-K_KK+EEfGPk=_SAzsp~Z>zL3izm%M_H>YG9 zJi-|gQ^8(+A}Q&d7?1Ky!eh6lV?-2#e4tpyM!tzE+Yu1A(m-Y*&QG9z@W{PvtK-1Q zd5{1+XcazEb zK&Lf|yLKshP?hQvMAM2Eixstms|M^7yMC9~EUGi8d1blQjbPcLNU^!?-~0`9mTd0q z?%sWMaKAG<>S+>fU_~315CN|5#nOTl%hbDg{GNPO%~mIvX5))#8;* zf{CDRw4Y+FJrgvb;3Zj}_j#{;WX)1)-kPfYtF_o|&BQ*Va>Q-oZ4ruT_<*hX$+_JZ zNBh#Hsb}o&ZGGSV(e8Yv@&(J|p|_Rxv^sqZ(oMW@u8iF{YFq?SPH{vIZ*+4R`wtZ= M*_xDNxpn7%0Kuvi$p8QV literal 0 HcmV?d00001 diff --git a/Signal/Icons/AppIcon60x60.png b/Signal/Icons/AppIcon60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..d262571937866ff70d139d68df731b2584d4244a GIT binary patch literal 7638 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 literal 0 HcmV?d00001 diff --git a/Signal/Icons/AppIcon60x60@2x.png b/Signal/Icons/AppIcon60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..65bfe9b2f2c80bb4f6af42d09a9a398a7ee8663e GIT binary patch literal 11664 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 literal 0 HcmV?d00001 diff --git a/Signal/Icons/AppIcon76x76.png b/Signal/Icons/AppIcon76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..9bbaa92b86c9f402ba30772ec5e5e2df771a9b98 GIT binary patch literal 8608 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/DefaultContactImage.png b/Signal/Images/DefaultContactImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9b5584f6cbb2a17c00cca2039959ed3010bd7b GIT binary patch literal 258589 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

qBs@ZN zFsrcBoh&DN%{akdCCrR=n&Hk8#EN6mifX4?9I{0Y5|$L-DJ$%15Ynw1pHPNfU%Oac-N ze$a|2;!m;(1Sw7(btcl}c{bLTOgj@5V6Zcnf=D+MFCVqp$SS6z5Eh)KVn*XkxiBG$ zW)_xAly9xK<77P*%D^{CY6Oe|#qVOy&NmYR2h)Rq(O@##spmdf5R*TxT7v(pfBBdH zpaEH-4QZZZqA+Jp%$o*Yj^Cy5$4^0k!dCKMda8dA<; zI*L_C?b?II!7dmrrAClN8A`JIury!>05=@9TouE~nE*!bn7l4E4O4Nv-QKn>9&zYU z(%RZQfSP4M^KHvE_dqG7)TMbTGx2F?pPC@ukFiM+v+w=5#k_CZ`_H3q?{h89G`BEp zoNdc;Z+qB-M>#YnaL*dBIXe`Yj`G~@IcaWIi)KPNjqKgj*h+4r*p>UVI&}$ov5C-O z6+K}47_KriM|L}jmoZXQrc~&v$sCc@7^e;_m{YQhexw~^$7A+O9`JRL6~~yh#2i`{ z@4)r634)QwP+>BNBARCoOuDm+?%5qa*EG$qi^sxQ8TYs>ybrSl8Y2!YXZT8>Sd{Hr`so|pn;wPlN^EB*j>d2A|iT>vf+KbRaw7;cdz75zdmwy%Y{nLL*sah z$B}d7F`4wF=dv8LdJaK3d(Kl{uicVhrJrsFk`7v7P9-xj-HPgWos!gO)(WOXgwbL890rx_j3mP&^bg>VwcN+f2C*-8yggrg}?Qdnq1 zsJ57O*@__K-uQeJ`7P2%F;JX2A*F%Gl1l=$P*X-I+?vt~Jdz7NQq_AH2$pm6%%%)+ zr3Rb`sM^e|26;?T<kw=}EyB&- zN-DG^Q9P%CVV02kUxuN`;7Lf2_d@d`$s$Mc9l~m2-KsrG( z0l8DCREQ>pp%gO|3B^uuhy_T4(?UhWVv?t(ica?8BXI2_?m(1q3YY^?WRge^x`Sb7 z$`YSZM961qTLSVkl+Aay6%vpkIy@~5#eh_Z_KdmF{6PHxq~vZo7oSseZAwg{JP!t9 zT1=3#V#-t$_+a|;@aQ^IkDmR++1LNezr4P@{O-q6{1TBI!7@1^pXgADre<+|@c{jT zHm|i#O~EGz(11>?LV$jG+7{OKdbxe>?VV2yQKf573h;?emMS@uSf~LtAZZ%d=L1tU z1!Wiz8(mF?}9dGzB&ArbtOrZj8cOV9U!|>dJB@F2#o|G@x*+M-EpIshr4Z05{Hq zi75~cI_;QqLJVbO0;SlF*<>^3J;r?GK8`;3Qetd5k!|)HWz<5+y>J&3KpY(jWTG%B zF>2PpEMX>7q0LxSQW$1l7L?@{%gSxXrRliO`s%$H@13+~vqj|9Fe1%?w*259dWW1y zZQF6`N>{*AwZ}1dxRz~B_5ObUIlsov>27f^h2}5Y*N(_#Epsf7N8gWq%e27fhFX>@ zdHLN7=>7A5@E1S*|Nfh?T#onndOzIXt}i1J(4+>l$>vNI65s#`YwleWDY$#c90i_= zN|;II!hq3Q5t4jX2|;O{a3FJJfMCQ_)-ZA=w zU_k5OtUjaEX$Z1*n?lEA#*i}tp^h9MYg~X8Y&;W8$~Qxj8NB-0$o~dOAJ0ZA&?BNC z1tO`WLCrE?&P=ZtSW4#ZZs+2pGsE~Su#me(Vu!cHJb%syiT@p5uk$jA0g`F-RFu=r zzgboph$znDCsRVO$}Ci{#M2xr z3nh(a7MYyR5&$yH>ID7Q2+VJNJwE#SwJq0A7aWr%RAhEtP@)92n2lT(>nR3dl{Tx@ z&~YiHXI8MnClV;cn0V>`c-9+`Tyw_cQfph8DOZ^oknE_XZ~*n>w5>q{5G@Ne-4)ZD zRU?s0x%Bz)f^qM(CL6OHET4HRch^-$I zel|aos*DscO$%)z8OTch+Sh|hS2e*37*ILEgHq|zLIjI_>j()FHZ?ITN;1!I%_U26 zU^or7AXA*@-rrQzVdf6&QJdLBU16cV&?V(q=>w;Yj}}$Mse+k%Yi*8~%L|N83_@o* zanjDD{!_QYqNMxco;VlFvxv~^w|1L>qBD#EbdIVEvoNuk!Vv%!u+h=7hcHREgl49Z zgbT)!rCJ%ZJWt5CLjMos;gF$7g{xU25fdy-m?b%3E>xtSn}jp|0pj^#EN(~?cv2QH zH#Z^EW#2THr?Tr;4pbJ(3}>B6KLIZq9NwPZSorB8(zCA*$^=gf!j%%;7?L|Y5r%5+ zse>onf^U8O(>)6jc4p82Dv)~SO+K}%Fg;_M&)96Hv%m(jFwFb|-2dq7fsela&wsEh z8q9*#ury#`U(f~z)au+p92{$wvN8^NLv&Fnf+k{d7p$R5Z!eEVE_TiP<@x(0Wx(v_9M30&%h`5r8H~ny1sl` zd*^%{eal_>*yr5y%MYI-{O#?ReS7HZ&tJbj?tL6b-$9KIa=?0MZYYErFBdv4Y3==h z`q+QjA8xn%4Q{(#^%(QW_mCmwYhg8D`xZd=YlZpvlK|nReA2~$UTkEA_BkyFuYTLQ zeX5VG+lxPL2WG^`_^}=lIb}bt`__GF7$ZwjPc#!@rFu6l!5-rvN+5bPwzTrnw7y()Fhit0;;rp^hq)$olMdRYXTOH|)teZ7xz$A&?Du0gYge=P zdvv#I=11WE^W5X!#{+R3T8!LJm$XD3AR&g(`;3W8@d>r(@`D{l2uF%YveUeIL-R_S zvX(M5Dr=7e(O@84qvu-6Dl1>MpZE0b)1TE}z8?K}91}~`96z@of*_R0UD-NdjMIrX z0Me?Lkpmg5Ft}@+J})QLR%&;)C1Ky>&);&-&+@s?p+rf<%nl<;Gx@^Y=_{4R%ut;= z0qC)Ckzh~_goeX=}Q<(=LzlY^z6mdG|4;*`qh!=RKh4k{>5q@BRE)gpjYeHJFd>`$

X!P~{Yu8!SWYGAqmvagaip67d0{ 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

^02#R#w0%~{#BthGos zxJeOdU>1qkSWaWDG?&TENGTF^TLqn!(V!6a{DcP$b7873AwC>2?a>E`ULU(hJ7Y-nhCTKD#@gjzKSxGYraH=#7oA+RliX7V1ZzWsZ^U-h1Z!`7i%< z_WXDh+o*Xw2K&c8V$!bAG;R!jo70K=C{5o4`*zxo?a_5PvXq>+HY>WUHk=*x%8#%4 zlEPbgbh}kO5`IiSrjz`;?RcN%b<>NTxw)CHIJWAwRq+;f22%{pP|x4S!f4IxHRnh8 z)+w=PuZntg9lVB4PrF&Ko+CbU`^dH@gnR z=G#YQys;D_OT96wgd41L?r)bEhh46k*Goa@?78>%Q}s<}O`Ns07O%YQu=aBG%=3lU zD7@Wviyzr(Z8O|pKkQzSn^X*&=vM4v6{w^2F{b+u+qjgM7x%Z@OMUsW&j;(d=L>4I zz0B~v!fUs;3j=YI1U+)mWs31LQ_K%&G`9+y5e-FByEEmS3sE@FB_9HlMkL{;;VfENI>iQHyHF?WqLfwkjddC-f3*g#Yu!1N z85Aw0oqocvNUO!!0Gd;z7b=l~&m_+zN+O6{AMz2@)W&=O;3Nr}4U+{*t!F3YAp(-J zR3m^2n2BGu8eW6u1zVM(kaU(c3}fNaFIc zV1<>ZRyS#x@f_d?Kq?}k=Tl6WG?}&xdJW5pbfy)BSFIlmgs+0Fn^wh1P%(1@l}TAC z#YzuVusm5tD2ZI};IT$G&qZ@d4u#Q2D)g)KQY(r>3c0Q!QcxwG5!w{RV8scu@|!M| z(TgwpivKVF{vZF%mwMe^FB@iNxQ=F%)360`{O>{G5>V5FMglCy`N<(y6|z&-O3hhQ>Cjs&it6g ztORQC<<@%GTjhC<81l>JkIuil^aBl*7nYa8?OJ<#V|UQ*3hMZ4Qqa}w_Dk)S@CLTU=EIN z;wlL=p<9_~A>0bawr{v@fAlO@Wym`aVD#(ddPBcp-p9~+f64w4@rv#*X`y^^F((_;-KYaoxAe#Jt;#k5=-Bi@&}8@qhT^ z%OBqU@ULolY4K(EFXiorx9ipU!?s-zKW*o0*>@kMeXE<^=603cepXyPj^0XtyZ&AF zkuy<@g({a>>&)&wK3ZP#d>gjYP7dHCY-4%jYqjlic`g33m0Q;~P(S9FsyX}7UQwp+ zuX_vDQt0RXcp!>Y=>~VW&Vri7Hm;?Ja>fZvBH<_n7AjzR^D(gd$zoAe*dwiAJ`s=M zIEno19m;tQRDzW(wSn=pWE2x;C^xdhH-v3#`{gTQ7wq=D!I?B_Dgty~h1*`z|9vKM3n8I zpOw!l3mLR&24TjkBp@;~-3lTJV;Y`z0EQ*Byo0LGX*JD#T}iRQNmQ9^y$_)nJglbq zTA{4}d8OQ+99W-<9$88v3X;UY^F%+#2W>a;^?k|>Joa&~v!!iM+=!TpRnh3G z&iUgWU-dT5Y_&}J9j|6QdweRiY}*QeN1sFh$C(>APp{VYO5E@<sE+v}L+ zGT%#M%A#Ym7fLz5Ne95BePDhw1K*>x+0!|KmB2^0Rn^#W4{lao1nnidw!QdEjC;ow z?}Cw5*@{}>IQ*QFDPseqShO`dZP-48Z$;Md@ye@GlafnzJqQoh3DD$BR*0*M*ho*PMKRN zcJcC~u^No$X~uAbTd{^mbyl}p3eo1Md$n!+aP7y%(+mIn(?{QaVH@bLmYti-%_LDk z)ckt6$KCsm`{>tyDg9@Bl*CVCR@epGS9?T@(}xM!Ad6QeET;9tUp782Rvl+o$()b# zL9mF8b&5os>f{4NJ(njsss3H_ZC%i(Sz3K^Kg9}E!Ung@(Z3h>Im2f!1{wONj#U8E z3=)%ba8~*hv6DFASC6?d5$jSfD6!D+&ueVJI^Fav&2zPDx64y0P1Hhy(X*dftAB?# zt~_DyOT4{9*cf+skS820ClspI&ri)z2U3^-vo+GFQZ>K!iAa ztj&R5Ct`B6XGKmnggSGS(4rTl!HrhUYaeTOxP-@HN_%=vev1_V3)L8isuAi40aJJ& zNIzvAjf~&~VDLmbEGa8QnTL3UoXn{Ura61CINM}S%fxf;VlK#v`~Um@{_p?IKi13T zwrw?)`IyB$nBMkcew+^g%vn}vq?H;msy87@5paosS&lQ{h>5_UJklf2aUS!8>h;>( zZO{w+?C|&dATPD80}%Nh9-|7&CVrXBX(-M%jG& zE?3!0o2&?#Vx%mIpnHRomLpfcdqG9#}WMv~qF{yy%=uQ|p8 z-1+Fk%C=uT0o1Z@?RENN&%In~tMxdK>>ncq^=T-*YrCqbNJ4pIb#7$Rjos?;(e7U( z^=@o{F>9GYSsq=;9J_QWX*b_4^-KHma&4ENE|=PV`m&Q(Vc)m%!|SbW8~1X(yn3rv zuBhdQw;%uh%Uk8>9sl)Xp8cez<>Slc`gXhiaQlP(wB2yoF0}~d+G<7AxzAGC?(J;Y zx9gW5zr4J@wCnZd<@)!xm)%%){eQ3f-v@q*mn`>aDtp;Zj7-_=)9lg8D~*pZo7^rh z*mf`U+hA~mH@h75F;C0+-VyonIN7C4wd^ccZu=Z|j$dMa;4qSnK9vI_N5s&2)!zyQ z&(UE}C+0FPL9`KY$`XayoETJ6R5p33US^Ti!rW6X5EL|%BX|bZA%g8~H z8iryPSR^t(+(-pD!J3m&1X9VukX)-%S&)#)XbYQTB%x_u02#w9Q3*7AmYH za`R$Xw%kY*n%qRCR{CY_ZncC`fCp)?Al6P75YpDTFyXmSms%HpDI1cCKs6YD?Na3m z*-*xO-s(`8PgD~H&tR!(v<2iu3e5+6#op7G2SUh=z_3yQa(4G(Pz9$~Gv8-U^L^yl zwmq$79$PKhRcf1&UPrC_%t0IbR!b3@(d1M(I98{+DxqY53E*A z;&Y4XltYcuqW>Ty%}^DprIF9ICMYngp?DH5wY8BDpk#`*UQxyDp5JY(LJoWqIo$wr z+Ri~&VK>9BB1OBdre1o?b+B9`{+S!6X>37~?>a@v|#S)@Wib6JJS zo;jkz-}rLBZ+oAvG70JOpiIx@asWU_PA)zJQX*}p&F|rQnZZ-B>IDP`r$k>Cip+#n z*YoGpV@2@Sdm2QXbIalX=|B9tfBpCE%l=w+pA(o__U21->&=lfqk3_#UK+fX4AtgK z%jmU;U~_shs!&p+ZB1z`LDuc!hz!pJy%d;Gk1_lG=^Xpv?I(HdkK-{OAK!Wld1*pr z+FyS8`t{Mr@l`#4xgYP3{N-Qn$NSG8$G0wiwd%PYI>s^YN5&je+HBsZms$s|QofXN zMsc@Tc?HnTSj@n(tH;Hgov{HV(@U6TZDyz)?QI`sSEB5S1571yHZs`CXA(D9qwL0T zURYUzqm&ql{xMQUHcw^QJs#)m$vrzY2lfOir`lUtHwV8e4@hKA^vsw!C0wHqo0qJr zb-UUdtvOh>%Erdaj8yV$!pB?2iF&EEfLoIpk*HH73(>sR7yII0wp+`WwioT9d%0cf zt*KQU=T~LT5uyEu?XnfTz3tpDKiSoJ89l*q$GJVwEMIR|zxnmc>*aE3+hz0FOD)Li zMoaUO4zVEes_VS$Z(H@3`hxv(3-fkkyk5Lyvfa+?)jn|S>A1~$yX-G)=U%VjkBV*f zd6vFh4lQrz<(u6ZmvPR{`!TYAjE_0z7$ePL(e~G!zF&~>7~}Cc7%T0v?7Fl}8L@Ad zy|u>bWe&`kIi8U|XSzWkoSaV!6f89BG-GxXdP|Ga8JCF(2v##4mJ3GXgId?Cq7)H@ zK%r17Wrc1mtVRhkRQ~?B7a_@dl_Hm`!fT<7#zrBXUe^wQPKsns(P+;wpeoQ1iJm06BC?nPSPGk^gf26a(UvxjfK7qQQl66T@0yXB7zt$P=~7*@7D`3( zprhmScWA*X4b^vCJ z^Slx=23yd8UzyBYaNG5Mr8SYWr8yUkptuZzNw_FJ?K6Uk1yZs0po_5gghfM=L!{Q3c_X3j2pj%k-|#93A8hq-aF!tt(TKV}!fZA$@joI)L$Yj=FWCyYcs z@G#G~Y8Z9u`mauZ&vUHLF@F8f=*2$1jy)59itW1jIA^U6v$M}W!Z$OL#WTT8wVax3 zlTRdK6hOYu2lrQf+_9gGI92U2=T#SYHIB6ABVuN@{$Um@EzXWOgrwYphm|iSlzzs!g?_IZ3r~sk^CqK$J-lbWySkA90 zJIK9%-5BS~UI1S=EEJTz)f+boxwkK3%fjNftCxCPi?Zt_pxJQE%6w_0XHGzOP`c~m z&cV0b&Mzj?E^j3Z6M(wAI&Sbf;hgT?xeZ%<&0X5r_O!oczd1W{dymd?RtF) z)fc?f|4mqkP_;g@HsA!xXJqs_;|%B=@pAvrmsdiG{`pv1g;ib3 z$jI=m00gUSBC9ZgB1WbsR78Qp7C4PFI8~)6t{w|bJwS6*BPMZ~oK>`FUNdWq?{$4h zAx15TwBPuSz9)c^q6bhwaHZo*$S3f;n5}ueYu>Cu6(bf=$8$Anf{br2FF)Bc+kHSo9p zr4-I!7THuzPFS&=K?a6UN+x8i;v~v?J2HKe;(Fa+-TRj^FDeUPuaaqX0|_r!-o=O_ z9Hns_Pr51!AQ$Mq))81w$5Om*;gy+*Om~NOWIZPBgt5amjnEh&cDhLukKiLd;y6UK zuMwf+-sgwR=9zyQXIR`1w#yh0Gj&&t;Kt*l;|S|ne?Cf|IfcFY9Mg(Lrlb_yQDwDG z48Ar+R*2j()p8h0k}54YZ5I#h7aS{j8)mk302hlj{j`}iXQ{aBW%RDdU*Ja`2M~@P ztR*GRIRPbfoFUjt=^uFke5z3KY<7b=hw=y^gq{&0*J;R_Bk4u2ZztWr)O$X@<^3D) z5z{(421eeM9TV|w-kZcR)r!osGB3AX`(6oMw^x1CLjBNQF5av%OT472)%HVa7j4(p z_LrtwSMjif`q%q8j{M8Z-Cq4|D?hcD=Kl72Etl6FM&Q&qG1V`v78n8@OxobZJZ|l3 zELN^1%T{u3m(Km~-rjDP{mW~+)%H^BwfgOxe$TVP#--e9>D!lL?R>p%zFXU-TF>*) zi7wg%94N3_>h9(C=6;%IVxIlz_mA^Kb5@p(2EJ^U7cY&U9oG(X$_;cxsL%6bOn)L1 znqY8x=t<;$mNo&2JSOpwJQc~g-n-?R#dV@J$e<~6s2DLSY&`ks zScCrl3~-8ogQ?=fNDl^UE=;*UBRcul_~4~IjJYBz5C@mQbX3Ba?UZd#)Jjg;WTd-| znH3g8r7$}Pnd3V!Sy0A(4!VPMG|I5NXwO7WN&95rBE|b)OfDw1g<-l$Y`=>u{*ZmJKTZBv&T}O!yt02edsjM+VQyF!3q&(6Uj3g#}(ag$!ET;9S|s+&t& zW#t5L$7@AjKhwL(sGUgQ!X>e+TkG5KXk*%ttR0UsqGX2Q^^s0-!)v|1ZC9_i`sIA9 z^|F^QKYY3D`*qvjUN7ysec9W!R(sog`s?LZ*tB0?Udl_^uZ7!oeQ9>N{P4rJbv8SL z=lsZtkG^fF*Ydi3kzID|&2N{B%yK81my&b%ezCrn;`uOGzuT_7v^MQ}-Bhx{bKcT9R2L~{29koFLn-Ol;kc>pNEdjv~!V^fZ~J!GBg7k z(QT;QdLLd9^U>?Y%a(zGV;nQbyytMBD>jnCJ>z^ZFao}lPf0fnm}{#jqs?DADJ%XG z17Q^TNy~x|GYjWnQ6Yq8oWV79QKA5|RI@-@CTj|8Bb)W13ucmYnNW#92qk^4Y@x+^z|asED(F+( z5CgW!Yu%e$*jnvUG%J>=BtmA!q}GBED4WPNyVoadtRR7ET=r5DDMf^Yo+_4>DOho{ z+{{IjfsjgZ|I9_L?W;?UQ~=CSS{VIlAAr&lfNYvvrWK&9jg@PbVO$`|=bI0~)MvJ( zCQ}s0ub4?CwE6&CP2;(~UoMLvWKPHvL@J-6uJ1j{l_pz%W;`VWE`}*68B+AbBHTmR z!~_OGSWV8EFjJ`7v^i(lp098VqqaP>pG@PKa8jZpf|@W2CLHqcCDdgy34e}#{?mW> zcmMkD%j>0GFRhc4kM{V(sPMR-bAt5Jroz0A(Wfwp3578Biaw17`KMptuijMxk2~nk zaS?FFkYw()?wKQ!_m6MA!yPnSuy>!|?uQs+`8X#GfBOIY`QzU1zx>yX_~8ESA&fIw zyx`m1W}fqG0lphgx`w_xZ+q`y;q+6(pJEqW+H{!qQgBS)M%RhlOk+$$?TqR4XawLh zj+zvb8Rd1;W<2U`=Rr=UX3P_3f$zfo^ot&6>{Bg3%kyf-M-;%`_&!kuR>no=gRQkW zr<i-#V+{Q2Y2`@`Hp*TVsEPxRC{&Ol_D zXEm{1PMrgap?h{5$-wTO^mi~c*gHeM5v66Egbb0e8iqIQUEnSEp_mMDfWv`nHdSG8}*LnywseVbOY7!w?h4RB!?O4q4e;gZ!nZ*y$)?r$|HtJjDWLpg`D}oxU?z z<;Lyh#bn7z8bNcD(u=J_cN1YO@}%NP8?peh>N#0oD(vqmqm*LVt^*MPGe=C7^|}_t z3fxU8`;CK;pBqc_^;l-u7;~*qxxBHKNMDUoZbyQnCXNYO`F4j98P?>FrXGUbm9rzLiL{>(g@gH@{LkV(#u&dJvoN z7qKX&aEGdtirx>eyz&~1$N3Sew++$j@jie4`Ooji`};3t_K)N7F%tjfmuR?eyGMDY z+d1E*n4{hquc!ty+RW^|2O~3C5DO(JnOud&(psby)6BzCI1xQG*+oj*ns1@|@e%-ak73(9ZYVk+D^~9|~?H-|YRa3)%L9 z8CL3MWW#abRxy_PG;?gGw}P{j-O}yFG+%8yx3-lBoYwPRWSUNvt8MM=$Cs_XUG}}b z?bq$Peev!3<>j)~+wF(jo7+|{^@lIG@KxK(-@mlF@7{j+TwwsyC})Fu;WA zD9`pRB}7q8nQ}A*Lfy5~nPBl%q*~*gl^nUKOgspOU-@x@6y?d`IrTJ_C?Q2KxVV)F!;-XEEW&6~Ni7v1*U@diyVrj&-98_0 zaM6+if}+8bsVwr94l076;EG`dV42}%Em%!bDYo7sxh6bIeuEgug_bOcAOZ{XE7Cj* zVJKt0M1IF4n3@;-+)$<;M}9Rg(WBNnMkmnPwq6lN+c3^v3uo`Em}JEN-Y=5u;0INq z$G&fx5=`1-j$j`oaIQQ7k-RuN%gr|&@T2?3BQoC;N+OfW?AWsWuv>ndEfW3Ma&cOr z2jjFbz(|KL%PQp!TQKCRZr&oSIWCizWIQx^#vH=|imlZ`{9%90F|Pa;V{9=m@`fK? zYB*)OiXX1H#9c5e<3O8P_c%kk=Zky&+WA)Q_h^UQ48|1ruGebVs`|82N&RvjJ=1o4 zNO)i;MC71-tF0=^a^slp+rtOZw-MQ;8>f-HdbQUdHZRxmhiTXSdfV&|ms)?=%>35g z+AG+8xY+I5X8e?I<>jWc6xx>`-i&A*m;C(Z*+vzl<>O<5m_C0gI);@C&+=ySN<+Im zzA$f&v)oeHKCf!if2z-=L!1#0e<6R}BHi=X$zIX$U3BNA^2N^=+{}R1QNsWDugcrs zxAyginQp=OnLWQ=e>r2Kw|Q>jaVhC1WF>VyLKFRnvA%RmE3C1tPT>xy!4~AZnog3O*AM$-l!W;SHkf+Wb~;ffdy^{ESpwK$CzH($MLxU$11lg7 zeqywLMJz<{5Yuo1a^;2)IEe`>><^5`_(WbRi@`h{HdLX_lGX~{RxbgpG8vQdxu#B3ktimUs?(Z&b6b@;ED%%@ zH8Nu`H4Vk+^zcF}tHL#%e?8AI`wifMFr!=nj9MU@feD#eFB+fRySYA!l)3-aGmgNF zOhs^f-_az3R5dDO%0QuPN6N{C?e{55jEc{SFiHQ<|Kb1qKYZbS{eyno%fp>*^1eU* zj=cNU*dHjBX0U2ztXn-!wXHz#x)!$oyYw<(K=%*JIp%PJF!|$JhRsL*K?_w|-17I70bzszaCX?)gF&IFW9i z7Nt~YX)Gc(R+%-rmm=0guR7t;(^Obc+5$&_>AD|vYjtR=)lR=u;$ma{d{2bubsirx zbA(Umio=pet#OW*(v#&@@Ttv9BuzWAN6OXEtr_9Rl~uU@TS{&M)G%q{;S+Xe2- zYerwPUv-Lay_&wBbzJV_9vIEuj{hB}DU#5Nidfx{#zDiTLX&?>l$ii)i(WcyTYHR2Q zvngztDaR2}TYMd<5j8Qxu=T>4iEM0>G|w=@nDONC_Vk=t5Gq#EhOniqk`vMB0Tr)( zc=K7DX&(;hwLpWcWHBn-#9BocoK!juNHjWjt&@nxlH_Gu()yN2uCk(Mqsek@Ou|EB z=4v8}?zpl*fU|P*%7u?NW37(l_k?f8nk5~GoQ)PG@t{Ghi@Qk(RhZT@TiSpMtb;3( zNtt}7J!CJ8!7_l>=GOL7D`>@Ck`|%enIS?SGcB8#WD-r7_FTj+`qpX`f4Y2(1Tr!v z#mO8p8fXIOreX#f=%#1y2S042AQ|(C2W-=<1XC$Ow16Z_qPinO3yZK~SZjVq)w9d> zDcJ|*PmDlFOTbye^K!+c5>gKH0@$hwBM_J_BrMfwg@TgnWq3l!iMDu~M2Ik~5-u~*<@9(e zQSkr%|ME9~^RGT%Zr4}Kljl4~tCzSQG=fwNS~0R>C8;xGuoRT`y)gEd7LwJWnfV0X z#VFXO%(<9!9DUlqrFeDBh~|@4>vr)!-pAklc+5Y1{W<*SbE3z2e}5cr zKb~8T_pQXkx4W2wBdA17UCp)ibINF|Z>zw4v*rbbtr&`m%q}B>S z)iIKgcVKYzP+*E_S+D5bO;-7Bl z-(D{G+aG@xYeawd+_llLho$tbm60bBlI~&_D~W82PZbCuF(!SUBJR3aK`g0xHUw%m~5RXtud=Gtvq{ZhG!WYcTy;T#}(= zhPnjLG0iMnQ%_n43S(jhoJcKeTB(+q#R)JcSexaE^yR2;@GQnGrPR6^N~Q>S^;`vY zT*w+W(V#(AJrlaBt9MvImDQwM#N0%nIEJw=iEwd3^}N%3oY5 zgE>RF?l%x1gVH6+VAZg?pjQr-IAVDovrgT9RT$Ph>~W=OwM z?LRuF770I73ovA4POSC4W~!7V&9H*jb;&`c`74Bq4GvS9HVgDNyiahykNV0p>)w^L z!u~jv8_3z|wGv4(bpp(Qz<$CF5xQK%ZKjogU6e-~eDpChu@!^2&D{2#G$3b;-Z6vs zKlQW!{PUl`e);!59rv&4p?8dXzrXRE^?M)C z=A}Hj*+mH$4LwucYdtWm>}PN*ZN8k_yv<^>$fkTsrJk8v)Hvt$R;;0C7LCQ27+<=x zgr+XrVE3(!yY{^tZ|BR^4uMyGdyCJX_L9)X?6i@eEAKIa?r#S!7YOa@j}Tv|Q{L_(;$r8E|~yJIxJ#tUmL^U-C0QooF7kq6FJwm6%Cp-U> z^T<{n83rXVdaqSrMVtU-?Ki2|?5ovzNaX^m0bM=7{Q+TPDS@S(c5*%k8 z=KJq{Kz;e`pYqN>|J}QvvfrIy*DuE`?t7-$>jPic)KQp9qOXxDL1ozMoMUhI$*s&D zWL7JWn7)d?+4G*mtdfY)q758z!I9NutW- zDPOF1X(JwBq83bH^+psj3pAZ7zG(v|<+)UM80B7RsSz#=M?LXab1oL1AIOntV--M{ zp<5zQ3WKV}0ZPl$Jg^wn0H>A^K&WRQNp{G$0 zQ}E2i|8yaLLHg#h$ERdzg|XCKh|ElviAb6$L|Y;yp9Ni$Y!c^+lZh#q3G!gNY-(`f z;pMja%mgLPC1tpf28V^7O#amkK0~Uu>V%(_f?QvFN|4)f`->nZiZP(+u*sB+Pcp{f zw5ONpDRU*{&WPs~L7{(Sw0}~nNlw|uJYf|PvV>r^sO-NkMIgN_0H^bKw9nqpd3}f1 z%a^SvX8H>=0_{&n)(9LD$X*Za(74@8N$5H@?8bz17G z5-l^q>66heqs_)zNfX1fj+yLHD1gz+lP4iNM+xR7t@n~8i;FhLthV_TrI&ii$5CE1 z=ENz?lQNV`%x|-0rM0@lTnK)?+WS4Wo4+5rU0W84UY&Oz}@g835 z%H(5RS6^IwI@m<+H@$c81^eTA?s|D)!pm#(vhUSdiMAKsCBNJ@!;H9F*QkHxXy`|YOXD?k*!G&mZl#vrVoP3bn;s#%U+Y!pNZ&0F z*{h{VVg3lZ22sxUx9d)r8x+4<(dbBC#h?gY4IM0m6GsBT)$GsmPoQ+JE+^e?4!HxA%7)?`L<+JGL%-zNcAyrEi|NsXw%Cz)Z?2aL?OC-7%-_jM18% zsX#^(Xu<~XtAsKa&=3+&VS{qOmPyx8fNH?R0c&f(Mq(O?a0)#0A8ZVBMaWk&J6$a_ z3d<}5kYh7WG(nrHXUM->2}v}H5M~$4^NV4E{xrGI%C)m(bANVM0YI%JO_DdR#feC3 zm^~9=2mu>o(59>+6J-a8jD<)n8*;)L^ov9;n8_ORlo^hv36=GnM?YVzgwrKvq#509 zo$bFmeOQGDaWRu2ph*>26T?$v{JP^oV2Xw=79(S*I_J|M|LZ@6kD~?>v8*dkA9?^O zRBEip(6X{K{|WQ=uaC(|(z6md{*lfOi7^vkL7C47ZIUzKp=6p6jZpZ>P*i6;@%D%{ z)01#ROnJ3m>HkWDa{aJGkq5hHg=@-SE=>MqcEaGcgfduR%)j_Kq@c{rdSrF{Iy@(^ z3coJMElL}30xX67RBIan z`U$Anw2|MgTOD&BO)b71??=x5HD%vq2)pcm&a4Aoaenz$NL;V2+y2-8{N?_q|L@OV z$b0LNo=wpEw88BNC*Yy~N|5Tb$?8mvctllTo~8^afyt zpPJ~dq{C{Rz^7O5GNoQ${K(SIeq1s7D6?dS**0b`*IO?G=60A>s$*Q6Gd0HLvU#-t zF9d}d+CAP4`{uos?diP*0*9f2yiwNz8AaPv61wGaGIO_`*U#p^X?|_B6ersC!q)1Q z23$|R(^(9KQZ1WNdo9J;$F{frr?)ScQ=|4Pi|MY|uXeZgV#EAp->vRH{9wM{S~2eB z%B_R9%5SZq&&$pF{gJhtk9w=nDNCsEDY?RUlmNz(@pd+eT}TjIfqAsaEu!&2!YM)2Dj4F|9<5vbD-6UE-Y7K&-y)e4p_$ z(wdQ0>cW56*42eVSw9mcTiqQRC2&l*Frboi%;KAQEXQ2~t+DhrFjv#7dx1q|30WC( zBUW$h$C(9!sEwnGChyDjMnA!8ODzfWWYfOV6OO5@^b2B}_oxBSh zAl883+lo-8yUmd<8_Gt{*-A~gGfQ)hP^0D<6=y%& z>8ygDl&oYa-)%(eSuGF37Lp70bPki5(_#F$NvuP5mAJ2JStA8eUeRNEmhWv3f^3%N z7#S%mmf^N#OnOoUGL(c&b=9P+zzA&-k9bY94Ub2@)l6=~OR+fsvTaL>MMj@fy7;GSaBi-udb*sPIKh@W!0#%(pl98K?mztQoZev?dj+5gAVu;%S z($zhIDQ7=fvrOzrFsA_sno8M#B$^iyaGMVESsEE&B88oV8CMN&a&D^D4xd| zk${#J#Eicx649h4P{Q58iPKeKPSi9~QT0R=(0=(zsAPevm%`bQ!M5D;t73~EO?$9) z5s6$vwveH4gSSAYEdU*ntAU2G2m~_?iSL@#binCB7cLzPCyFICF*2E?=#pmQgWA7(h{RldSY#DF2vYzu^JE5}KkfI2 zgVBs`&$Jpv3I1I}gSc9xO1GFuvOzG<>@0Fh#4I5Ja}D(pG*tw4GUq+V78<46q)RX} z)s4Ugdn~9%Y4~_roy49x=Nat+>LNXe?=Jf$9E4Ntv9Vom7tF`|CD{J@H=oXSk59e# zjOZDqZbjL)&8)nk`dhU)|91TR|M~lW`^T?uZ~61&4?A`uHSRe-(POL6ExBfC&6iin zFMXa{aXb4YkUDO=4&Gk3IaG_+x7>bnd-UWRi!wh~eUYDfDGo*aun#p2+Bf2p9wcYG z*m>Tyi{oAWw(-61uPwN7V(-{)?Oj|1J-u!u^v;(n-T)13Qzgfv&vLUb-}1$vO^+Xs z*NtzVYENEEHS!9uwsOf~5HoibUJCb%UAEWDSs&%LU3=xHrt9vj)U5G_8`=}iwO!C& z`J1#mFo6r~)aTFke!u-6K0kDR(j^JrE}u&?(k_KZ;H8#t<7KP76vDyH@;=MO#^c-l z6=hl;_ebTGIKOJ%-b$aR9oymKVmEt>avkr%ZOeWs_HMTqX0nCdO8oS(ryBNRLg|m! zdi=^$yB`zz`~Nj+{~ABv-^%s1 z-YEN75qh`QFi!~c9^gB-%*O%^#5zz7ZNm(|TyoxpXGeC-kpY7RPr!*OG^8~MBBIN; z)6U8^a0naKiEKE>%)`KO3cUw%s@l1sX@{>EVNa9;3!J8Xa<5e$5)((yw(~qYwuY22 zXnPVNbaG3~l`kT;;7Jq}xC&1K1F4dXQAS8<;g!RO$;YlDe45T7{scDyx5nTCO(!falQhuk^@H<1P zSx1IdX~LC3qhWN^0+dP6P=ceN(qP5?zyB})lfU`rpRSkRY+Ht+e7)&`ff!>0X31vQ#NbNy%y{PSUQm7BBY0`Pbhr z-;TF=Ki>a;fBU!ZKlS5|Zf!7QW?DU_K~%cZk%o^AIKYhZz3bHp?$@1TsGdF28s@oM zbY4@?h)-MIhoPS1h}UvFqQcuKTXr`eleZJv<``@R@nE?^=b-95W*MdK=t7s(AP>4h zCvFw9V(^L4ih|k0F8p@rlJJ|of6H6r+;|?iZmv}>^xCdt_KV9{=l8@mt2j_l~)Eh%h&VGK7St=;UU34@oXNQ46?R3R^=zPTi0G)7!VN5QOWv zqg=k~Ayl9^9f=-6ND1iT7r`}$9cB)lZS#UM&v)81<074==E$Y!8aadthxmW+`MQo_y~`ffS8SYvMAf@T1u`6sai5$N|Bq4qMn_i;xB3P7GPG zVC700%PFI~l|hOk9YxKh>1pAaPs|PgDwf;F93H~Rs3y)#<8C6Ew6T0#uyt!yIDKgC z+07IjQnXT@pqpibv@@+PjAPitZIi;1Nt5|AN%!57Z{M45zn8Z>nQxSkf*b}LRAkZ$ zL#&7l?k=}Nm~m-1&!dB+|4;wjfB7%}+Fq{tx?g*Q_|0u@A3KhV@OU1mWIuo>3nBQS zoPoQzt_o2KNC!?rnK^n=idh9^3@Nt##c_a}%u9iL&eHbchG=c8di%m3fBy6T^6&om z|NMXbInN*8tc;W54;klI)|h!P8ce`{FvKN%+K|>7=5@O|Pr?mj$Y0Bh%t-d&OVc?@ z%D)cV%iONkNCoFG+9OX48)MGN!I(YL95LKm&(llg$?Zi!uwniETrFzy0vT?Q;9gcH3UApD&Si`?Otdc71(mmzVvzUq9`2yItd|O`F9r zbC;(4YS)9_z%E)I5BKfRJ(X8wXp~YP$rrm=_GWp{vGaVyxXEQU;dFZ-`{TZuzn^R? z*K)!J3{xdVJbbr!aPqT_yR=xexMy*b>4{df-65D;izG$jlqc(6Xny?u%a8L-=llKZ zrfwJE{Pg&CgkgUX=4>+pkAZfbiJKm0D(5)plen>54&N^Q?56^H*Hr(0Q3eUyOm$ST#3Qw^T6R1s@*#SkLVZ1GNRb>_*zEfi@!JiDDEo)tX1hNQH^R=0vggFb9n2 zv|R85EF}6A3o${r9J0EJld(}{^AaI9E*7+56{0D}ZTsZKu|DCeX5GH;bukUKa%f>> zP-UwJ?@6IteuzMPtQa+?(tuD3UKpE~vZ_kl&Fmv(iuDyWX96hZ;_4tQotbNpmVhO7 zP-Xx(rmP_elbbIdP7zc$rU zNVJ+zVU$dyMRBXeJ)ittU$hIMxllBx#B5F>5G=Ie8CpNfFZ2zip6%p7=5tN*y*}k> zx0DT_8Z0xwTGkb413=I3m9x*Y-su11fAe4btG{YLeEO{GE=4z8tctd%Kemw5>#8;< ze8LLAn=q+{Gf;!@we2A;!b!AM46=MfUhBd&0~T}9v}e&KYAZQ#xEa*nn%}Sg`9rl@1mw}Gr~7D?6Z>{vOG5(;=Byed&k28~ZbhQUEuR_s=I;!4HPmdK6^^Ncv2 zD=6X^#!>}~eRyee?Av~Yvie4=8+oZt-#^d4YT){WZ z3fNYQ`A)|c<+?XBgVl^nsa6{w?_OI4ue1laA-E+D%>DPmKvVO?Xyt7&JQ!VfIifpdMCj zYP0nbx2IG;pH+KH;jvf0cUq#R-p_vj^7rRG-yg?}`jSVt@)!;RUP`zUZ!4>ehn~WNq~$)Rzf*I2NKON#gf7Xt&)ra95afVlWf${ zSG~58o+wWQz-pE#gDNEnvlS@whMa0o87D=~WjsX(DmB4HyR5L8>pDdsHCvgJwHVGS z2jR&9vAUP$q=C5#pFTc!T-kjmJQ1WjlJe(Czeru6I6W=N#K?#xe;Sd6oTw0{p4iA| z7RkS7smL|hW=1Mp%p4{dGayP9N=wVKN3=Y^@YG{Ew!6Y4Hq0SO1 znVFP{gfghebVmjW?aQpW82D6@#R;)`5e6LggqIVvjCg8Xf&jPGPk|~!L57?(*|1P7 zF53stQpgA|_Ut&(Ge5Du!9}+ghAnGYa0)ArQ=qUKoTaXGoJ{3eO7Q)x|Cj%~e)_as z%@Iafzg~7}MGNCsATt|>>h(!3CI*-&n-D}52{d35n{YxFLtd#Q*Sm4NxgRxBq`(#y!+ zK3#H?xv3aR9)_qG-wxjV{?qBoOxiiUFx=#%Q80f$>_5#Ow@-z6E!T@zV$17}%9odR zgV?s$PnTA>)vYz$l=kWuGvD1)APx0WO{m`Nvpd^v`}GHAxYyg|GyPOLdD}J@H**qR zc5Uu4nTWhFlFFX_7eQ{SWj+QRIMf|{A1tO08qp^(HE1dJt66`LHj|c|IXTBnS{|A_ zb+vxLkqYtbX%nQe!*^y+zfXF=Q^xqFT8a16Wl_2dSwD7tP7pCQXtuYR{^y_H`+a_W z`#MJ+wl6VJX4#6H!Rd2=I4@VQVsyft%^d-DZB!n?uHeilP{_BcnafGnVO|>ZBpoh` z1pH(agaJY&;N%n>I0Kw%pzSVtWRz(G!yS`ufC_42%AalJPSOgz6$@u6*ed#ox+l_> z@j4l1ZpI9hjTtA z(9*a>Rs#f|Ok2do@3smOnHZQt8Dv3j?oQE+UTt+It_Ns*B#ANvk>bRh;b06rH?|aH zOgB=b8DwVeB1&=6j0;G=a66E#1JpQDjOE&Yx`vjmbJ_BHs5o+eH>dRltlz3O-p+ z6G)(vt?a8Mr&-Al_@rY3%hs_OX0F&aHgM!w=eMC~<9ko+AqLhPQLzrQGM}BM{&jyxiYg#gDI*^KGk_ zsBV1qHdL{_A4H405Mksp;rA;w;1fg#L?5_e7d&7A z&du?h^4^umcjRH}X?bk<{)lKe4_K-fb)gI6J&sVH8r1u#IHfVDM=EkD__}8WcE+BM zOR?X5zU}|a%Ed{UP7)geOR%T!WfA$N4jBpuF$4Rt$>$S+-(RO z5=u>;wPaX=%(*B1nQ;-B2?h})o$fW6!~lWHfQ2&#f$xFWUp_KE7xIQ<%EbuGiHi%< zF@1rs3~sJDKu2Uk8!&Cm@)+s)fFfZ-szjIT7Yb z771Q}ET~c}mEJ9U@dZ?UAlDNPGj@j-RLqPMQV0r+Ip!SG%ox!^Y-Jfh-4&@>P*!^_ zpTqO_vX7{OL9RNyh_)i1A+n%UuI9mdN_u*u6ESmLbNqY36S!(%Bho-}4A2)whzTY$ zd_D`DK&xE65s@FV5lJVzGN54s6i^V~T{vs5_ov5SMmkN21;JQ8E~i2kW`v3~#EGIX z@M$v=kjThs_7_}xhjPM&TA+X^H(~;YG}%9~e#-S^LXd3ea?RU81uv2ZsZ1G}lz0I$^`iQFM8Hp$Hy zDI>gD2^f$>kcAeu3{BtL+#g_Wb(@)lCNxh;8a;=`AO7fn`ttj~{h$Bc92S%B!TZz^ zOreLJYc3#UsIc$VEss80EfG=N!Q7mpBXRFJWAu?B2Qen!yUMB~12YUj!h6Alt6SkR-T-V1(a3VV<&QkK2m*%I= z3vK4HO*(Tz4yel)mT;^sd74GWj-lB^#mu1M%y1wx?kTZdaP|zDdPE13+l7kyiJRdV zyjTPvzPLF#eLo+6`1!{l-yfoswb|}7DV378`8Kpr?55GU1;T0di1DW*CuBOH>}L=x zSZ_tSL8a!L0hlJ67i`8=j+bIU*UF%0tyj8 zso+Rdqijk|ka38`$r`RjAukH96Eo(5l&8rjay%l{N31n0D-D%Se^zkR1v>_j3Lt2ZanFb zyrIB@l%}JE!43S#>YAWuJIT7WIz{WUMn)09Ra%lj{v*3prdGS;vXZabGJ=U=@WezW zVThVaTWbxY$p~W&J`)a0tS2Or{y+Y=|EItC=iAF~u4tDQkzkU!o`a*q%kq1Au^HNd z0BS;!-b@5~CYfRhoPeheGK8F`yE|4VCmN7I!U3aMa~yFTGktm#D~&qq|Ne(RzCDik z@yoY5|NLcU93JoY*=|igod<%`wX~o@ezYXQ&5GO8mMaRG!bm8Sz-<#@mzpD3oc60^ zN|@%T9kel~F2-TjIAwd+k;6y8ee?<09Nk`9F3 zvE3SbaJPET6Tk%q;IYyt&$FH%*l278Bxi(Ttl!{BCs7?VW+*d!n;+Lpr8F~RBpZ&H zRt>Z+FF4OkYHIR0Ey9z1aNc8HZ!v zucde$)xYlL?sRVw&f!aHyqcZ4Zz!?9zDR8%EAd_IGqO2bJ-l6jqg(^zSx$^iSm4E@ zc*tDc89Xz>1w{_aw~})&_cNb`U_L08HU7nv*s$BF0U!cxc%Aw9Z&@bByr_37V<5z&RrDI2w37qBzV( zHef@du-w{pyV&-d@|#O(oN6mUA)nDlCE zD%Ug|6W(x4_)M0Rxy_N2tT;MRUc5R#i%ft$U(2Lwt1x0Vtr02pweq8ro(OA z%fJ2opZeD?KmBy{*}r!E`B8s5?6bem`orVs1ha6BF&B7_B>C$?gEcz-`Zi?5ZA0g^ z&$O~52hEX`DCj(68dzawMyA6+Pg#8&*eoMC=TLL9@3eUi!T1dOWmp$bD^;~K3P^{wL$GZA1+CmIwTh4vZs&ikvXSD*BXFDiY!kBL<2hD(p$`~P};^}x5UIQGX>ny%BMI!@MMmGclR zR377=+|1C^HsqX^TGus<87P#dSh+C}#o?&Oyr%+FCD38D0`m4G(?sG4qEP{i@k=iu zv?Kv!qAc)PfUS{bFXBcF7-?IxT4^4IyP=5F(Y_a12oSqrOCB(}#T3o}Gb6vx^`=5G z5pL9~3tu<=Uk1{r1wA24bO<(Ek0`)$#L8*s5OPRf#2Ipwq3YG43bC1Gq8LWfiAXEe zvDs>!0qW+cR$pGXIn#}9maAj)pRzzR6PZSN6D9;t&Zdw;7R#AO(kp=!{G(EXMg`0% zBB_jZVlDKeVyOo4EKbRsXClE_kCRG)@;OZUP+S&#A66GctfX;#z3oGCiBR=vXf zzySXP!Crw>nscp>#k3@5KqgJ1XeyF~B3YSoi-E=f%OX9P%5%4A&r{AgXcyI@!+b;^ z5WuYumL*-G0|P!gEglC`VAXvI@TeXdE&_G&jT;_y+YbZzARyxfe5rOC3%^~O11VT3 z*KL!C*8l3_`%WG2QxfzZ%sJ<8{M&`&C;ue-vb9xm?6Fl!2cjbd+&TZ@73||5&OQU^ zc6qS_w#9fXseYBL5Ny=|^~^DY;1$J_0`tL_`6bdd_TnXy#zZY|C$1S2o1R-FK5#j+ zzH84K4_&WS@BITt4IB0dD}@Vc3mQw^N@+&EYRuCvmuW7Ok}#^zJdg1_fq}2rH&_B+ z@%&_qN&&Wz-H!MAx^%d`F^x_419L=vl~Va85UgPPIOPHI=4Qs5uLYLt0S=J!GC$+p zr;z6NEy^Bs&a&~n`LywE?skck3TF8Af$B5yR1 z#PW*V0|F4K`xh_AC8ZM?@jk};yvK31NTuR1ov+niK5uPnKTzK)u5I$?D!=8^SDi?` z^t)YdcDWVKiy3g#`f~Ad22dBlP%*RB!l4+TRC?VCi?)gLV^z%)F!VlD8mG8{fnSJY zb^^a@-Ea&b4Uwq7^u9tiB%G6Zz#F`xoSlwsS;Z13QGy+S+mc8jC~zvNv&oSE2;KFN z5XiVG(YhtU#l+bw@gdvI;EXgW2nV7LQsFHInutP#d z`mcC0<}|YvEQn<91=5bW@X2PjTNcU&c|L!S2d&c`Wz|dlay(>Ov0%#KWCr|KYlZbV z@#6ZQb4BCU1Nv8_)ZeX|@%OK6@ z5>dtcM|mNIM3I(}h_b2>*CCPwvjLqR)zhIAQj9<=%E=)WGl9vmPQ@j1`TzC5{?GsA zU#o7Paozp6d++82B&=`-ye@>%hM6%|TnttUN4KpNx}-p|Yzg0u&5(s++e=#Mvu};u zn&v$ux43s3c_(u5O2_-Jz<7aKjC>VApi{trfv1Re-ZAZU$^PT;|DFz2bTeInEP zYaVAYOPeQ;V}3pIJm=A2<~6bywq~Dd_Fi9hIdv&zFE`R&w%_iTYE5WXoVVs(ypRBD ztJ~|XY@69$q?_mC;#*AmXla+iWA{p4)BqBj0kx1;26Ux0GGEK*92Zs)#Ei$xQcl0b z``aRx&HMXa>z!PyE&EQFA4-Yi`34u8;X#s^;a9b8er=nH{$d-@>t^QWo73pcFoBb{ z)0&x?Z_h>s1M153{)6}HSVeR)byQRTi??)`Juc;?GiRA`mcGchyzH0zxJ$s9DxTHb zrGO4_i^ zq0yJ%kP0%15*(peC_Hfza5i~odiFXw;aMb0AeAKpxh2yeDwp`Tu|Ld z$MVNb5P}WKde0@svOx0Wl(`5PRRa%r*$h3fuauZS@r@{Tv%WPy$EDD(SD6!5!bssC z`98uCM27b8^cIMHasmbp%PB>!s;lSXG?j+xYt8GHnQJ+_7{A}=dZSpTfF33RsYrA) zTeq5Oq#Sm!a3kQw%hRY%l*N}JOmtb8 zO$rH&SSA$~B%zsB3!F#>NmG6L>kVGqM&4;#ql(h3tUU`zjze`PunB?&L>ke+VmLee zR&CuJt609H&?IXMIYfu5DOgRZ^0L=)AD4y`+7p8uZWp}FHjl?!870fl4%DGLc)Soh zX2NPM4~0BMjLjyUwNO@1?8WbzTXQ=F;O!h|the|qZes+H^5cAe8xh}W{{barz=e*D z1;D^;s|trJXLz17PvNY0zG~DFcJ}$5O(bgc@tDZ=skzDgbqhn%DyhJ@ZLikGk~5t9 zb?`A&OY?aas?J9ly-0vw5#Q2Z->0J3X?cA;X}{5H@%=j}dNe+E@$6UUgLXi?U%r&r zL564UBgDbLKLlH8e zEIhrc?YENABV$>l%SV+V?HQh@A&vTV>F_-i(h>e_Ci;a2%roaD-45SMR^Bh_%=g*y z<|Y)R_rWZ%9?S#Xcy@SzeMP`d@XS`lnYOKKmdcWPKccFR^P`-Lfl_G+KGXFJn}(U- zn@pgg34_=|XZD)NK(~+IZ*my6#V{_`WYM<1gF-oBb9<`c6f@-`KYHr`qE&wey7+XG z#7xeOf_lAFh&IIy2||@N)Wiw456TwAlmj;4v)QJ!GeBy>hLUM$l!0?hwZ~}%_dcg! z5sqLP^VhfO@1s4fh%%hj${11FqY932xmaWi!A6Q$19AbR#GE;2Jogw&{CBz=4Q5QS z_adP_Js&D!!CVX0<&qEJfpWkNnz_nHG|boCE`b3I%jYro>yLqw;FDSQ3wOH2vPLr9 zVMX*s60P~mLGOVHl9-IUArRSSK+4oip3a9q;cN^Oi~`M%Vt_jHA4^~axSDQct^y!2 zoIAWk1hR_7P`pHn4Vko%SLGQsRa2`lYkVx65t05s{dfP>zxdZ>zuu~QRCn{|E8uON zZb5EHA?4S`F6RY%u=-3k)54pqs6yrJxl(F$g65^I8lFCLL`z|!q~2zqFl6*2%RSGx z@uweuy#LRC`1boh{rw-m)nodPQHu#|Umntk+XWPHf_*9iQiM*=Hu)&pWY#>C;TUHK z8W{-zJp1xYa7I=)0G%<;1k^|FrEJx!t*nfa7-b^Y!}4aYcMWT(j4$-Y(_VzX(>9VZ|C{;NtyS%Y5$$uk?)D<%U|jl;zUF{kpAU?^9mJj z?3G(-g|N4i?{R9YsyGu=pYFMuDv0Vug@Qqt;9MO0 z3RJ0zVRLy2#k7^w{TP-D-BlJ?(XIOKNMx4DWQ-B2AygXnoz}gU&5-5UgVt*9tr;LW zbS|={CWA1${v#^8<+g3{pLVOCp#`A(J|v z)$bFza$Z5^sx4ibxWP!cd$lIP{bPqg6tbCW!qU=!$^kDSF$#0)pTNC5e~E#GPFUGt zEi2NPv_uX-${FbmDWc1E1W0EL5aAzAU@oh^EyKS_d0C$+RwgExQ@J+VAH@O`is4dFp#qRbx7o)SlVoOQm>qn7e4SC+d;=BPyS@0< zwn`M_q?M4Qpr&EUY%yot5^Q!xFuiPUiO}%z&Ew zkZCvDi$|7w-B!IKVv4@%)w1E$nDip|o zf&erd0l9dAhg_pwGMlT|ZPaqc<>S}%dt&Ym*t6|s-K_ti(fxI12@7@cF~F(6G_W6!LK={`tT|g zmeO!nDFWX?7MiqEoAUs3#S4Jd6qtBoYAV{gdRPGIQ_9GXyf>zhokcT9q%(plJJJ&2 z1%RIUY6}*^|u05MaV=~Jl%|u z=^uVAeUud~2Cjt(rI+Ed3y?q{Wkim20ih?)-H2KcM42!r!z@R%np5YhlQQG%xs47< zz(~E1Y4SDE&*gK;Uj;wusq=7zyTj{>H!KD{G1o}H7Rm5a7l%LXO>4f!(-xjU;1}HE z@>AJliRYClxMUcEFQ>WzmW15edhHhIXx%WW0afG`xvaMb>|}lv#V%ySUmm<7raREY z!5~Q&J&XkjE)>njdSwxx=FF#P^6^`lDNPa#UmB)1jj%AY8UF#MS3bAy<|L8NB!DF| ztt<^XBU9E8paL*rkgEp1AZi)V1R~kL-xU0R_^#z9nyEzqxc*XWDH?lF8jt?iQgjCqSPVkqk^Frjog?*b8o3SmWucDwkR- zZ5`q{(w&5EH!{{X0w?mp3`T+twA$xs0_GVbb7)*&(7d_aOnZw`Znxc>t>h7{QYBkn zv1^;Fvw(yuNqj;+%B4b8@f*f|-Oi);$)ka0%`XPLj@f_vyO(7995#+~8i><|%}{-xY{#2-Jyh}N^LR|4xHUT`+u8hO-cKDd(;oS5s1KdD%MT~| zR#`V@$j#N~D=&Aq+g^tauo(jhsF7y7)Uud|cr%6H_INF&!iyQJ8q%zuJ58r~E!={o zfu>ZZSL`#+V7A5?T;+H#iDS==dx_0uZYL?{G`04*Z2Pq|lizXdN1Um6hUNj(FMs~y z*S8>}4x$nZjRwp7Nu=zyE%Yxx}^@DJvsgnyd5q6eG1r@;<{HmL`OhJGX>N zbCO}u7&Fl|MogxiR=66y%Y?RCBGg!taFWqj25c5pZB`7m0c>`XVX9Wig~us@LC|1S z1?OVlmlaNeJaY_9KL@E9nXu)gpgVOKE~OF0NO!D0(nSRj79&BK2pl5fWLIY>@Jwi~ z4V?`Ekgwo;tBGPvIAj;Y3=aBbF~>-E(|`ZR+Z0V>mto*^>b<|-Ow4y~UP6iOi7q##ge#HvtP z4+gJhuq<9av{w{ENy}%z7y(KP6$*ipRzPQk>6SxWKA3LdzIvfyt47nTJWrb^!{<8* zfdupE{d!uPlMM3|sw6RtlchkJ`VUkSfJQChO@nfgi8QX)5ftQ>xe5-Eq@MaA7Zl6{ zQ&|PH-`Qv8R%IH&VeW!2<|fvgV`2B!LpeL>CDYh3|vXwb6k3)Xwg`Q8ijYd7cq6P1tDCBlJk0gaj&@5nxo8U#b*K~7Ep zne#aid~7&ok5ri4;Fabd3UY`7v;)#Q>& z8wodmx$OazqInuK6H3N3i_-Hwogo;mFsI2|U3wgmsq>huh0rEl5%cC@cE5c3^zHuJ zR7>57PF$0hcvL7~4d**)b>Kx_%~=BI8ks@=0dv}F3?`>R&6)A#c2C+u`nK2yOjQ@dsUip zgBr|umDC~`cQ@U9PFIa-bKot;gkQ^U)eB}{InDX$JxFH^Q6)el&$I0mq-`-cn-5TCHR=QK_w z=h4X79_+`g%q~AWdW5<2IWi9{C}od2!`_jj#95zti8{553OF<96))bgIAm=iBX3 z?d!LPZ{v-5(F1m);+r1Z>+5~?dAaxc%uqJy{2hhCPcE>k*~W5(>>=c3g?7vHiW(W<_r7>;+mwvSc%qfZh3ySR&u!q ztr|d<+uj~LE4y7mKXnr!6)QWS4QQyC-`uI3ol!R6c z!;k*H5L50stwK|Uj0ViE%Cx;VRJeUK(GjSXLBC4?K!{iV8@I;PJekR3eH@p?cR`d#K2uB^#^3k{zCDQ3xR;DLE3a}u^-7@92 z=t2+{8col}ShBie1dSm7BWL|H?J5avX>-Z^5Cda95l0%KE~^lU6KGPvQdZzllGSA& zhHDt0tdV?NXFq3vv@!Nf0^VG}4ooxv6V+i3m3GaUEtLbbEsCSo-Ot58wsyS}f<3S? zF|EHNy!k<7&z=#&I9D-OmQqsrIJ%_!@%7uN6s0DNLyw=voR4^%k38nL^Fb}}>Bo}_ zp}zDz1Z482&0to6KvP>gV^9-;(X&Xs%CH zso`&5EFUv}CX&ZVeLJ$ejQ$mW)gSZaOaHo!ypFGr-oG3_oZSbLXsjcjLW10xjQ zJb${M@BHzuAHR(GCmctaG-s$;&3TR0#y0o)#3HJ+0|$pHxWiJS7hCD zbsN!NO(+-8KsS1!I{oS!v2LZP7uvpWFk?7XVXeH9z;!zlk6B5#Vq4=8sAB?{&UvX9 z%pD23#4(il<^9B69cQ;wMYOy=>Q-!^a(}cPOxhiG0s}`VvM)&z@6M!WZRYMUBfGFu zY}g@HY%_~59{R6T-qO!O$Ss=Zw9-lCxgn`@Tl2Dju+AoewQ=-Fj;&q@pE@(dly<B9io67{XYnn^oSFhfazSeQ#<{|%l96IXf*P{RdIq@alV$9J(q^gV#WPl!?{wStH*et;kV*X)&k<`Y z8yKj4)emicAF$#itT_fd*OQygb4*|ij~#db*QR>%DB!NE|D_9! zM74a;IY!|8wv|Pbiamzk^r)@2B2>4EA^Uw=?FP zk$@or=9RyEnCepdyn`Mr%ix|AO$eYbDx{hRXNVNSzqrM z1aV422LkK=FHqTI=P026YWWCm+FiDf+Gesld91V8G8IF{fhYI^6?v0^`|D%Vm_tg(!(8V6i{~=@`A2E6oFZkWF4~n2gwFak)lC(aykJ_ zNJPu!v4lZ$L~4~C{>SEe0$x}Pw|Q7j0l@)C%(M_TMM1G&?SLU*f+Gww!h&*xxyqaw zGwk_ii|k~LxW==&CxJ_|W8`(S2?cpW6gg_k$d-CC;l}FbPz#l=$C*{n7lDva0-NIz zDDlDe;Nxzmk*k(zL#gM1{_WdO$Im~HjB^0MW#%ee9aR&OV&yd)e3jYRrVmzvf1%m#H`HAoiUMb zn0Lg$#bPp}*>$hQ+(=+st}PecFq(1uFk_S0+xGtGZr(2S<>l49ee$imR+;|UxA8Fi z`%?PFE}OjwuYUXN)y+R+g#GoQ-|qe|mI!#@WuOR5V||HN_VCXzy}I9Dto|_Vx9;^b ze%OfJ;fzbi=WIXR?7CmEU7_tIgxJgNx@GET@`3o1=AX{De{;Y8Z}7LoAM9o)?i0Zp zpya92?Uv%%@%7-JEcnwO?zw;a`KSH(Q)-;!XCscX&1-y$-^*XBYA+^i;5B7JS>(H+ zZzM1EhviKY=<*`uId5!cR@^SWKl0?0o8xC><`B)i+V!^|E_Z--dTkjQ zC-QyHIM0~o)3Y!9z+awGzYg*Of)66#gL25_Q{S6aGs25Qq}*xW`}VxJ zGK`j5tsTnLIhk4{j%TYGCu~Tm8H2-3Rb+>4%8v-xFQl1(#JEsKR$7Ersd(!9&4C^< z6OouSrO%mB6iOZw!+k8;eu)n~_%FAqg$8VSI>XB^j-ClkOQSL&v1%1^T5`|h$>`wpRLPhy3QJ0KsCcTh#bynJD0hEuNB(dB^?&i7|1-P%_V&6J zi6iv{r=<4gFf(E+9CT02lsD^1Cr+Uib0LeX0OD5dNEE}A9A}^iL-2|zaMF}XBsP|H zlgaEoe){_U=imR~?|=O1-~RibJ$l)$=PZLY`lAOjLJ}LAZPCk82U{3j1r5;0LEo0o z(pl7@qQcTxY9nc_xc};vozQ`}z-j2jC*Nx2WlL-oc*UF}&XLiXv(MtKJcU@|lucAG z?Q#jTE8wkd651Xpyyr2|r5OTtZm`*0}`BFb!+jVchz1?2+*Kxr{duRMyyKVYy z_pqeP1-qGF>a~87K5f@a;ivk^O8Lzg-C%z*zesBJY@e=Hj%zC~8CUGiAFhAZe%N08 zbt^An*vhSr`kUOY_Nn?b`S=l!zvuhkz5nzj#@o-w*WTan=O5ao zlW)Pd%ClYQ?Thb+@=e?Ow|+n7arF1&$H#bwTl9D1sOn|+OZzFuH8#}tc*pPBZ9d|! z9$w#L4!d@{!mhU)QZE#Cn_)MHj`G`_-S@OhvCX&EFp|Q1KkycJ4Lt%#mG#f#X$#?{>Ievo#`@7yd>pkDpLi*whP4 z7%GueOP&jks6?&a>!1)n43tJ%O=OgPsGS{z$m1nUy6`TZ;Q>z7BCeH~kN)S_4 zvD(MXE$Lh{%fdE!F_Qj2{x|={zy8^xc~Jk!^>d9wRlWBli1w{{9btzJIy@{_nn=R%`Xee{Xo<)So6FC_kWzGRg#9lTtv`eN6jP~RaGBY0v^ely? zt<=2P7MTU`TmSkyS=P=)U zMRa(hjXs5Y%sEz8W!BKu-RP=DqZ?RYfdw`o{4e|itUv@JP!q5KuqZT&x+)1>Q)XmD zX2$5V&Gsf$6}k5^GZv!H+#|Apa&k9vBXTph_f%En3iCPNw@nvkG4G>GqGeOJw0>@Y*%h+xmXr*U$9aeZsM z`L^%Ay51#G%Y)bic!Ez-*3moCmT7X#&Vsn9D}>EcOHv{{2@@oO;l2X>>j-;ai3q%WRrfgo;@BFbpXz^Q|NIE&hlm6r^zH_yn_5H)C}H67lrMBO zg3cVlfu6|_EXqbnvZPQ{!tbAftVd7bL3tfEzh zt=4&ul7$gzSxKzCN75K0O@oM|vxp<)aC|B}mf#Asi3)Q{y#b@pL0Gh~hFXn`j)m@1 z)~o0-_+s{&9Z7amk3bDi=(1$E>SE+@9dzYIU7gOnO%aI3WF0CERD;I|cQ-UA$nGiU zTc%6Wx7&@IY|359XVqSv#@1{qWnKFX{UJ(Qx9l3oE}ZL1n7*;&>$F{wZaW)vD-@P64{w<{R zKI9?OC)p0A53xzO9H;%9Ps`L^%H!H@H#gH8uSr9Mt2}x>U9Vts2RZdw%HgINTa9+} z1^}=rdcD~UvRQ7+xs8k)fWS4$x!q1q@D7VYw7wRKg%moWdWar98&f|_!9t4ARgy>L z;F9;yjY?jh`t{Y~M>#c5#3Y(La<~8`d}(re_l_yE2QBB^10|J(f!kqb?v|sdfIVW# zEB1iFglwR*AbWw=oD#ZFGTgi^5e+~Y0f@G_CqWP?q7i}2!i4b566jgf%__ArG+Jq? z7H4D$)P)`tAkbP(1cH4rK)D0Wx-cqO5JZp2IG7DgM|DL~=@wa$j;t<KXoi|tcu%HzHwO=rp&oA1VNOPk zV5CxXlWm?>}2uD*Eg*ajv;w<6Z17r&UlA=2M99k+l26oJ&w`!i8 z46l>{bIN0_9(j4u>Egv6vdy;}#_WPR!kLSnY&&5@ay zV_=jFj)Umj4{SEWqGJYvtxr7?azUdRRik_B>wGe69~g8<1yV%UXu`ZpP(cix2k=td zO-P9%TkmZfT*T^8D&uyn!+x`W z^LGDy|8oD;_1*1VZ+BORRlm%aczCwO+2y19^pTC*r@OzWGJlZSHno@008z4RcS!s#hHn zPsTF$IDOI@s(c3s>N+z?lfA6@kM z{N{8st^^3b4IM{kAT-6HiZXT-3yg_78#c1ry9{|a*LvHBc`;hKbb``YEm*8$Am*Z+ zW-zz=&)OJH=*VD6aCk?wPKXZ!IZKDTOrh+N5eT*H3l(CnvC_RYB@WeOAZmjlM%kkZ zh2EN!;?a&p9DrzoWatD1$+Y*PsWk_zf<(z)kp`%vW-X-Lx{UPS3T}`&)1zA(W063aB*0J?!vZk4KLQJ!fQPty z93zhWJMxbOKF1!G;2;eGY2xB^{?05VoR-AH!azwP-6xSzyqk(dVuS}-5vo{jn%{f@ zzm=p62q+H9;^GPl#Y4h#kb!oub|Fcj_rF6x`)$rpFc`jANDKuBRtR@Nr8@xm>~ifsDj>+G<6RVM)M-`)N^5SPz$34 z(Pd%lN(KdqKKT+Ga1V4y={0Z&<~%gagXq}jn_eQ^0&cb}(C^;^L?j|Svp0*NNsLUB zcPm800)LkfF3`LsY}I{^4tO{XEqXwiB$I%MQZy}~IWabGP&99S$q8j@AluT#Rm{?J zWQIbEXUoRw2Z)KKJ?6CzzQzg6JJ=9g^@ut{_@)Q*SGz>H&1wu&dvD=`*`+{!(qF6(D2jzWWe3N zm%JIKhyU=!Dwm71b$d?Luii-6y?WNyt9jozDk8RN_wd1kMkcHi*?jTiHct!&#=s63m^^LaAkw~@dk}CCV%lg)KxaUlkri`*`5m*E zfd%w}BJ2rX12~GO*nuKeExC6L3?w*}f&mu66+JYy7OO;Swuj_ErdQK}=7|vXs6l9` z!4o8)#=7JgJi10iKc*f-0G}&zf`wJjVjZF}Dvrc-kQC676x`lLAa702wLbM(ZG~k3Bl6-t|~?NAG!Yc!YX6*-dpQh*037hNewCRilFpm zGa3*jQf}sHphe4sgGN_JUnU#Jup{r8Ukd|3lSIG`U^qNzLJ7iS(Mi!IWI664zBLH( zYH20uw>V-9IF%Z(FbHXc+JfF*)(CJ0Ljtjo4=9qlI$IBxrQu3A*_?^G1nUh;Bq$&I z!ploY9zlRdl@#>8CycoFb;=PcK~Q7ymfu?@>{|@xSZ>tbkCc0LZ%&#qyK#XyKm?FL z5RDW%1Bj8N({f4-dW3>0h`?&lpbUA49J9B)_gEE9HCWr2IAR(4P>KvKFI#&dR|OP_xu6ic<3SPy!pbQ}SmDr3o$x-;tBXEu6tga#EV9LB|D3el1T zdG>=GaM}*6ZLSg z$Eg~&+b!d~+FtAS?7aH^_Vt@rT6eSg>g-1!otKmMPtG5|dVcfj<@43pfBeCBYM!Qk zftyLs*B^QNRP!(t_kE#!vD0;ZnhjA@#w{-@qvIKwsqfunt$H%f7t~M9E zy>794_~3)_{NazD#I1esk0PEQZePFp8vE(>Ja3B@)?sL7h z1DaCEp-@V-K&`cVq!P91a&`MTtP5k_FLxFs)ISg>UT@>fi`L#M0elvtuDsmb(1{ z7h95F9XUq#ijX-xyY$)fG9}`yIWLSJAAt%8n84PeWJc|=$pvf@IhfocGWac{VC$iU zy~XJ413=U|k3$7X$ze0SA`GN!s0%a$Ef`it0Ldz9Z)u2hTkOew-bO=gAw z6fCM;>k)*+Z)*tcNKO)Huz3V5Jd3v@LMQ+WX1EazGSDgH(OF0d@g5ZDlKo*oFdtV7 zsv@k!K(vMuC|Mk+gkkY%1Pe*d?HIsvk0Ty|aA<9?2w}K~(49@AHlJ_9%$j7fA?NMl z$Ij9iK=0OjKfD@p3BW^=MDrYr$zCK@x)js~IxJ%LF3CuQ(@-;{2JQHc6cG{GNN&}W zikeC!?c!}&%8RIW1(iVo;O0KJZq}^#ZgUURIv=5Yi=ng6aPNqAd?id^N!4}}N{1(3 zH6Db3N-4f_4+A1LVoh}|%o49I9<|kWpy<9ech2*Eim^_inR&H68y}t4)_2{?`dpYK z&9`y3irf9wi4xHD<*<6%53e`<4y`&uww%;9t)v{xLmrBXUbc%vT4BYL+3M?8zqz|U zJpbFT_HVD={-$o)a56sLeDw6u!;8zu=fl${C&OlylFRId-gl?N$yzt-^~uSpk7ucP zI$O<>C9l$O^62y=Up|nF(~C`Ct=2xI(Y}(K7teO*w(n;x+u7TPr^DvM4>lL;NAV!^ zEIpitB$vDW?fZ|g9`E|2GKrL|>kc}2U<3P9eR?sAR*i1vy~74e({OmTn-90QySrEW zn>V<7JKx=w-J$z&#Muk$xIR>!_qPetbnt{ax!r0HM<5RnMTwN9wCk2uN6xk>8iTIC zgA|}I>py@ci??RZQOcbAS&w&nwRWe0cSOp&8zWlu?)4yHi;-Nw!JQ7gFBqY;_ z&Sn&8f}rL=y-!AVc*+ce{|sahYG3 zx_DbP0!c7sX;Y@9VabW#U(q1sa;{&7Hlng1EJYVg!jYrPY@{%T1g@3`28S!eMM7N{ zF1Z!B1{u0&{FXUSB&UZuQ-XjUT0;j%;9K*0QEPVO*uxE&!z1Z3;)tVW3IcfK2xUw6 zj}3NgkMUaxR02zvz{3-q5zZ3u4hvSgFf<*>deWgn)o-#n3%DNy6YOYCjAIE7q4XK* zM1>YO5euw)Q5^+CLKZ;6LYr_q@&-_1h%mF;)+1V9qLyuT4l-3MKnMe@AS3oI)1a^RswY9!%DU?_b zATxtgYp6j;E;-D4kJac+dau^hLZ#F^qbViD5e#y>+efy{#@}B(_^ZQQDdg|Km$}IMz0wU3~CQ{@d?9IN#{Wbo#-YX%lN)-40jr9$YRT6^x&}=npoR%!Q5e z^_<0V`l>q_?04&2n%(s?ApV1+Ev|(8#C|9b)!w?@y>9*HZ(iB$Z*E@PP3!f``F-+_ z9`VlWt2STUZl0)KpKKp~@fw2D)%EUU&%v{+(J%8CzRxFlcs1q67Z+v3!-qIK`{HkY ze&_SQ`r-`_^>x?PRk87GRiE)q<}ElDzOAc=Lwz}o&S-2JJ)v+|ah@HRFhrr$ARP2L01i=VAj>>E zt)WX$krdkN6fq1$3>-PcJI@86%e*A2v|whTOkILA37{O?N!6n$=43-9$YU9DaYC1l z`Cg<7Hvl11T{d@uI9R_ETZyKYj~po=r8LjSMwKrDkZL6ZGuWCB!+q7By*16~jSbMb z4$B;!SWeM3xT4D(Uewzj1DD0+n9IBC*HBXJmQ@*uIVLd#hB5hF*VBY0R}!{_Cm{o1 z&eGLasCyZEEQDi~P&bdo7z{wEE@mE%-kbtajR;aAf=UURAM5dzWc;(KMS5;pomx2pJ9T zNJ)?!mRP71^0!iyacp&_rM?V0q9Nj_fkgn(jz4XPhy_r`Z+Z$!cDT7lLbwCqM%TC#N;pLiQ`x6JsM}ENWpOSBn-q7$ptBttTLvg$x?6Ghk{jLUaY(O=)O3G z9T3#T{`bw67dcQ0qz`dOFrXl+MS(H3v{nyc+XtPZjIH>-@U%LFk^*T7x}lS=w`T|# z$K5PQYceTbrKx*tf*d2q?DL=sz@mhIhC=L4MeAPYGd{t&Tjl1s!8S5 zJWwJdl}M-%3(iw8Duy8=S0CB$KRl%rN)4boF$HOmMx}PtICeHk4n)s^!afBuV+Iiw z3@;-wfGrlXR^$Pp@YS$HZvwfg3%|8ue79Po!6vT_%6Gb&`y!>Hr2DwS{Q|Q!1DUff z6JvAlDz(9ZNYFXpRa(G3tBA*vy-C2l4EsaGD8|B=L&AY-D9dM7#Phc$hB6~3vDu}e z4}hZ#D#62H3J-0^Kfm-P-CTzn**sM{AZMROSWr#8ODr+=vYbPg!~3@w3D#Xz!x8R6 znp;E&M4;Jqb;LY0cw^Vj+!G-TPc@_jSjQuyCIF&muXK+v^CjKinzoREJ!tmLq<<;# zx}#$OAyl|%>oJ6NN=+$P+zQ|!>ODESMqUiy3qL9>ep`ANeM$Rh=z_jSEQ7+u*>Zoy z{@r!5TO6Nt;^=MnOk)#?a{rnzFv2u^fGMLSpa-K;IxP_}bdQC*yHsWaeL~KU=#!;l0>W30u4s*;0g&jO1Sp3 zvi3kK4Jmt-aY$Sc4967fU`eAL+Jm^ph;5W_JKH8hwqju<`kWkFprb6(w#(u5d8 z89nzY$EI-_XH8=XhDxk5bu~Uj+t3Wu{hmX<)^c|B5~^fm>2KfMot&1F)n=bgq{lUP z05ho^lMdzNG#XEIzTQ7bSBL5IPWbQ{r#lgAVTQWTUV6}elANizuUVbLgk zI@D-)uclj$AGEud`s9qYELt z`>~s|gn*Aybhw))1&|nsICR-wu+Ji#C)ZT-u=egP4k{;%M(N!L3SxH2R@*R?Lv5R) z^E{iZRuMA-MN)@3RK(13t5n53A$kf&uNv5orPS&&5)&XB;dr>Z79bDV`nl{TJ*V}O zyMWTe1DnVQpV^VhRQEwoSr%Srr~zC_(Xq-rpcnC0R?Ca)l6!9heTfTuxJ~fECBoM)0h&=x}8-U&34|szrlW^2j1AGUAL&Yz79qPv!rpA z3M|2i!hV3sGdeXy3ZW;kC> zU+f<#-kk8}D$dFHcEGD=la zhaAoIJ;rO7hx1LW9t7jfhzAn4bHCb7PpiN3HWQWYG^SoQ*LR28+jwoPhty^L^zoCE zeDkV)vR%Ku`|4+}CLORCM$1;z`FHGGldMc!wTy;+op@<%~o31%j8x# z_XiS(Iqkh~{qAJ&w^JPyt&0PLz-kK_!6`Hu#oNMNQTG~(S{J4_2r9(s5v?%!kbu+) z19e$c5hfZIon#p$X0o)191fNUWMU4C=s{${vUKN4vWLCfd>)hH$K(a705c*}Dl*)d zMj>(|2EauXNtOrOoTXM@z{0(|n;%yX(9kT??4?Pk0?Ozh5raf?W)hN?3e+ItXclQD zh(RTIq)mP6Tt7fPp|==>baioYCZn5mYhbawC_jK;w(hwY+KSTW+Sh|*m)?7d0N^21 zrBCnRV+6>g%ZK%7g)?`dAVg@9L)}ZHimvDeMl|nq6=IppFa>%rI!lHqmn&}~DKH1= z5bDB{rj&>Pjf?svC~DP&%0Tz(fh^0+94-h$5%*Zgk_u$;P)&~h4qK1E(}Z2LmC{AU zJs^&bSgtJ+(UuY}{!@SPzNChE2(K;?QfL(#-~z)~(LyCRYpO+Kd4gC@^qSPVrM!Ur z4L%B(!I^pNz}l)51(}$enfI>C8!J{y&c0XVK(okQ#&3+eO-*jE8!|6lGbMuR^kLCc}I;_An?9s4o*f30K4#*eTrt{r1WSocm)%>ve(f2u<7wCPhe?R zF5FcE1)4=s(dcQt_1yBE%~^As55X;9EmZPQAR)a@t)Hn}ot%1`576FX*X5=0^2_IX zHr+jZv^rpW`C#tbiIdIq&p-cJjeYn2qX*wJ#(cQdfXs)Uc)2R)>+1()yVYJ&>nE1V z!>_*ln`b}$56@2jUgdj#`2P3O?_J*Zhrjav^_LH?wvYYcKR!JA-Uq|YtDNpu!{N67 z%Y8pxrQ6Cf&8OvKghOJO13(>g2@mWC?MW_fstIbA(_L#S1jM5jT|?5t_4nn4n}7c0 z)$ZoY=dXW0o1HzqsJAztzWV%=^G7=r5BDCgoFB&iaF<>k@^&?Gx4N<^&$8=zX4=i= z?VIb@uV4Q34S&<}|LVW{XOBL7{Na!OERc6^U;pi2{N(@gm%p<1`qf*Uuj8V`TaT;j z9s8W{<~r%==4>p=;M@J%*w>b#X1{!JTE_M2ws$*PBO+!7rT0)WG^8!z5!FI5rR*G2=7@&Qeac!YT6C2}UxM6KfL(eC0Y<2dAUrvP+_Qo5;!hq0q8&f(;t8QR5#nx^|tR=Kovqo6XZvd_BdTi|R)bY9aVL?QVYR7YMTZqB36Vup_E4@?+O=jnB#*V%(Y3Nxw6fXi5)rgT1!ej=Zw}#rRwaM8;b3_@FY&J|8=8%cEDZP_Juk!$}^u=`81vyJ>e*r`tDQezx0*xv!u8z4tHQ ze{%8tZGP}?zHWD(9z1!1D9_LSGqwG0)|-{yIQ9uQ85@<-+pD$ekme%l(XtLbYLUa3 zRz{;TJQ{QN)s>dUuZ{=0v*ySm+;p9*%D zF>OxH#+=XSI~~p>5*ndK;VKQQ$@*c}Vt@Dg_K;po`xkGnzWVGlJA1f&{Qf5&KRv&Y zucos*zP?yJoHn?*dp*kLrL~RLljIGDFf>%VxeDo1Y9}XW>-9P-f}1xtSLoJTzCFxC z?~ski5ITrYx>XKOADqR-_WWeX+qHL#15v1GHZcUDqJkxA7m*@yiJqa*Eg~^RSYi@6 zmiGZaDv(GmC=69Jhl3HFaCL^dLmLd}(gVVblIb!gYSLH$&Ip9FlW+-fIK#u((W||i z)>lbFCZ|iJP>=(B!Nksv%!Tv-_weXlXr2Hf%yXi(5OFYqk)n7|=qZ6JAweZVmhc8e z5lo^f6PZ|4rCjL1#!#VkMtb;a@A=RI;44I@fI3@Ie8Bz#!*W`q!6kj79`G+#^F6P;W(OG#JI;- zI(i&|LA>9?^4smU-`16wEx*C?NKlU^c3HkpwtzjBNPmh@dZ&mAlaeHqkfpDnN9isQ z%2;*)x&WPN7DFO{ZoUq`p)0xrNmEH#fIFtG_T@%c9jiqDR$w`YM`5>Er5w$h^eQ>S znJBteJ2V$m^szY}wyUU=G#^dl@sDnt3fRfuXRa+gzA0Vz< zDmvu4ze6fGxE&L-W@*vR^m?Qxkg!D>(u_d#WyOPH$c#CV0qUSM$SNBmH5ZL$u!VW) z1xZDtF%|cyQ=f%8-~=j7xJ@agJdVjIa=2~7M&_H|j6er6)4|hrWIs6>87*MLDo#&- z?;rii^Iv_Yn)dDOvuUo^FQ(i5r_-D1wm!|hjw?jx$QCEkb;$i-)72jK_NqSr_c;6H znDufr|4nc;uq@Q8Ozh8cg?T5eP>k`&Yg*r z>i?eh$A`$x?z+7MzGt+A5bg#o-I{RK`hK1H>h@Q^{_8g{u3l}Dv&871eesK{pZ?n? zk3V_*^rMZ)<>{H7uZqW9V;$3)E6CabUHM8q@6*&DNW853c9^g2`Y^qC{nd*%pZxwm zdh*Ewl@u1A|NNK3gZ$wi|6u#%;zxh)r2DgKr>`Q#JDvcjb9BHg0?-7z zn=y87`hJmfj7C zvfOb!!NmzRY8JwjdBGbk12`sU7q8(ea3C|(If*JlaD>)FLZRj+uHixPBp###1&fd- zDhXMr9&GV7a^_0Ca{2QjbMY zxP*NjO1M!HO%}RkN(2qA3r$lP?gaIp|LKoEd8nJ=WZbOG>e6snA*MWJc`fI$ngfC-Wr%-UOM0-Hlq=OSk0fW#E=G87Jus9Dd|O~l95DiQ0HnIuZ2>gbg{ zrier65V%ccP{j5`N{%wD1e@pykr9uyd~|V9uivQ5xV5iey}GMYK&@oLZpx!=?x6cn zE;j26jXKEPp+4KyyDnEa=h>spqwlxNKYFwNaNa(--9G+*eg6E-9UuSj_fpwj>3aev zkvujY98zEU*eZ!!G6}0{o@Cu)1m-cPK$LXWB6r`9X){}Hi?bQRwAv!u-JE2;?%nopZ=dbj{_5`a z^=tOJ`Q*oO`KVsEt2gy8U%hy~9qLL3$@Xo>8-AM2W+P&G45)0nA9nn*HtBj0=(ze6}+T2!`+UHCusSNK8zU3T4s8_h*Y4_n$?i)kvPAiywaVW6l$ zKxR!?PB40WK+=EqXFq=WIG>zvSL532zICUFgM)G}dDVJ5f-gHTddMh93_&>Ljyk#{ zAo!gg0Rc#OpRXYbNn}Xccx}B4aRjgOz+*(gzv(0YJY!kj$UAs{z zXXo3^N?T2g3y&anH`meYc)Mf7!_$jbch^7p;?oa?;q>%m*V;5beLD2iZM?Zl!{*_W zhmO^Ct-rZxcfWb{LLS8BKO8RKw~IgcYWrat9&A4Ps6Kf7=Rf&dp8x3bh5m7KG{E??!Nl;tDnfruh;GC)6rIE+vis|hquqS=OYI6+uK8}eb^pCyVcUB+4RKv z7P`~bv#*|A&GY9kU){J{H+lHIM`!65U;pyg&%XNE*ROYn+u!@%c$qe5KmPq)o__x8 zFXCo@dwZQ$=ND)7N>0`y}>*rr>hOVc1U-8-Vw|$ypYU?4NJvx23 zUd7yh^*2BL^6SqZK7Q{H{^*DAJ$b(mk3Rk4i?e)JZ5+k~VgP4}jPSyg+0u~Iyyk%x zS&>CLXgG{i3nYl;P&y^$K+n}8NAEDHVPsPdi6k2Gm^BcCMI2qCes3AiZEgQ4yoalGut5g}&YgDrwnXAeq(JD2o&Ru>^WToXJq zK}3B)YToz4ROR?Q@vZf2I`R^@#4Y1Lb(MFAHFFQpJ38Uw8rDsu_vJ1ldX%x~;-xZb z9w4e3BbQAFn39Mdd6&l=PH|@i5TUw^KO7MFCQL_V#J=W)2K1cAZl_)$L;BS zdlTcS-rjK)kQ#ZaR#pdkVNu22=c2>hr%`ePF!Yp%*=tU@$)o@@smcgYY{?-lT|_BB zU7O#3@RoX9^cgv8%;8I2*rP%QAOnQf}Lkiy4kYg(_T5ez4N?`1G`+J-6Yh&eIx~ zQ|#Ve?>(Cy#?{4xALfU;xf(yjI##FK&Bb?r^W7`C^EZb_yO%cy{u}gv*vB6u?Awop zn_YXO<^93dVUx)9Mz?d@jarj1usQU@peI-LI!h~(+?QN|GQM|m^Yh!&dHBxZ>P?Ql z`R&Ey#}E50a3*r|AHwYIoPYiEl=96gzkK$fyZg>A*W=Bb=ghZQ1 zzITt?@O*X_^w^T%n$}}ZLuRdSfAPxqy-0dHelk4%<45JGjts$q$2`dF-R0=6%2u@Sn5qBq>Gfj%{m~aW=F)NhlNVxpf(dDQkGJ*o8_dA zce^y{!7yLD?P09h^Q>)lQBTNHbP^vML7*5zA{9mr7HSP0V628g@9U#_FcuXO0}c)A zu_?2#1=2$A@zS+5%UP}3Z9R-9YiVIQ=nRcO=-ezj%n_+4>F7lAMY@}@c69$X@$5)! z#JfX1LRG9A+=@y=OrA8N&?lj51T+>HeTX1qUX-cd8ZkH;AZlR{5%1E4>uxi@X(~iu z*BJ#peB6iAnzUPlQkg45XyAH0qfHjsE}VXitHN zm{}kcVxXahyQ->KS4BlZ7-5UuJn7I4rcB9TiK;3`mEO1hKnb*2)QiCm@AQeQqNLn0 zZP0g;&c|9!r*78!n3VUBQ}>vfrvN0%yLOZ9zq#CVu(ppj=`bvc80)5kvKj=Dqf)G_ zR_keVeS4Afi?#)>2MzBK)js)o-tS`VA=mu`{fPW1S!rU>f_>v!U|x&lCTe{iOQ~i# zh8z>fUNpvy+MEE)(T;B{!ZXnn(1OLR5D}2sIfzUM`ch!T04Df-G-}xpRlqt=i;NtM z)iI`dLYD4v-E1&BG5AQ)yx9iR0FOX$zsXsc(cZMFc5cR`yS}S)2v1AgUfX`+xn_7uR=x{`&6uxPHiViNnva`9s7J+<1Lblz5u0$1!EJX{~21)todzZuPF~ zzuA5L_vhQ;$Nyk;JI{52LG#!1*@MeN`QmSX1MSw|3)^1yO?&wL@|~BzKG4lipbc($HCSVcO2bQ8F|yJLX5E*l0gwaAB?xwG$V?QdV# z&v$dY#H~(ezxvgSc=+pI-Coq|XVNEMTr(b-alUPDrxofx+B#xIq|@~WchY!z8h3Y? zR@-5(=MVn+#qRa(Ui~*_<>`0c`{e2O&rUC{pIyDZdiD9M&++7+p3ojVcq+GtuWoLv zItjBrLzkh674*5JQoWUwYj4m2(ux<0Qh*8+pe~k;3`;d;=zfWH2q9V|18ojS!zx)N zM*2QvJ$D(&NWsofjUf;amOzw3LFBWjZx?Cz?N9{b}VoA;Gt($>W(U2^&446H z`aU4H`}p+NueI&>hnuA1p|um78;48OCd80BGMZs!wazaDegh+?(sj9i88BlF@mbw!U4$ci9Omo}FG~Z6OHyh3` zE$!@4@#@vz zeCc+5eLAimZU>VshD{e$;J{c<``&4L+^U|YNE#<2A~i+S18; zCy(kTB-B7foIFZ1#+UW{_U)%X|K*o2U%s-N`j^-8_4D6+x_cllvE2;seZY8d8=KUw`%D>eXv>nGQQU?8y_OISvZ5^8Dk3afu2zJkY zlgYcoD+`&TsqH)E&~k#OeY3FB_36V8PH*PJ;qLa)#aQ1Se(|?It!?-6E_md#lgBV# zxDhZn1S~vc8CpaK19Yho;^GoX2~*Ihjt;0)i-9Eo#tZ`u8l73-%O@Z)kSNEott=6+ zceW{zDbYKk_iD&+cW;_U_=GI3svV|zppYei#6(87L84pKoWhM^+O5Gu6M7RwhQBI=z$?Elr*509~SPfdBR*!TsQ05Z~f4$!L&NLVzA* zmMoE+xg=L2n3zPFvhx1DB%~@0(nV9pnZQl$P!FcU=yS@&U9DM?NjaKL22T=m7evV- z6h%9<*c8%zQEI(IBq>Mtz0b~3DM>`#b~6%Y&!bxPP=`)2le5PlOk<=;R| z>ufJ>#%g$6r&~EWm+Nb6DJsyCl7>t;#!{M+)29uPQgsIvl`L2?~x=qmBy#)wE zq!69oAuDLa9LPSo!*UT|nv9BqBDMN4IR;%0^%)E_)Pg~0_<%;hY8Fmu2F})@UF?8# ziY7UV!hycoN;7~P*e99jm?&iCjSAjE(=j+v&AdF}cS2oPS zFYM&BNEyZ{P9XKOlRx_5Mp7 zoV@p&eql3b(=M3Oes0WVb;J0pH#cFunhS69`=6}bHJ_QjTJPRIzoOyl7xL!4f$WdZ z4tst5>ecqN)x-6?zuiwU`}^N{|4P$z_V7K@%NWCxE#%4he15xEI_A5-{rs==?Cj&) zY5n-4@qD|*n?o!%wRHL5^n;7l^;getB3cbN%?ehaZiX&q}(kxOsj!9Ioa_XmE64NknDPEx`hASS_R87>H@%U9txU3ktjx znVSzudXqGSfbPe{eMYL#Ql^Ln&Kn|vuH+Q4rq3$lQm98+WO&MWV37HP?nMhqz!UH|KU@Z;}4)y-;qcDkZjC%0}bXU*c>RuWaxiMY5oM?wvV zIkPe=Yj<_z1+SJ#^d0ufBZQ9hmL{ zyz5!TyUXd=WO?nzG^F!Qg3)K$Cb39GU^|nQO`W?|a}938cMToV=L%wKorq@E5D0I+ z2x#~qd9T{(_luiJbSe^wn&Mx?;LW;CwC>K1kHn=eK?j?OfMqv(W>t_ z)TzF{zPa8_Cu4f>{tvEp)5|a3TptW&^;q^7X|mg^yX)P)wIP>(@Z^&sa((%o%RXVc zyLtQk^Pm3t+1qC?E>AunJ*^_3i9>I#!6BeoefIV3G~MmhZ)5-L`Myp!UrTw~&(?pt zxBX{-6VLzVZNjASP>8R$7k}>$-oF@zX__yIhk2gOL{|@PpA8bl;x=p( zETeADPlphWxq1Kk#n<(AdT%ps9y~nz{`YRLcQ0N%|KjD{_0`?WNmkx%opM-0q;X67 z^)$(DpH`>qEDtYEK6-lo<(u2*&!4R~zFE~rmroOQ=(XiIym`@5G~2Y+ErRe(>9HmE8AOZP$%{y?b+acbAjDzK&sxp=4E5rp6_+tbxUcCvXqI!^OiH-9yF2BT@tz zOt~zrjRqt!_XHv(gn6(z5=tLJO<7V3Atf9tfkr4n?+Aw|-92J)om%&~H}3|zff^!U zB$bo~m1B}TtBl9avzSOH3Zr>HqP4t>OA2SG>ynUngi@B<5*QsG;=06!lo64X0YMbW za7A>cWK|M{(FjdLv;$QcwQB&04P9N7VJOfpzRn{=Aq)=*P#{4|p-V|r>2hovH({1= zUv6`xFvt*v`+Z71KrA7c@Zj)PK4-yef0#RA&7s618Na*5meWyzdgsR*>mMyn!YElzPBq;YFhy`}X zMbN0qkkHOZSZ<0UvS8@rT@@>23GE9sKth79qL2S4%Pb0*08_E3&e~nU+C2%IdARTE0TzaEnjwVJP_>2k-U4O;GO0O2ETtlf zux@JiBs2vD=s-fuh!mNb;U#08hLlgT_}NerBML=0LTvWh&F9%AX>TR#B)Va9WRHCb z-%Qq&TeE{IY9P~2lFn(Dbc8dhUVbtRo152PRZT&?)74blyqkaZ{OZBQ)9-(9@*iIB zesXiWzIl1M{m{$-d46@5b9#M!qvPiNfAGIZdj6zc|4;0%=iB@j|KjDZ|B89@e|_-% zAD6bqxwR0L5BPSicH2vsmsg*S#EWU#-M;w6I+x4q#M1%YPm8VB zIIK@jLo~Gm%GvJv=E?g{e(-yL^76$mpTByu>ARTa;=zUXIC(TE<<+x0_dHR%jw!^$ z5uU{mwWO0njrDdsdwBZd>e=npq4~Z>dUI1Xw^U}4BvHCpcU^R#kug*?afBI~x{DZk zSZE@Um|T}IhB{J+M|DN6l$_c!s3xII;)0avRg=3&TAH^M=rUb(hJdxZyLlkstTu&& z&y0}lDk&fuRHm|8T0Ka5)F>k&x`}y2bVGz?pqa9FD}btm5Irao-9>@M0L{b#3}{MV z3$EZKnG;ALIr5-knkcEz43dy4QU++=ebA-jni zLJ*_TxR46(8DL9SA?_nIv2@!I5e7q4h%7rZgT^uj3s9g8YNkk%q=`u-kp)UcB*c;n zVpu>S4owczVBFhWm(b2~RTeHa2ZTglYDvMxUxU03W}5lBlkQx+b9 zk_nQ)ML{W^ZPCXr*>nObWM|V5!Mn6R0&XDWc&|7P`jw%AOnShU+F5X^QN26bQ$b72 z1VUVuo>CA%DKJtbbgq*!%{i}Ntpzf;XnzH3R*~e)abKq>a8SO5&0zQe5Do$hP$MX4 z-r;Sk@Y)a(3@L+n7s^Ob1GX9U74Q7ucW-xXNAZ*H+a!9TA_MaZ-q4x(x@X25Le+XZ zLpT2Ng>CEH_Xk+tm7HXiG>Hrvtyl3?P8RMm2&G7Z2bJAyz%XpmzSmJ176dK&2=`2| zz{Bfg$JIzq3p@uHmVd|?SfDw~sc1W>+_iqeh6c|=$8_$!l1#`^J2W3|#xXPO3^Frv z#BP<&yY0ua?s1dz+0AagX?M{IPB)Zse`nrq4!7pGJ#gHfJwAWm&z`vA(fYgT^n>3p zb5(17{pS4r?}QWhY8pS?_t!69rqk`_^z^}5RlEJSasEJjb9V86_=ErMzrB3_|9Sr5 z|NiY~`J?~lT18;1lyw?I!y0BJbxIg+uQk>%SrFM%kNyo>h|*C4*Fnx z{D29F>+5HK``KTNPyYFT{a^iW9;Dk}efEog_b>kCUw`#!`eOT?&%ZOQKm7g&H_w0h z^_%(Shr0(D&Nh!ew6*Ay%W}wmH6wNohl~+%z(%q@yFHZQ^+!K=|Ix)q|L)UQ6^ET& zc)Z+uPy4(&-2#lT9FPF$wAZ)vouF6RFAq8L^JuG^Mp%a4WRz;x@ ziZ#SUNLYr5AS>w^3L+btK!fIpuz~1|;_Qw-lZL*~3g=pw`nm*4(bS05J7O9f-90G- z@QBueo`5#h2z0v39F}Vp>J*vY1gTd~1iFhbMFa_GAW`;3*7cr$2MQr7JnF4IXPw=` zqq=lv!a731)&lkyuYcp&j;jOH7NiTtG-fjtDtw z2Kb){#gji&L89*?Hf@lm4)i4813so=eJ2t?8gA`@Lhrq32tD z!*v>UOu3|0_ME+6y1FCHG0GvLRi+2GcX#vZUlWo*;f>~c z2)MCzL5%PPpqbp;44Z0!BiiVs$d<9snuT{+RvA~DO~ZDiW;cyCfR~%|EpM-xWP1I} z)puW}N5lHj@c4tLYrWiFzKsWKZhu^F-<&*T#7!&J)8YE9@Alu@ZYAqv^4wQ>(8oEw zC;VSeug;|XmxovMcp6UOu%TP5r|C)FKN`Da@qC89eJbTF#b$qZ)(<(Iyp)T()%pxEj+evga=q$4+A&=F*^^@ z;Vt~oIJPExFH?TBT95fKMQhDRhuNZm7VSqYKoN>MH09!G0N6z-v0p@zFM@wDNLI;gRYonchi?|Zg=%im1=g`(i1RG# zSOKHs0IUU*#|D(Qb49N{Z}cR<2EN?y&UZH>5Bs~m87}?+ce4HPPk#J|U(fnw)#+q- zFyig&)mQKT@jq?d06|3XhnEjN&2jR{KUqs^SM$Tw{53X&zm5384}R}2?>_qDu~73{ z)x)dqXZiBP?znz?o%C`S1T94ga(M?GJwZdw>7;e{`|k zOnijv>%EtM^ZC`u;}5?3!FvyPAHIJvewcs!=|`Xb>jzKI=GE>{H{0PyA8)UJv+g;! z`TF+NqxU|HaW#DW;YY8Z-MrS<%_EnL8T&M@SlWL7R$dRZyDvU}e*WYS{)>P5_U+XF zKmX#(SKqVxuONfesf%|nOa9%`0My=*mm*;&(AeOap#xEksL>fIAVZsKiXdk&mq29< zDMudE4>hhIBZjnj5N-#pyX^X`VxZ%d{~{1s27wFi;T6*k{q|GROiGGYe5) z)_n%0$13!|Mc$|ecjkUE@hT7*m zIA#E)vwbUNf5aUcLDV24Ee;Mr{++|Wg)gB-KaTT`PePPf1g1GL7^DPM+*MP6HA2`+ z@`Ym@;gAdnqJaaVhIlO1gn%+Mv?29?iaJnZf$v5Gi5@|K1E(4pB$zYMjU`DVQotE` zK|BfWH~OS!{WgGSQ)@~x)j;zh(W@u64qB)dwPpfpQa5K2v{*U=_~s@Bfr;Nm4>uP< zl~rI}AwwLSRbZ7~XH8gD$elNzyU9zqz}KoLQ17zJD%eR{7T6(&eF#|u`gz`5wyW8P z@Aw@+=s>cW)ue3Q^dL*^Iy`DD_ONE|9spa-LeV6$P7Z4@hZ(H0NaNxyOEKKvy5O;l z`5>|!I|2dDzCejQx>AH^bRUvQ-KfVzvE)2ag}fb{ICB-q&jmw|(0C+5Xnt?@oW;THD{<-duh8 z`qrj$xI6u#&EMPjVVu6VeX#u}n~(l?ufN~M&5Kw|**@vlt+?*3S>tG+&4uQ#VdZ71nfgIDdrBrUhZ3>Si^G8f6T4)%bc#Uc@f&T{~Pxso$k z(R&EM3KaO#_Y8QC-n*>b?kCllqi$*+^3u{+K(!$#JnFBH=OCvUy4@=egYE;Q20hTM zv(ItR{o?YSP^b#%acSNKt-OwIuS74T(_KhqB6NH8Mmk{p4D z*|(_60ZjKy#<=Gu-+xS^(h_76>tVY#iA8de`wP7@N@Z#0?BT)MBNs8LB72dsqll+> zLO}A}5Z!YU)vP%oAwX*QRTmP>9ODmCY1ttLf;qDUYL z(zmW$iw!CiV63Gn9H2(Nzm`YPn4C(bEG)=x1M36fJpxFI`%3^N80h96ZUYSwvx|iX zI%I%%ctZqyK0-@QRD%kg^h1mQ#+WU9f=3P$q~H=lJ$^GB z64Zb8XFq=Ol;`7WTy46w*6UC+QOim9Dhrp{U+) z)pmXpb(*HDelnHeW;3*+znR(V!NM$cqcwxo*1e6VV=m$>Wg~~Mrr`;lag-l7?rmcC z8Ieo`DhBr6&5YKeCBsAu4FY$pM?@Jks-VRoI=UvF=Q^l?l1D9GFR+5{k;g<7ay7(i zMIl;6&I_HR!1jb}B(}gVupw(;bJy-t^l5j(vP;Qo-$z`XJXqVSKmTectUr1f^hnFjsyQlr| zW_~emzw_~S|H1QT^S^)5pIzM^_IDpXU7tVw;H#d-K~{I|(H0ja&U0K8wpU+0``KS! zfAP!RZudw0Y9yN3C-Xs5G1J?s<#xX<8||yRyc&l1{qsj>!?=0-YW(o`1?h*Er%y2c ztJmjWwasC*8i(yCzxU(Kliy4EWL-*Nxcd67-`-g7w_kn!dq4i8|MHK2c>DT=x!=C1 zSJ!;F-XzCIk3Rsk)pmvS(?E^3Q>(|eI^NYV7_H(_u`TR<+59y1qKUK|`f30w_iet>m(JT8?>SA!Q6rpfUnbcoj1+f=rGHk`&^Iv~2stV11c_f{2oUeHRbp z1dbD&gO)>?H2j;*ttKHw zpkX12VGAA8MC5mhP;TKtRU#bG1A!309pa0*UNXEQf>cQQoqoG?c5yltV#kMrWWh+q z38rkK6m6lRDFm7{Qp#dEmQ(?p?#Dn$B$scM8QMs|!Vm5Q!{GW!(EcNEw}l@S0Bj#eA3zAAU|xQ40+mu_>fX(5r{@+BkriW;lSw&=b{A>u?TY2Z zD}c}pD02^mx!*a`7#zT|Y^$M|HTTJihe)85!2E8_k7OZ%KYwjA)( zq4Qo2Z>wakPP3#>$B($X`|RY@-Bz~PP;u+lzz6f(V=E!6u=V)^C0c8+<%0ntR@ufA z1%%KC!;(}!)Oc|yw>QW314l}(9>=i&*=EQJF8A`U0GyS`hWVSJZx^4F^N3C%LRc%o2({$^OA=aiViz)p z6r{yt#|R*-Fo5Mp^^n#X79gVr45&+)v{Pp|BS>vjG#fJoMD;)fOjs=3yM?>J2_G;S zQpHHv@ydvp=>xP@TbckfZL(T>^ZO;9I?6H@aA+yXvzQ#SE&w{h;Ti_-{M`nrY8r;3 z%6qgT<9@(>-(`pNcT=8wpp z>$0t^1}HQH2|^M-el(+f97v{#%rw$Jsev@nP{sj`pGb-XKmtTls47%tWxjlwuZ!k3 z?tYrRHSinRR24)@jKRx6Msu%U_kGUUd#&FxcolE`G7y#!5mRpP1_~ZbQw~@_Z~<6o zaJX4C6^FMfqOF>M=alnSVcv?@0+Uq3c{UqdmsA83G)19J+5#PYasvXn5+31{ez!zx zI!eW=6Zh}(!UxWO#Q&vj(`4NKRfJ?>x<3B@BL4I@CSeJK`4f3u#eOeY!~e(_n`tmzQ+G0O83b>|03JF z%RfE->ixg@fBvWO%b$Gp7yssyC!c)y-sK1HU1|40%U{Lh|MXA(<ET&6KbPL|)@zkdH}*uMA4=7ygfzxty1i(lQ?mF%f~mKwV>&U^B7Xgi6gXHT!5 zTtENOkN^FM7q4F&YTn?!`BSyl@5i^kvn~{cxvhlPo#^_Lt3Ui_H-GzIU;Nd-{g40Y zKfM0(<=y8m=i6~*Ap4uU{l~Q<op2m-S;ME+t+)d%nKv z-W}%Y)vx~S(-(G{0*K})7KF1e*^@%g?uLd61Vb|q(bYJ^WuDE!JT=g3rHYY^jgxCn zPGELRWNx)=5(VJ5($PvIyoD zd(>u8nUZQj3C`?p3)48y+0~)h7rsmrw%PzHA=SO-Vy6OBP!+*}5YN(ZVe+o1_sa@M zXOgM3q{vl<2`j(_Eudxe0vJ5Hw9ci{qo?U?2WGB0wE?j$!(EYpsAk#SEmH&{THVxH zkm*vXl!o?OK`|l{rK_#7K}1c8gXoCngn5YgX6abS1X6=ESSJF9O%{SjK`jPn=<{(RPVGB`U&1iFT zFuB`lfzDf&G4aoM11dw78=>TbShZz;`AfRX4EOZ|s6MvH7<>085z_;sFI!^UBO4Bx`Ku@{DO z#E(OQJKSt)Ejvqrh)Z&vn-ai;An;IyfJ{>b&5N}~?qD$rQcCIoI*Eb7n*bpoFa=8J zZDvEKF0-!!R556@rcol{DR|P|A=*J^ZCA=1KUmXIhB zTI|r9#AKb-xMrh_8f)Lpx(&|Ixp^{N8(;Cm&o~Y%hnK$4@@KOdZD0PNy#p)uc5I*d&jgkk~}uMr=Ay zR^J>`h>yN!H@EB##edbUO?XlXWGvGg;1IQ*#vUj@H=cI;eQg;nubFMSXWn0R*;2T- zepqQaS*|qau5*P&dACivW8T>mQpa{WI1XxsHKR(fgM_)>&wdz%+Yp}&9D-FOGfp$A zhkNPs^saAqxp}nN>~^%JA>?8PvNPq@sm_+2ts%IXvAH)?XC?roB|N9Kf@WkNqlN`BvW!5Npls0=&@x5X z+R_w8uC*m+Wv3i8(1{QS+6-;y2Ms7N(j2+L!6na?P(^D<6-J;H7#E9fIXkVyjqc)O z?L|4-;C}9K7-B&pT5t&}jaITHh#LqN@q#wy32shEqdH@Sd98G{YH`U72_|r7qaQs*T2~F{|g`bgL$4 zl7#%uDgg-D5QHjXZVJ$RvLxToX&@A9CL(4=k+y_*+qd(uAi7KpAqa%R4W0!QbJzrj zS6D@LZ{}9u;tg=MKxyjYTy8wV-O-$;ee@LW;d}a4Pw*<{sXF0K)N>~ zI2{O15GlkYP%wpY~s4xdmzW zvpf?&N(ebewnae~hE1`twrW6wbOjWbRSMRPmZ2(*Fb6{^B`t3TF70G8z*|5av%6D) zK$k8IA$6T;;EIDl%zfRgg9ZufNOd3T0db2qC-X+^==ZcPML>~Y%cR{!ST-<7-9-~aG;K7RD^Klvx`eSA0m>;Lut6+iv6{rG49` z_J6Ah99gpO2=O6(^Mu; zQRGlCbo&1Dk8u0)X4m~$Kiu8DxlE_~+pms?8||$dR^95*f&1x&`TFVbX!EFl{-`^h zrr8YD=F`|xD#_l?*Vo?t?k7+0SM}fh`QL47d5c5v)E3rZmFBLqpF&AaFK&PY2p}5G z=~^UP04w2O7K+0qxsxi%6=J3$%dWc?f+meiy(g+cAS|E}J)1W<`}80l%teDVYfgnG zU~(idOC&rxG{PkcKr3F%Q@0pSV~j1wq1Bcqubx{VGn`^@3BFK#q+yYhumBz4C7@G) z2nGuQbR$FXZjq4j97rWgoD||&S0R8pj7tyQR3t#A)|w(0OJH!iv$fVr^*$~kh&ia% zlG7qvgwxz+X=2VQV0EAdd`Ir?->N1{5tTr3*5U!;OZy%!2++9LOut14AeZJrQ1KdD zo1%(UY0e<5wE@MY!cxH!REOq}2V)mmU<{%SjuvQ)V)Y?+%9n=AHtArxn~N2V`5b9ijblMUMl)>0_pYr2h)L->tCPr%)6MN>a;atx1%Y&lulK z37`Y&b8gsuq*$G=&X+^i`^B!phH`jjSj-#XwWmy6#XhFs`kda*$VBSkxb@`^>T(mO5IkwX;zx>rJ_?M@Df2@<=-2Cj2 zd))u=haY|L;U_OXy|1_7fB*g8J$?B8|Mri6|Gi=L!Lz5UUCaOCek<#)GPypP^8wgZE5o&2hEIxafCSp7(^|2}H$ z`i*$oy0~MW&FxesIjq{sTkT^=t6utyVHO-p-pZi?S=y>m4)3+0)Jl3Qk zvZB$8I|&D8qN|vRkVuC}P}V-+Wa^Mk%IFenZZ2#f1u1KBiz>C5DjRH3GoFFoF156f zW?1w<^WaSdUOHufI=v$e0u={bJ1@C;TMh+F#wP_jc*{|nc!K;|qr(>cj>AFmn7t*G z0%H=iASkkw((33^tQF2kt&RjMlr9LHAqgf!0zhMHXI(ZCV?`J9sfNMH1*1z(4k$R6 zR+R?#Y(1nsP@P+|d76gRpiYwpZ;(hRFpEdF+SEW$tgl{(U{b$TX#|X965Y*EUr?c1TTwYUkHIW z7pE_R?SBw`@4xjh^l(|ulb--5ExAT<6nHUDh>rRE^S`;HukOtyiDk2H*#Vc|n9Ne8 zH=n5k%v^zG>fKA7tSyv_LV*|H(587l;NT9l?or2JD7xc8EO`YNz7~7b*OWcbCKIRq1aJ_QQz3uVL%C8+2;ywVE@kl@?L}!+Eb@ zj#@*-j>$I4M5=mxE{8d4A7Pu72A&)bYzTQ?hq^oUm)obI+6;_uprx9`1u z)>gxAh@Fo|_8r*w@9nsX(rf+s-~ZM4>Qm1L&}18KE#KVbM~`>cA_H|K*e6g*>Ql~b zoDX7`SFp|Il}{unY2Wj zdjLXos{w)5+>3PrhVrm`C(tSg=-k*vrvtNi@+JHatQ8vELgysG z0vbHI5}^BBa=3lYn{QCZ*RxrJ7eLzCRg1=;;?3&9#Q{Xc z-Gc^Omm3eOLc7q3cZ;tiv^K~UQIO( zat_bZB?zj0H!{$%jBUSLgv}92G0AK+24!fN9YUykq04#DivT)2nl>>vH$Y9y=|ph0 z@A?3KP51a)%m3FIhjR8syL+2+OAnbTbekyl4~C~yXQC5G%UMUk;Iup! zEAT$MI#2Q-s1jkHf|-`w8-UQ2wJdMAs)i;kW-Vf+r{^b|-(-(j9_GfqUPygBNwSTyN_1W7YZGh&d>!H2;*=Kh* zFMp?h`@x_5@kbv&TCEQAZTtD{JUcJF{X)WNls7;9i@W~vhgU1~&*1RRY}NVew}1co z?UV2S-m@pe@y@oFdiC*@0e$RtL%d;bXuC^Xt$L;J^5MSY}Mxyh$AEd zmc5}iAkJ_iyTt&>*}>{ix@5Bi&1fkAr646oCk+oaLjr$hsNycb1Vllmr)AT* zV$-PVi7w9Q&N^Xbgga}^@a6_#b4jRkp`{LPY!xxWX`PEpPtNWGtP<5lXdWe0FwxI+ z$Y3<07IcKKb(}3h!y+LsG>1Rz!M%4~OuKj(j}|rnA&SB3skF<8c23-tYA{+jOXHl- z!WKMMt(~DVHa40G6J;?JsVy0Cq{9NyJQ*9I!U@i1YQ?~UK)NVfHHx@34f+uGit5&y zsfzgmSf{zeU=CHbVhh%$R^yUmC5w$lLg3?HI2H^A>0i<{kDW$tA>{A(mPuokFH@l$ILgvyz4BcNvt`z;aDY@ zVBRs$0y(TgcWiDO$lWDx-_G5m=!2Fd`RXL^qk8l8^Jh;sx3Rze!#N9rZ^J{7oB#Cb zdpmvot9z?%cU6CY3K~;<&T&6=7v1z~9K7>ra?jFZtrjuNQiPR;CitdzDDaq4(6H{J zP*Y0*`IPz<%UW8}2omrBm-*BWv9(hcZrkagL$Io_l~3F-9zv*h1UJCmFo4y4b)OFx z-ML8!!Z7qcSD6m?Yh{d`xgYOdhl|pe?RCg6Z{E6hmvK)#+9V!tU%Z@e-oBnf9@qCj zkdNOG(+<_i~Ei5#-nXS<11$Cs;v zedGiuW9|)aWNVXY;`O?JZ1qm!;Q|SsoiA_aYrV619C1|o5H2b)H|v5mMTeub5q5GL z-TO}S0PW=QklJ{vr@A4Vd#5EIx#{A5atp;$3K{g|b!I{oR$DAiW^kXWU1TtWi-X>! zI_Hi)f#OiQffLXHX7G&J5#T~rXCS}?EA*KK9-FaS?0(BfAG=WIws`J?hta+H#L_@D zO#~dVqD>OIuhT4yrCN$|Jcsz4Px%dVWE))_6|6u2lN1AB3RayXdfM!gV9PzuV&dNN zO1k27Ls9R*2y+L<+J*Ry#Qs6=a;_IM&A;12P!|AN$;dM2by->GMqggQ(rR8xJcuSP zm6J*{S)&{*BgT7m*Ndv2329JG#FqoF#CjwV42q1N*up;i-u0yAqSo5O_k5=UDm=I}m{3oU zCl;G4#ZlCRQkzMG6LhaU^KJeI%DwxNLDd{u6 zYqGm3N1k98TJ9%rtu!|~`Q|d-e|{gHBaCq#;KmYPxnj7tHncjK#balF)yZ8F(&UZrZ;kg{LCf=)s+2?lNYK6s< z4Jw&JCy_!&%Q)5+1w^U<*@_ER!}>61t~Ov((TZZycUBiO2G_uGaGWYbk=4ph!1)jTgaQ$z1Ng{3=iTcVpOYhqt%mYW~yDPwzQhZtns7;@)oGjQQr>&vU;W zj$M=&wSCsA_)RA6$(8_TRm{8rF}WeM0)>YN|86L0kKZw}0{J zt5>f+?}p8L*XhM)FHZYIe{9dnJR75SZIEN9D;q_C5qpF0{h+d4hv+ACJ6JCNf9Ux`gc3(z~oj_&O%7pX@fj+-!$#e($PNxz6R&e7mldCqG={Q31>l ztJSe_iGPsZoVkXB>()*=?@mEC>F#ab#M8^ELGxjr8zM8@7nYy1C+3{z#KC7+u*{|i zK2H;KPM!=o&kIEd4>>ph-6K&PJso6BkhajK612i)=;INBc9_dCO^AymZ zYdDG_b9PYz?79aMnKxD`>|&H95IY8`#k!=Os711QDPGVUTH^7xWADu=(CiifNL3G| zr%bLr+w7WP1=2@YGYK0q@$?O)tiYG zg;a#4gG$kw8-V)V-bDSnkeRXxX)!a#KxK}V(27(pYewAL6PZ9!@Lcf^tZfByWux{cp@^&;9^J1(BdKfc&~FNDkI&sS+k!$mso z>-V3DqquD^FD2**aChaz@2OVZ{r-CwFW;0e-@W|N)PCXq?oQsjobRw>FR;cVT4R~A z3uXLfk8%$0>(HUw^=l~I0Bt~$zs;P7oYp*c9it2fucDeHO!M?eEsytYwN{%cGPoSr z1*y{9r@jr-bXsln;At)+wt{2B>X1*{bVPdR?rs-p?U$pdQ?B3kG=~7s)Jk(#l`RWw=A4)jBme0GFv*cgg)J<>mAEn0C z`{n*ufBlQMztZDjR|ZkTd+m$6e7yVV@$h(hu{w08%?Dq+bDmxu4mY12>)qkyCCt)$ zoqYDEfAzQbqy5#}`om{`79PEyx;4Zww{SPD+ZSJa`SRw?bn$GJhKp(Z<=y_+3EsuH z7B6<1oxS^A<)Bj{Nj6MuYuOw6f@bUFUFWyQyWYl>xlWIsZTpMcS3g_zH;3clP}Oq{+;{O~v)*Q}ZBdIH$GNYnPd8J>MS(tI zp~J@RRqm_pk4tm8I3S^zM-+IaBx`kck~NlUO-q5rg3ypL`?qKRnfuHbD~^fy&HtyfZ}+Bh?@-0tE4C^al`&0yHKD_Q5o$6>~U<5XFJn zt*@aa6?Ltp^=Oj}**sA5Y=QLV>P<;tMmmGr&wm=Fm;hQjcDZp@@5L!dWhnu*x&yAI zdJIrhpUWsi!H~`@*ai21T3pk)r(dftFwfICri)G*nz$HUoCX(a@fM~Q&mnQ*Z0gaa z!HQc1&&xxAE4VPUNCT|76QQxXNno|kaGqnkp;`NlF{Ph($WGD?%~^l|P+S(LBn-{k zS$%1)Do(U&A<1_N{eSa|5NB{{^ktyxutKU6jZGayFdx*MqQ#7e3nmS2JpZ7e%&LjURtlZ=Vjo_{FQ!VgIA* zca4GjyJ^+&_{Mhwy+)vni@IPj3&0^n7rP-Q@E~}k^X<&d&JUzeG@z~N(dwq_G=_OU z#vUig6H0Kaq1#n&Yv4#pM;^mAnAOx(IO?6D6F=@cbG%#TB!Gk4dH@8D9mQ+;W`tApAzp*tFU@)Am9%mX`*4p&E5%|0Uoz$_lD5qScgqU`!eGIo6+=wc#1 z!XsOzWELkz7mOhRGCDwSE(sL4L`HNrL%OO+i!X4@hF}ha3UK|6_%o@Bk3HG0_Abg$@ab&Bw1>TDFL2nj?~gFg480 zj&xXaih%kc#c^S3J}p1n9Mwmuqp^3;;1S4ZKxeW5x+Py8#yE|W*9iA%vBMrwK)^@zIHeA<>N8kU0wf-m|iN9aUrjO&C-{j+Imdh0KEKiEp%groI*0xWd z9`jf)Kb*($d#y!;`RAV>Yq`9i_XA!hp?3y1cmT2}wJb|jb_7C#CoGGfF2j98bwJVe zb=Ac=#iW4FBXzy%I9Azl-x$`QshgUZdQd-3Nakv+(Xq6=IJyByK>0OIv373RQje1L z%TGfW)&W^ub2YQOa_nkYt+x<4swacUvH$3MPo7?5dwP1cT|IgUUp*h6TmP5i{oCm{ zSjaDN^WOZ^{nh+vl~&;=FVaf7_ck}5TRz6FyxqQy`Pyc*?&9)to@xsIv&#-I{By4U zCy}u+dkl%6Jsx5^SbAK;*iF;^c>C#_n=kjj_|?_mmsjuCn{j`8L#vwz64Q1S91SVT zv===k+m^r@qYhGsjmq+YdM6 zG-=I^<2=9Ro4R>dYvt+lRSU6O?N!mdZw#mPP-4tB($}T*ZWBWacC5OIlB8qL%{D=gPvnOGpcaq>_04Y_g&f0S^sXn+8-Li{Ygq0S%e$i8$9f&8F5U!HXd{GLalb zkq&u?3En(P*4VcOI=wE@l_6(K9W>IKW6_j+N9XR7mpJ9sW|gZ`yj4+7rt&}>;<1i!As`Tw=l3m72YNej@tyjH*$CDpeU zG*gFx4HobqxgjMoRY7vny)9*`+4$|hFjE*dt6Bsm;EWdAm_=xzs-U!Z2b5uZHI)7F z?H$!m*d!_G6dKX318+8mFx#yu3!Xjd-urO<{U5!**8cJS&PqPc$Im6)j&mIkB=2vE z)p>gUQLwp-DT+RRc4e|U2xPnIPp8Y>_D8?_$?Kcb7oWeHhuq)WUDYk~TZTYb*GIHA z6}U(DCMGd389GTW#koQpy%(8{Ui!wQ4hc55%}#>Q!#YM|pf|V1lVLsK6uh6Q0iEH{ zd76i^+Z_}PYZznogv#z>?aBF>+ju~pT`r*)mAGrF`?$b&h4_E8Wg#_Hp6GfvjZ z^g5?^K_6G2PRG%+tmczAViP3$2DBXi3=3(16+X((BvSYHMgBrXo#Mqi_als|uUh36Z+RCWBxK zYY`+F+dj4UIJQdx=8QX=wj|x&bzAu zns)=n3I)QV1f{3gP)ci2!x>=dxk!vYx=FRKw_FNM-e$L4nQCc@Mj!z;ktvcHfd(iC zxCJ^)REW;q>B(vGkjd6q1X>DW)MM@d?>e_)839CSrt0P-VFnRk(Ts+#XM&z{ou}Ln zeKQ8269)`Naj~fGjmoCsTb&YvyPGu+U1-L|$ZMgAiF&I|7HE)*1Ff~nia@%<6d8$woevDRzQJ7 z*6Knng5IUMbS6eP9SkCMrDg^+Rx+4Fp@s?48=5r}LR>4Pc_s)hef!E^Rp99$c+|_2%6?toME9J8RoGO{^zCQ{sRO?c6|0 z<;v#Up&%+wBWjXJ5az~u+)vxxW|}63O&lV1=xvyep-pc;zis=Me}5Rdiw(!aT@0sH zOQXG=Z;qe6xtqVb9iOarY0|tJ;dWHL7?}L)HgrK^+;qcTJ7J=`gD7!T&}$e^KD6;@ zXmHG}F;J~BHJ<`71Fg2!fW88Ol@6DLJR$%U46d=0;xk0N(H$*H{Kn_$WQD~@H)@bt zj1cQg=d(@ZR03x;*$J8ycMp3uQ@s z0_h514(zH=NxJNfBDGMX31@Fo>CqB6Qr3W3awUJQG3^9p!E8BL^YZ?@%pPRP2y}ca zu4(CXn?d7uIu33==PBoP>VmiP5H<*!Hh3kKF6I*?EKMUIskcs&_Uo1;{+2=sLg=+w3PQKDQnTUAs}XMvbVO1W zR?SIhjlPIL-9j-5(gZ9X0;`{+4;4$aN)rz*wK+jCxOwF{srG9D{%?J?{FWk$zwO=n znGr`C0t~E0+LAv&Gb%H_4P_=I2oz_$Ty{?&XD%M9??$6GvN*~RM z`gPg>XHzZK58|yLXFuZ9?tQkBPrBQvYe1hDkj$AEa@92(?VtbXd*#EAcI!<)oiH8e zX@80l6cCBNTMY_HDN%WUn&tH?S#5pZBMpB2c-M^Jlj$_3aJRbr{fp+-%pRNj==F!r zyIIz9I)$N^>CMzR(MjFL>fRfuC<$duLoBDD-CEAhVkd`r)UZO$mUAg`wOZwn(Y51= zi8TUVDDV}=gKcR`P<@PON*tZP^)n>Ao^tA(R_dXKS})P>hk)7C&TvQ z(f2;QfA{)LoqqCnzg+z+#v#_yKYf~lL7|fl??N|!F~9uPi`&WKKD_&gTU^J@D{0D5 zo9ol=tH3$@c-NO%#_D<#;{9Fd)2_a^GBbquRYHAl69FD8Z;v*Ag}2$)znZ?de|z}! zGf`gyTiYL7lT$P@5!AFX0-oV(=)~2H%mfCjp5-2-@V(;<20RK)#iy3azFdUs<9v}` zUpDl2Kf8;^XPu@=()bQfHrL&gFQcJh&TnSYzOwm%qOjl=HK7`T`f_p`v&90p{Kr+PW zj=)ZgU_g`rMS#fy0?-1Wcu~)?jFs z&1Wkixgb#*T3}NkNowigDPNR-u95nk+QqU!W*VcQfN<~4>6H?tR+kQ5fG)VrCC0R5 z%cX~~6gq>`8VbK5+B!k?u0g6BSk#l5hsb6K0$m_F>fa$gD$SgNJWpM>fx0_2$l~yY zbPk7`$STyLCMKw&PA`FARbgEy$21tVNmDD;95@?hWAKV43=B@8IBeOo8_togggOjp zfXc%p{OmPeysHbJzyQTLh02!m7ko)zbu=)jwCWb2fHqLPIaS;ma1PpaHkmZ^=cEib zZz0m%#mr?1zG&x?Ne=>V;IgxAwGd0cC^*%kmC zZuS1^v^r`($MbCyp$$z0tqM4qTS4&(pu=o<*7L-Q11B&EMBqDOc=lwtzJ9Njt9rVd z^G)ieNe*|n&Ft+|8+;<;&{;EYvW}7^pw!pR!-VVM6#A8!bxB?0ACUv~5DyqZijwH59Ka>9C$Ss(+~l$L-#`#Q}j*Bj`r`pImy!bpS` zyh%?L>>Y;c+>GJ)7;uk`p<(0<6#ob2ErdH#=gJvTN^QqU&}Nh|ZU-Bu``A4Tlj|n= z7ky`8yOsSsm(w9p5chI7&`xt{ zYMRl6g~DbeqqEW+z7%~0)ahWOH3DE-ntcu<#96C%6c1jSi8DG1mRXP!6sN&Z861U1 zr~WU0^5c)5uQr=ij2&t#Wm5fzLUf%nPzOrqUfjzvihvi&zy}ymR3TWJr?8CZJCS1!SI)TL|7x5=sH* zdD4{}_q5gh5#3m{*RYQF)8TY`_cy=zi_gFK^#04&Kl|*bzx?|zfA!++d<42GX#ePU z{^+0li~qyue|L|%Bp5xG)zozyPXy-S;xb)KF+Lrxhhcl)^+UHq$?k0#wkv_tR!Q!^3Hssacps&B_Qq zsc23ZoRqgVCUu_I^Vw=-`>vIV;7sGjdUt{k$m1Hzf>Jt%G3S3yF&`W zL`GtXvSS**ODzI6y!%GDO)m(BZNgLn>5u;F-oJn8QF)R&gwC14$w3H@E zbM`>@u<}-_SH>vNsB{U5F$#nsP@+g{rIw??rV^wYqc=DZ+bAIWD`9}Ei?SJ9A);^>q0>h^ z^oAll{>FBbOW>HhgF8{N(|HCxwWaUmHoG^KH5f=BTNnM5Pt&SRTZtBzZzCBwCJz-p zR|_GUv9oe?FKwd|tAr74BYBiz#;BMNhiJyjO&WCQj{6ZUy?#CDtm*&`r<1!YxdigY zsI^s6d09e&3ftyF;7$v10}BKwccCNDmc;3LdlUd)}im^HQvpzDTvZIIAY zRl_RsYU7)7J8Lp36TRPFqUCzLd4?Wj@c0zN3SDX%Jp>Et8a*Tm&C%>L z89%7~T4FaJR`vd3-^c9ZpS%4y4mk{7wZ@@wK$*TlGi&J+z9ZVD8?^0zN@5#)U;o zR*@N&P=li)>A8`D3e6CjeNAdFbEC8KP(^C!MMBX)K#^J}7C8$7v||NAj7p&e5WPim z?g$okOQ2%%y2um--I9Z7$PP4S>nThCD&~iPEjZDNiNGpC1c+Li1lD{u{nXmrsVcQK z0%pyuRMoPi+4&4NCz~@j6fn!m1zEFs0eq6RBJ}g@8p*h1Mp(57iA&I{p;m*rnVb7L zFyw2K6kGFUdyU3ro~jjdTlT(7hB?h)iU1e&aQ>JH5iVF80Xt_8QK(m+*v^&;q6Lo) z1I+`PHgnVzW|6pLxP-Q%MR+gfBpB3#G=oh5gq^lP^!gxz5imLl8hoZJ7@Z0RSDq=W zUc}&xCbL-!NYKtiKqp+`twrbJx>9gad9pwRQ6yCaM~Ev)=p(TfWEJ+&9%QEv17^3` z02)p*^i+T%lDwX1uxgA#>B-=Lu(Q^XIAyHTiYF2WL;TuZ*s!5A?N@!$)82pn^5z%s z@^l~Cc;dwq0o`!)4RKmDVWXwfA^aE z`uy(hKmUjS{PK&heq489D<5|kw_W+!Pk!-S^rNdkv`*8o*-D!3-?@7+yAhSUeTCk3 zt1VcGP0h3DD+c@W4jr<$cRsgMAt4jE+kRwoYq%fhKHQ()-0XjP{Q2kq+Q9$Ydi~^s z?(uH()yDXfaE^14603n%(UpGX844{j5(RhJR${9 zMg*H7f+0Sv7-pxy_5!EZcWJP zG^MT&kh;L;+~}1E;w6gAZ44Bb2-B3y7*18BMt7^$tSwVp^=iIkmx(O-&k&_MR8h0} z*%(8*>UqCK^|0i15fp@mY)h!bc?B(`t9R~jcXy|LEe^lb2?RGW1$CSFg0;; zb|O;f>a(~2v9V%dVPbGC@Ipk0el41(H}#+l`QRS?LI2^SOAz*q?zMRlsxK)$?uucF zDkM8=N1-AbQ^1l%WwTSh7GIK{tM|ziYe$6NHA~BlyKxlfAzPlZANO{A*St#DI)w0n zeu9bT)_Q0Ld@u_kgs!`5bpRc~3!3Msw*c%^zhkFv-CkVh{!5h-P>x-IT8S)_f_uUg8`j+EUge- zYq3s!-@omGs<^XK){eR$F5tfPaA2+auwS3ybt|}SV6)S<1JJ;eA*ea!VC;lq7!G#7 z#q4>uwyFvS-#iL_=(WzL!~LK#&&MF$W*sK8a`aMCuOSX`J4Y+h!H@+g3Q2z|C4uH8Dr^RiBt+2#lHyn>zm?7A#7>YpZEG58q``7 zQL78f(v~Gfz=lkh#R28;Guj=2Sdw*p`3;NWa=|*fLy5BqRw%-3;^wO6pfJ*RvJ)2H>@l(?93||Imz%kiQpV%d3GYv_RtF)X>#eQUSC{MSiwhZc zPG7yd?_vi4rM&$6qd%KIczO-65tb5%A^E>hhNH~TQ8n|E?M-Lxi~!43V&F?-i} z*@d||8Y%F~`GFD2Nf-;*OB#=9yEz&ktu$0W>frNn78&7NdT^kiZ+gfb>m)+29ep?hEn-w~{bCTBY&cvfjPBInHxDTzpvC>ix^>%WJ)# z%c`sM$wfbY^30#dbh|1sR6PB`vvhf}`}@y-_38in|N5UlJN#F{kAJZH@sB=v{KwyW z@;i^xI957IaqqfKf4a!GKdb)!v)vCXS{tyVA5YU4bvpd){?Fci@!9K7g^`{9@Y(Zv zAOG~v|6D`-cvwB(?E1|GYHMA{_iuA;bIFScY!==lZmis%ri<2^h71^aV0>&RA6D|{ znkinO<&&ki8)zj>1n|LLb++`hf{xgHO!!b-4`|B@|$)+MwOgL3F)Yqj=v+mKsDzy+4KN-EBF&o2MC~-RhF?2&NF$LuwSn zkh*>oqk9=I&}EHz!E3=J&}@dtSS$d|Ad=J(BHiGaqxQKr^9qU9sxkzZbA4XTb4xMg zx!R()hAewIMOT{@bAY+DL(OPSB4(}#3=%AwGblhm_W`v(qs#K_8mVxV=%OmMIyk1_ zwOV7NMRTOaS%F5Owpy5!UF*OF$&3h8s1>*1UMOj{5?m(-K26pFSzi@7HF7rJ1T^d>wSrjq_U=XL9(+E_S8X*=Ms{0Ickj>m1 zs1S#l*I%D)K~>zpF+`mKyhLk)bAiXQL9b@NNtp-SP!-7?-kjnp;?)>vz|19%%f;9W z&=xiYTT)mv7Ip#`iqk2`YtkbY(sGNxUgez-V^fDMTnW$a;lYLLfUAclP2CAPRH=yo+~MI2;vQW3tEVg?9Y-Q0`23kboodU{RN>aCYJTfvgO zjwomi5Oxp9v+V2ur&mDCrX>NkHbikivLVPxoiz$->S$94v^I{@H0LSTIf7l9t58R$ z1dSEOPTIOJ$k(OZJ?VxRs1on1@(3IA}@h`+Ro|F^3gsMsa%H#sv;7j%Lt= z-p5nJK$4_+&(PCnc&E~)xn_xiaaeb8vo*WlpT;+ieJwqjA03tZc2Pqd#g!=Q+P`R4 zSIs`$3<1~j==q12-P0dzKKkV8-Y)s&_Hi+RjV;rVx~p+|we6mUsXra=5DF`93V1u# za?IlxCVz~J8C}Tv-R<#o44bQAbC|i?wARXLuX8E8^`VxN<&e@;#zl+U2y^wySDLI@ zrx5ypHf`1rgghS07}Bs^^NbS;Nq?Ty#U)Xjkrx^(ITsKuXczUW*-lP81C?XG(ilBAPFAS=l8iV4^pPZM|wF zr#S%Tt<~(V8wois=@H%-wUw?oR$YyPnmt6RwZ)+WXl0=0q=MK&MV@z1M`)yq7B5>> zky#}fgo_On(nVPDoR&RXF_Y*x282Qh#WfZ!p>~LjpsNq&=f+ThbZ7TVYpApc5i^<# zXcV$J+XVDx324^D%#BoaA+1>q+PnqM&K{lujssTI+e+g9WIc_&fKhm z(S+Z0A3{Q{lmIi=_>Cp$YxQ?4r7YPIOR5SLXvK`KQfa~-tRP!$;Z0KZfr1Jpmd992cVxx@9ypb0ocq$NCTDX?t~a9i=Wh&3H&+h zj|yvGb8~PhwvOLGEHtMY7THb{hXFKeFkt|6(SAA#yc$6CE)ciHt8td*IvXMG0(S_Q zj1}H01SV%JuU8^CNHKN+QpJ>FgrEIw{MsVBghEV$jxrE!BXP8o!>Lh~hOfuq=l0hE zMIbu>=JVH|dKYORQALs<0#(B3wWDEG$`rTLalShoUcNf+$ELo9%Kl(Fl+AG2)k%jw z=ka*Tzw?ATbl00}|9u8joIC%FqTj(HtdCodR5@Hig*R9Cxxx5Ft<#b>K;-n_(@_(k;Hqsw7)H3;U4?TgnD+^lyT z-|XAiZ!e!beX>RW{N47W?>&3+?E9;aR*DO~b$@k_VHK;tIYDb1FY890tae+yZ7)81 z8`i4eXJ3wTbDMImfzfoB9<8o7t9Q4rKYNSAY1_v^2LPJqFy0^13iWXE(Jh~D?-_SP zr*pGGadOG?7;4U8YYZUYg>)Sx2kG|rhoPUFh0Rvw!Z}`UANgFvmq&2CxL!TKc=G0Q zy`G1R!=JzS*?RI1TVBuoMV1@$gHPG4ZR76AV+ijW?rz)hczh1YZ(D9`JrE5S_P1+D3is zw`nzw$39@h5O$YZVdNObT~qf9#WdH|Rn#$`2uZPPR#ekC&+3F#J)uZB8e&GJixmt} zDy%~g^eW;ZBsm`oIzSKttU@Dfw$s)VBnny~9O|gf(BL}fhT1~nGFpVVdmvl0uox=q z1PwEK`sixeQ*<_j2opfO`3#_;h)ZdO0&)FK{a8>phk)Pp^-u(&JQma1o}{jwK{BK> zIJB}wYV+2tL_vf*#83_A#F6&j$?pBu)?O)OD@&g2x0pG6*ydwtQ3Xd6YC{vqvi^$F zn&SbU;Aet8s<@d4(es=}+qX4~-|#cPWiDID5e*PBqqRmA_r?gJ!p#G`0uDqnD!fri zi_QiL&S7H>-yB;EB%8Qb7eF9|7M+D2oJItoa*Rj>Md^f!CDFx8MIj@3a#*$)#kHB8 z3Qxp!#|S}(v~Q=ac9~sn*^ue9(kx|obIfPF^uq;J85UkbY10gKn97Bu0ynSd-J`bH z#ZB+iOaoGASR@Z-@S>9sHQae)p%!DIAW8e7|3Pbv(skdp}P7eRtFO^*m!!g@b@o$9#mqHr@I@3_#Yd#uO6C zlelb3J7t>)0yOi6iJ?`}#G^13bBN{~V2?_>Gi4Y0)&*&Fz=Cg%+vECZ$h9Y%`v`dI zMd?eTHuU3r6a`3!4 z3fyyVw+`!N#eDBzOOK|ptWzlcsvP#+_NtW{_IHOgTuud#*4;c0(|9VU={UZ)KmF_b zU;OQx7xj4T=YG!OrRt%0haq5#h8FJ*P_aDEcW=kr`x~uQap7rzI}aI%fD1C1rxrIM zgruz(3u~s>Dt6F0MDbdve`dy-}NQt!MsXe;@rwn?V!0QaziQyVb&WrmUMd&M50RH9_adRc>*}+K%fhHc!%}p5WjEO!g@wLI|AmX`18&t&M<_1c1m@tq_2WCl^ zw4m_jUMYktr4j1xU|U|way8W5&Bb8iOKSNNNxJZDC@76l!Nv-MQv=N18&rwrVsv#s zb2OX_-JFny_U^;mzDtNSIfZ)ntKcYT4W;j+Aaf z!#Nm%0NC7H_2!HXfXb@*3^&nLvk8D>=~s;xdUB-<{9Ss5s{7!ap^gnNI_;~F3tY&x8%CFlmejAFvv z$@-WNT^&>!j&&G(9<6QiK{&vr&~uQXNpW?JS0cWW{aCME_R(mOINihgW19om_x0_~ zPfqygaVe{tuC8nv(#2hkp399y z8E@6i46(IRrzs#8#6E60AH)xiwYjh2e80EUhr8*%)Qi^IMc3F03tPeXjxcv4VdVX^ zy^vSlYNl*u>nX1LZHvL-QtP}D94B0F*@X~*%KL8Kzqq^m=}mj{7CGNW)9oX%R{J6K z<0+4HFF=aeAM2abt6Lj!e>bvQi$)$@*4}S$%F(Hila-ch_0CuEafbssC!%VVRN-rc zoBe5=EMk5~+w_~&bl4}|c-_17@JQUZvi(+gN2d`j1WmOxcN9006@h?Q1rfuh)?D0@ z_SVL&x{1iL(^Mq~VeO!zVd&TQhuhxf&F#C>+@Z@$l=adSXG22?S~Yj;W#)7gcG+H4)}*VXP3}>g_NDnpq42xVP3M$TFa0gwO8g!bO7lYw3OeoiQBEy@&>H zy6o8>WXoq)vSBHZ%Y>O~4}0(>ZQTGR8X1}wMOaeRmx-=Y7Kue9eL-agH!RZy13^Z& znRH*qyGv*2n_xm)NEzl<8-qdsHDrMiLa-9>+@Nw!Ltk|LXJn|Jhql_-#6zVE8X*GE z6%yRrxjptB9~4bIh=~Kp!6~AuAx}O(T;jN#5W;2=)sW#*q@v$jc1CZfA*?5x$(|zC z4(tS?qR|O>X?7-DTALmuo6B$Rk5-oMkAZhmBaU->HQs)8IHq!ll(sC(^)#QFUr65% z<>)6x-f2EuKmTP+e@JviRweD%bN{Yf3Vt=03K#OCj~B#;qE1C%Q_3(rx>)@b7c25` z^X`dDS1o#rL&tq-(sZKj0?|`NR)XnJZNAs;y5$?zPGuDB=lNcUZs5$9cG57MWtMC= z?X=ZZ-#Z(974DMkP`llB9;cIvyB~>R6?|^!QyUw`!TnT`W-qB8*V2G5v>xB*Z%Ex0=l#;Gwg%utYglkI}oZEA7;VtCt+$3NI@-`%{stL1L0hmsG+)6jQE(d%h^w+_|qaV~ul8@B74!?+1g@56n&>+JGn ztJ}eE^RSxkHmlw3`0P6Qww2d0UU-{AGU02D@1G-1mm_$%D3>8`H*>e|;`gto`Q_c0 zHwVkD>G3$^qfB?lgJt*aRa6;o?un|mwlhu_T_n}b!@BL;UHCAb&Ro7c`HX3DEB64r4fhArD*5K&7%jMqu)9H5?!q7@CJ=a|J;>@G zoIVIiyFA3+r(PU_eLXg}H?!>gs4CP1NzSsPNw=Lb-Pijy)i561X*zZ308qvsHu^FOF+Zof;|`* z#G1H6WNqzup{t{FAmEkWNFLjXvUVTcTxIQCG6)+T=1pbQd^!(97?jbydOjH}KK91On~EP)0-(uE-|DO_d}e-rn1Gt8|}PCH0!#?%$PGmjlK$ z72To(W}w0!&F!X-Hizf^`WIg|K7GE5_i{RnYNE-lUF<71^{)9!8h}Wz<^}HLZ9877 za^Cm-qY|+SK%O$oHn4HCzI`)a4rBJ+JhrRVm?53VWinyzs&%__Z_Axfjx}zBkIh$t z!ya(O6h&tS*feg^j!+YD*t8S4CeVd)Y*9;+?S6L}d_DQ8bI$IYF3ku)T4C~Sy#qV| z7qUboJK1lMB88wwwTtbe)-DX5)Ylifzc*2Y)6O8=2KQaF)*J}{q%dK#TLPOKkYQGe z??E0%G`D%XUElAk5|@`Y&cPtwj{D@4fjZbF8SN>?&{BXFs z)7`t{>lt=C)JK$K!s$@cU44WLofR}YR4 zi?18aYMI7zs)osGDGhe+51)>JxCCfsIXm-=|cSyx# z4(w8=?yy!18rw_GU@$I?`sGrH_N=oyTnM@ew18w>46uIITJFu}aaL)L4EXHDAY&UlNj*bmT-;>7tX1;_F#(p$E`itsx&G@R!b;X5 z#1=?Q6V$<#&IeCro^M{g{PfM~^IyF_)GE5!dUxA$aW?l&AG^ov$TGy3zdDMhk3V>F z++BVzkDJIhr_+9adz;HjB%6Ql`ttWL-oMyhZ{B+rN|nLhBwlAPtE~?GllPwQwikId z=v>B|x5xe6AP)$#463qTciKtU?{XcsPuzKB$LcrxxpyZDWo5(s72C0DM-Cg6 z&3YsKkVU^={eIJ5jk!<352u%CljW}8Ts;5&$gLhPjWvV?U?^RZ2d`-B}tN{i=D$PgR0(p&3uT6%*vX&o0`y%@RnGBumlLd z8Gir^fUv{@1mN}SuBxt_BEsFx><+3bBFv11T0~}bV`B?pi-?&ePNPKm3;Y^BU>p%YXUP>pw}Ei`F3A?5Eea$KG2D+IaM%#~O@nqbFzyw16kl zj~D%suAmKz(WUnmmVD4U=#dII0z&NYgzk@F;k{XlZMV&@+vCypZL}D}cHbWRwr*b| z+Sbr)H+%GUyMFuj?I%ArmsAU8>tc>90*B?CWtwtM1x^YhICekKTR9(p5_{kJh_F3K z6G^H`QV53~?WhRs3~JkhA)!U+)DFg>$PS)h!DF&4EFyiP8mWK_j$&r7B_|Y#EKs2U z5)tM@4isD*;r9@wsu(=!kvnFoQ_3?LMF=e=rNJVSz!H+kpcGA{WcX01IPHM1fg0?z z1erpPiA9DSR2+y3=}JqQ5uy|!NKyKG(TW0nKQr)`dR5Y2|Bgx#hJ!*95SFYI?bOLY zjjRF|V1W5pt9~)q9ShXKY=n``V$j`jcL3?wyCl^Bd9Q)fx&iT3k{t*q&K&!~J){lJIREcQbz-=GYmx`QLD8#IY^84I(&KZVh=651xI5@?5!}L z?@LHT-<@MhR6YABv^+0YoCa6}>ey%1)lekb%qb-qPo*hx^=kGRp&L5Ng|b}k7}PZHC*b#Nj?TX20lhC345kz z!9~hb%w!bgnNP!R2y%J~*H|PZj=gY}d{Gh9tmT|OLZ2_0==%I2#@NlmZT0vGUSHqX z2Kx2b8f$VyptmP)CKx#*LIatqmrK4~FZ7psdhj=lxah0U=b*(D;ACS#>pE8N8tcdL z_;#Ko;yTr4K7!GjuWf9*$N28^cmMJ7!>8YV`aXY}CGL?iSXDoyr%$I#jQurRgv?zW zvTp2~M0{Hx_4(;?xtxlw+`n#Z*yGHRiw`&J1M7~~eZ*KT!IAQTY*cLo*b0-A{dm!5 z!2lq5SAy&=F<=8-z>yG*O@TcCr!}%-uv_2w*rNbdK&rp(>%OBmNH?bsG~*UnMRT@3};kFV$ zJ!%)1RQKIeq+!C6y1HfoO`%TL$PP&%fGf`c16?YZshKJ%j^7m@0}S_}s+t%ExIkt- zwed!@!rEiqy*X>VXN2g!lZ8?NhTnb}R62l&VN!ee6QfbDNEtWrS$~|JZH=hJhw4=) z3U2gM++gwM5NMioouAZROI}|7`KyokI>zVM)jwZ)J+J@mx~*@YHJFM?N?PXcEG_mKx;!qgi<5M|U)@i4JD>E& z_e-cK@RPfV)&>0*PYhW6BqB1y*O2ph^`hNX-Xwn-`{QjBP5bj{hCwvltrL1TEF10u z!uHrDd6Z5$#SpvSb(xUM0nDl`T0QF{WN+0j+kD+i^)@JrwR=hDaGBP4Olg^?+hsi8 z>t1)n;>^y=^D^Zc6`rVIQS1vogyhgpA0abEW;;1jou3CJJ7PKxi3RAyvS?rZd?{%+ zD;VEtvB1mAIN9Un+v~0MeS3WQdH?eBPu8eO>IW(&iAh6CdD7F(dj0mTDf4wD#%k=2($lU+o7AUmx!A;ro1BIimL;?BiUDp?quO@3;18>+!sBUNZT^+veJT zzy0C<_D6Kz>hjF^kN^I2NlRG$_?su+;&VNxT1`*tCV1>yPuF{i$8~QbW}Q9;(4Bw! z7T>DT;8w21}{CF&7Y=tn|Elr=tKkJ`x!?N|kV^g{O|F zLw0&~`a~HOs)|V2gPFlW(R?yPO$bd$Nz*alX4U|-wVg!Yuh%U)0`yWq_K3_FVBTU( z%M6y88XOU0;K&3KMDQ5tMZg>~^%&5eCbka9J3jT7{b%P^_0(Y{1LF{G0{$+TW$Ee& zNaEIFstiB=8J$iTQH4Zs>;^!D>eUBJsw(i{!2G@~26E`|P$LtMd&*jWP*`@nc@2YEWzUtmIR5R$HqQ_5hcXMdxV4x zV^S)eVaKI|0}>vh6)+o-)FrY(ok%Xu#J15QVd@bH&5bBRL$sjO(OXIrM7LMJ9)m0?*37!PpEZ9#`-W5VKF4kEVf)A_P9=Xj$)ENU#`Up|7@Ovt zfiSPw*32$D;pIHfUw*v){XY)J`dG!Akhc*iK5)1S6( z|M~6u`Emc(>-FunuYKT#7qw-6Y4hcqhV(JIi}f{(BI{X=F&7+(-T)wMNtf=|GrM^w zr$9qH9xhJ=72%{d$_$UNa({c2l=A$9xQP}U)4h@wl)b7ycDRjbjod>%*tY9(@H;GZ zA1T&FW9un7amsyVN^Mx^WHDS*R8R_{O;Y_y9rFSge9Y%GQFWR_^8!n9NlV0-M(NxT z<+ID1?B_gdcPm0#!b?BG@Yd*+DVr2%K&ql?RtH=^}esypT2x~`L?dN z-QPZz&uva8^_rENj(NU_%2E`VTq6~m%1o@pY1@9zSub<_8G9m}g)ddgh_uBeDc5{i zF%rvn*!S~Owur3${nKe1b#0i+i^S+-{a=2+)e_6MGSYXK54~OO)h_7@O$Rk~`q**ss>d_Oh=5#@07mVd!Y|lFSr20wY55^p}iHAWUrZ|33MV z2!dMFj3N}ewVZq${TVSpl7N;DnphHeDK@s8=g=q&5kd|pP~vf;I}APX@mTlKB@F)V z^UJ{JiE48i0`?HF@%Ubb$}#{BZh? ziHIWc&|I6R5w#G5mRbf6hhK^rh~toZ^qVAsPSJN$jOx3q4_sLk6vr36h`@eP|@p{czYilOnr-<1q6cnN61W zc2SQYY|w3JGQ=dqAO$U$C`P0l%_N1WXj1F})niVTBLm|d5q~t0A|LAx>46LnGJ@ic zXrKsEl^E{`usA;Jn7%uhRD}?uDNG>)bRsz=LJ>?QoU|e7(1A#-LvoD>v7k#t7PC14 zb{sL%(bJl({Z0P$1X7EgcwgcKWtmlGJoTE^wD?oHKx<@@soV`-tYIHzCIqd zZ9boZ(zn-XnE-Of(^&3LC%^VTq#XA*cmFu-g=&=R@F(GC8QJk&Ib&Y|?8E#XZ*Tp@ zU;p^yPcNfifBNaC{+ia5_S;hu|6zID)(GCNF9o+)m$hA2_eaOHn=Dc{dy!nqduPFO zUJkFWDgswwu@xlHBX)QWHw1zT;OaaHZW}73q;UtP7%QXteh<#B`#qMhH^_q6gA?%p zI9BKxE~OQewUMy=e$^8mqfr-3Et$k=1eVhR5iA+Q;U^T+#p`)F?MeJ-3`RHwQ@ZT3 z&T@%q-QdxmJsGIyj}u~^Y39VKU@W$0=)f82c8xN#M-W)wGYP~|K7763e_kK^Y@pW+vnnb;$2 z=kzyzySMs7Zfl&+x4Hi2zKxRWRIgb-cl%}--+#B;Pq({$y+8i+$MCVyL{8DY{J;L6 z*OuFS{w>t+{b4Y-)o;;uB*_Xs>XnKE}$lc2?(yII}<(MY}> zgK0>D^+=Wbdfmr9dhdpbUIlX_B^P}Ua$wMhAUG;0QorN)k>v2MY5@dO9}~yp$Edy6(!hsSV|IIFrr1zs?zt! z_PDXMo6V~2P8l}t}vs7n8e{|%t{gTfm}ERq#qCk z*JC|UrN^LTs=Avd4H)M_CNqd2prQd0t$JL$AQHl%q>CWYjA;Ty-Kat-#Du~W9!Nn1 z5)m2L!y?>CVK(3Ph|p=X`w<`uBEpK4UGk0~<09$m7QRN9@?^0K%30Fg2T%g7$ds-R zFS;f5coB=SlT_8FUXZrt^kts^+t^OiQ%Z0BpjEg;-+{2#sm?L#?c4s%u$E=332);| z^I@jY03Ib_wDy4o4$IfR-oJfYr@r@b>*IsqkC*SXzd{x=-E`|SA&B*f!Gg$Fi4re(d9nNo%#R({g3|B(5V@)9bCP-KSF9w{QKsS>N9LyGwo`g1OH0t(Lvy=Web{d%LQf4Jp;X8zWBTb)BDQ zFU?&4ggA4be%c-w`~CWdp68b4KJaMmoMLanYP+9vOWW38UfQ>HZM`)!m5Fipeq1}c z7afF-8F>?OYV5+?|Dp&xCXz~~oM?S_;=s6!kyFALp(Rp@fJV?-h{>Kk4;kVDz?}48 za!07Ox!G8deR2^)NO)i{BKFq8#CIeQ0R|#tAQf0YAY0O+zO5*M zUpa~xBMz;c6Fmgj7$NllA0FIv8{LX9(h>In0+ncpT9E`1h)zxrq=9zaX1-5~K!@ou zPZ>v9KLhj;M~eTTVr2abEKMQ`wbinyQnO+x1K#&BSF|c^@Iy|C@tQ>L8z`Ko9OI>X zPT5yJi?_rL5lLhUTal6v(=z!G3dkuuG$nrrU;j3{2e0NX8B&AtQx_GRGY4|YFGdX10+`{+c$7}vjsX-Je zx9IQn~#zb!nV;o=zQyGS!%}3;z|7#0zEq?#7{fc>`$y^+}rDA z`iVT<9=G9VO_xpN# zLT*2OLH5Y$dSI_bWs;MWG%XHg(c~*4W(0n#Q1w62!J;l_hSvcl{P#?+h zkbHg@fCe>DBsn%{!DN($^6*ZnHGGw6ioK)nGSRoqQ4|QY5Zk%TkI=ju>S@?MFTt2s z+bidG@=_RdOz(y5STxIJ>2j`>|+_zLvARu-^XZj ztV_cS4KZ{KBZ`Ly>+bAa7%#i&Q|5gjkgP8l|Ml&s)6VaI`1Di1KQX_x?Nd6*Cr@tm zVVQi!1XDpk!N9(6A7gywSCm_znABfFZZ$j-~P*=AM0cN$#(7iDMvUryozWR zbeT%Juj5bG+ur^4v5TcS>tm?EoS=givN~g4*+Z4VnsW249fRut5vfzFvSjJqQ<2gA zNMxG182}w?MIO~e)vZC?l*=T2RMgqn@ktgs|TfqulVKSfsog6drGG_493^MZ=MFe zX*7bN1=gF-3`^W^V$B|pyKUWz0#?b*d722g#QI+9K2(q(zTLk(yiEqt6tk2`om{*pMz6O1S={s)Z-H6D5 z<<4QCKQVu~9de<&IbKIk%ao#z+aoUwq=ump&s}Z;N%T|Z){r$M1E|ovEMnbke@&;^ zu-tFwYOkIy`!~>6=C|Q_$k!euyr-EAuVp!JOW+%ZGY^;*X>@qKcEWE)X{8|1TTy=5QcFI0Uasp z%ve+GV%U%HTofqb5YL%?|7+=|1ELDD1G?wP@0HESx%M^!nPKb_oEZBt7VnjLw+_@@ zyuJ&(kqu3wwAjQcLUfg4^bRk9h6q4)vAqLvoK;s8T?UON#eoegtd_fHDQ-)l{`_FBxC4P zu^Z%XwST+l_-4iNNNoC^nO9)>D=y1)+#;w3IU5z8pD%7+_ED1DZ7bQ?CFK|!BIC@% zwT(<_MKnkjJ}9L+2$5lyaz-0-8a}2-(#`VByT>U&(Z{}5j7MMs6fiIjVnHTTEtV33Eu-OoC|jaxvPoF2D-c}hQE5?BXFY^6+)#rC$1KW{%?zy9H0Ud$;4z#4H* zGH_v001pW6&GGkOz3r{IK415v;IhBQ7v0%6sZXE#Lr(M0 zA4INBP0zXGw#g5_{dm0zz?9^}2MDL;_+C zQKinam?uM=!4*;9i3q%DzF0qYYKVAj;~bI#ZxWNQH%B2(fgP9yjdCAG$URLLyNw}x zl*&7}lsoS9(+N+j;q<`v6nRkgQ(RX5;@@#_-R$Wyxt_Bkb)bBT{6dR}a_{SNtr4YE z2ZH&si0&eDQ(fv?pJA;g025QU_elA#w~>OvIbPSX;sCGW`qHnDM{K_T^Hx5>en<<_ zTWsIS$yM$}pVGO`ak}177ke7qB$-YptUuq{a=r=K{U&ag^S0hT_U-4>$7YPX|EHX$4Z8W=hXwp*?c3}4;UsI+X&NaO%Ubg6rl@$l^<0y~l2FR5S$a)rWZxgZ zPUfAebDl-^COsostKkipK;+EPJ!Jr33nppJOOei)B%>Na0)^h75wVD)?m|(f2=O_y zIj#Hq+jC=}#9?e2{=Tg2Gf2!qSc(Ef*l5<(1Sw5(IK8!twpPv&QG~rm&TvP~fZH8d zJ<`UU=}MnAMyYIjK^@^)52AYs8%@3J`;Guad@g&Cxe&-lt0xyaK%~QD;_wx7z?6E% zqt~;-rn(J1osm#V&Pbw?W{)Q6a+IEuY=M1v9fsRBq&@=?mL)^+eLgK|eVgV_ zx!heotB>!18neTOZ2(o%@D1R3+@f_G$(V9JR$E-gH9toxY}(1hL)=Fmro!gDH_c~XEWAi~3{ z4$VB}9st=1nV3v{Dj9=3aA*n!F}))Q2vmwn$v%=yLYLAJXQnJ-L<464nLaau0LfXP z%X^7CBEf-QHY<#y0tb;<13lv5`%^l-jyFOjcpG;o7jVS=?kCk5aPvEp0?r8c?W%<` zg*TesMomjR>jIA5cExPs0A;jxz5c#GHY)@DI4>=NRrBnA3N_9PUcXHuJ2l|_I$rjF z`tbBa#i^Fpx3|&T=aQb;zkHbg11Zch)~C_`>5O?_`a#;g0 zSNr-63Vc3kK&EsKtr4%(p>}bUEVc~F@zBd1dL3kIOcW-u1*`=H<3^L1jjO|V5B{l?7#GMF@ldXaekRBAMum^16 zWD5Qo5fMcYieRo6q!D2U*LoS;in^E0aLKHq{gEH(ZJD3e@Oh)EC7W70){Ufjcys>? zAyB&M8|HJLZU4%-MTiH5rPfb>lY8%?6z2~-Q3M(vbdu#Z<2R2>Id9`KY8NewYW+#K@Pdj!m(~WncL1=*DQwNBJ<>TpR$qci$Iz<9d_VS@17X-E zo&g}99dC}c#=K)aTB8wYF{UxFvt;3B3;;?*hV_w#A#&DyfQ%IHaz#XRj>6g{ zN?;@;=U6#q5>m8Og$%88KFsV6yHit$l~^Tgvu9+NJmSIhl?VVD&+2m-`gCLsF}KYmtShS00fQ@0aNyBK(LL5o>s08%BDDT{j~QU%ok!E`+NiGZX`1Ys$yl6D{9bZ5}HfGY3z zBWY#;4iPGN0HvkOYzgJ4HKZUKn*~~6VY*U|{=uSpL!NcM+Ai1-XKyB1Bu%RmOtZwB zHA>Mk1uVkMHFGI*}}^w{qVv$6m5*uVVYkKfj}{xC1gQcCl9 z(y-G7Mb#fS$h7*sN)LY9v^>|-*YQ{?&U;L`)be#}`F8*9yVq;Pk|fT{zrH>`PrrN3 zU!O0CxP1BgN~v$x!&Igadw$-({EUb|dT{<5m8ZF))HKkQA-C6;uM>G+^1-1CfQTn+ zF9+HJB0>}O(ZuPKF$BGz6dkrho{2E-4P1hVhhP8)O#IG5aF;a4cK3bg=@yI_G9C}$ z%>AxKeAh`Hy`0wIvWI>^-O07c$JO2z&dTfz30z8OL=mFkmF+vPkLVem)(Jq)Oi4@Z zx`K{%vD5X+k|i&6OI@%en#E>E=Fb13Q=qH&Ph-CqS={#Rdgb%jAIto&KYhKwjqm^A z2k$0HE%522e)-cG-|w%da(Wa!M^2Hr6o$v?EDk)bPJ;r)rA3?Wdm8?#`P0-RKzjE0 zJOF_?9<%@B{qgpsXL_4hTHr(^=$Rk9=TwqUXf669WA4z$cTf4_bg2UYy-lk6_rwy3R-eLKz7t?n_#eg|UhLn!PP(?s&7964aWoOBp~``CR;=w|O% zkC0x<49A3skTSQnV;I4`71DR4=?Ef2!XsE&qV&1fsJrYlE22j?jHyhBKHP`B#%|zI zw|98J1L5F?Of+DhF%5V!bkOKF;)1(UIHiR7J?waF0s>&9Nqy}G?SCQe_CP(DUm9&< z+Cme34_121I$9L?9y-xBktw$UCS*S}tp~GR9JNab;1Q#8MSiNXq0WgsmvR(R+d{`O zWSe^uy}$YHn*>4s*%TcN+^3ezznxB}KvTw$K2-KaSx%?Sc{+QwGys3R zGr&>>0+!>jW+g_OjxW@_dDMYI^yUK`5@AE&69|k19nqX9K}Q_y$7v3)i~$Rm?<6_Y zBSut_Xdx%$L_L^CBf_HtianS%baqW+B}%M@tRtKcxLM22taPcGWX@4!C}b|Q6z-4d zbdpetDx%0>p;=DV$inCWC^Zogp6NnT7bIc?r{gr^f>9I-Rhi8PQ%J4|Xo`xVL6ws! z)9^i7(nMm)#7z9`onb_{-`}DN((VV&>4b15ATK%kL|UXhgv>&9M8qJoutW@H0bo9g zelU%-}AbpPBF3K&uO2XBVxj%7sVJfI8 z+^LE=(16aPE#6d+!mpf#p6nhvYYZbMQk;pAgSk!W8%~PH!lmI3oKhdII5A16R{wB&4#&9J2CjkOS@?QkT>IEHl+x)#|- z$mrih=lR3g|LOdk+&(=$@B6ynuLxY8pHOW~`Qzi?x6k!yvN18QUDb-75IN@n&>NRa z@Db7U$LWrj{eGVI7T@33uk-xf<9+a~5&~Fb`s)3$qMq$kniqU#j zx;h+97PpX|vp$Ai-0oC?i;x=4Mcun0L`*kY`DIuf7Rk^{cyr%?nfKNXf(UIL&oUB?}G88KZSYE&yb z{N@_$-OCJWf=nFr-h&l+h)9W?I672<4gv-qY#YKAz10#X7zj(A22M5h-9hy*vrM31 z3b+Gikt|AuC)nV$fFnH6Js7ML#4#qwi0DXyp~nPAPp~a?&=+^F!l{hSQyw&TVV^~B zI%_hgR&5%Yyl5IJ$v##_sd-WwB`7u*CTirIH8hGwS5=QR)Apz-xwwV~gD9W^2@EPE zX&9hF63LNt2Av``jG8Ip0ii?zdrm1~ibSA;1?V0wE14J`u@{|!-jE^v0AbF0-u;e< z4BI2XlqDf92AaQ-+5HYs^sL-|sF&5&Z}vdk8t)O9^%1G}mzmZ;CGfJ@>$k73-~Ygrrsu`}j69#< z<4YcXyUB-7=T9HnzNJ!hU2(rJzfv5657KWh*XQrPH>u0SSg%)%yWJ7OZ9C;@m}T7? zH1pA5#y3PBvQH{N&bd!{vJ43seYJR!QyiuP6&ryBN!$ZdL_fG0)Gf|u?H+gM0$dzH6y})UNw*9Ho?_pp?>?Bidu$!GbS;8ht5!|Xp!<5&RGC#x+P}uVU;!{E zK`Zxo#I71UgKKoN22UxX1RkpAke2s(zA&P}W5gH%(EH&QI%ommq`Cwe1QCEp<5cuG z4aAd1guv=tRs=m{K}1715n<>&K!c?TL}Mb5E2e3t%;6NB^QmYXQ#Bp2L#`gcZcG_r zcH7O$z4hQO3+KC9rCG5MULq-Jw-NerHIsobWU+i z!4*S}SkA9(7sokZ0UNA@k7!`|TykGbpWa9W$3UZ*9iSB$7 zcW@6@#E#MJgx)9-#oPq7(;%?NXk8P#q@bjGEIC*~i@=^A0W4U{=eMJ79_G|`` zAYQ&*KmPXPn8tFc`&H!2T`u!d|I=@`yoOlE!@EkSRbt z?nrKjQdClmE13|4OjLb@A;t=y5!Z+(U}Otv5eb#t1a*ye!ty9>nTN+D@B9gbUL!u? zXim)`IW$i}m7d#~WrTq79=hJy=8(M!A_zSd&=C3#vyH$eqnlEH#pgcgYfgzAejhS! zyMVl$Py2mEVC?H-jO#g{@1Lj7&rC9Zyy))pY%q9FFMs|LW-r&Bf@>dbAkihyYl>L# z-5n8lIiGdJvqp>!#5wV4o@H*cr{#=TaDm2InG<9~Pf!QCY1Y0^VnNwpfiy1*JAU`+ zvlZR{^`FSNq`vdiG3~8eKdi4Klc%(djM1igj$-*dmmnDxqnFC&N&qYRw*F>V4o{^gys9B*w5hq-#(SXx@mx zCQwy#%Nrs)n?{BbVi{8_#WhNtQC&M&Xh?1r-Kk;*w^6Z)sKl4swJ>Xzn5QZ^_QzxM zEkb(JoEv-$RrbkaFH(>YBldkSOoKq`N+3$we<3s>0xX$Bq7vWt8;O8i=^GH;?zCsEy5se z%F_(VPMs{pQqVPp=msE3Xb3Gr`Ky#F9ES=D2_Av$lf^(tcFUssY)gnYH~6WT!y*`M zGOkF0L1@H#du{+4aoDO9U@)1Sf_v(+<4IGiu&v3Q+(vWrrlroqVZI~#GDdfY7{HWW zCMGG9b!6s@CyZG`Swy`S$x_lpgo~=ufvnjLqytDI$n-!YfX%kf4Lh}IVM@i*tm1Azy|g@a{dnw|ywqtM zMKZDew{5C#oc|`Kg>H!c=|jEVlNLVKH=dsEvzX&!{npRr^`=jsJYtXF-smxnA)4HM zZjT3I$ ztW3`&@$D@SE0b?JEpA84Vdq~stzd>Ei-0$TAwbE%*g_&L_vn-K@EJ8x18omal5gHq z%Uenscx22OG2hZy9_r83$}$jrsDY?@>#v_n3hJM~Qpd&nOkyE{oo`U6IBhuvsA4 zzIkozmymtGhRr%ZVVP!>>Mtn1Ke9ygvBM_7O@qlZOAnw&Mu?3aqG5^CkWTLK`Ow~u zB()zl69)n&1Qg;K962%9G-nw=?>vWgW;JYQ@S%@Y#UEqu!(*5WEYmD44$+jtc7sHi z!bnB-H9tQ2xyO)zMUn0uHfbXgF_e3tRB1UGE!_jR;l~b?MW9bBZXCD29l1%6r3* z$R2cm%a$N`u$zyDQq?yv!cfl#cma}p51+I&vZ%OL%}KI^AaYd+Cq>mLun5iS2PN`d zVG15^d{BfUs%}gH8Td<@&Xqp+@en<5QW~8SINevNC+V5`%!H-498*FU|`(xZF!m5;6uh>jBzw3 z`8dsD^|ZI2FZj^c?QMI-x3`y<>&H)j+h70uJU`9neHUK)&p+MQ+wEuD;$Oav1I(SE zfu=jTt7PR}17D45;W-u^i9-<|V}DjyN9O<^5v#aJNVGSX^TPS^+f ze$Yiyz<};;Nww7Tl;dh=4MH$F*Iq`$J7qR^@AWLYV%h-E6DsIW^U|HqpPwbs&3o&& zwe5Yi(bE(&RjWi`^x+Igbj0DCt92q6AO)~7tapT4NU86ec}=O&8J0_F9Z4aN9?I57 z&fTKK&|HAXU|@Dwe{7y{plMqTSo50dm>h_CznHeCxllh)gIH?aTPsqwu{x=UPXVGo zEX@4zZ6DDeyN}*kYZj=$2684nT9Sf9bOv(KV4mq#QM*xfYcXYNHbyZV8XP7}^Q=PT zdJcM>KAf?xi`v>xQG4gO`0Z`C7VXyeZj{KXrIeI}kbn}wu|>c%E27dnQWhCwl)c=r zo=zusk7$;(`d@$)%E%nd3yKcNB-8-Jb5ilv!R$1RteG4G6sW)%(!EPaN-{7zTt*-{ zMwFLvND==31YAs)5AbB*+!T}*uN#w(_-VaGmlRgc@$K0OJ!3B=D=wm;X^u^Nx2t=ujZY@EvYIDE7P7O(;8%6uU(cBo$sSOi)Hxi-$tx&~zpP z6#`VWZ5oI&cY4w&OG51aAWisMIMfPFLk`W4hXV2m{da9QaWQN6>{@*5BrYN#N#SjpR4` z!y-1t-p#{+Cf3ZsHF58$8&lLAwG;@_Cqcn9PoF1Eb(*HB)bmqO4Ic(rcSn$bstX`l z)H_{SM}#QACWw01|ot-bF=f6m}sYNiC7q$O(B{iQnw*B&uOYO zR}oH?1-7skNhK*HAQWkC{f$Bqw<4jfikU z)aLW)SRVj_Z%7J%f00h-9)T0FfhWF)WH0BK z)`cZKyOuaT&t4*@NxfLobm%c>gO8X6Ls=i@B_cCH0xrydS-*X^?zb8+y9w7xd+@O; zO6gm>ZrvCC8FB?TH+ww#dA1g>)1?NMk;gLG>uUG*?e=*6a{a@fetvm*$fK{feSG@U z-#-85cju??e)m-V@s!T8KY4D>JMgmJf4=>%_T$!XpOFOiaZ)`0^ckNo=ie~tlvl?# ze1ofpLsiZTN`eZspgGEM56AtaR3-K=kGK1^|3CiaeScgEfA~1zKEOu`LF%Te1f59sK7OHhd02=HiE zgqbGSF3uRP5ZUtZ)`s^!24Lohl&gnN1;S7`6Nw}_m7Ho$5Z16%q(>KKT14za(LIm8 zdO%&u)5&Zqr<~K{G!~g-wh#JbCvSV(G==SbK-E<1Ff#|DdmuS`Ky|Pq)ESzYP@u!z znJZgEchurqPWf_TnNCwZ*P4^2q|hvcSbsC6c7N|l5OoF6PG1bVhBss}QaMci!IY~;IlB#5hWRZ_O^RX;tkwg&?QM9Mb#33cI zNJ>zRDmI(Aq*UOBz;NMU#PHT(Hj=f3GIU7VBvIJNI@PiyvQCL|Qp+jw#44)Pb7C!; zm6KYkl2cDfbBZKgMUwQQqo8J?BqoSvh=K`1Btap_n1dO|sGixF2xbVVt_0me;AERD zqDL&&%|`f&7~brc+vtey1h2OBxE%>&B>}hSA`{^vRhR=5UECh;GYgr0SQ|EojqnKF z2dLD{g8KY+F7G@g+lQ_Fu}XqG&F5*t^V-kl{5f29N(~W8`GD}xY!ddm&EDfrN$c|| zz&~&1b-v%O>FdvsmhyDw>Gbscw(f7nTym-N_WDZii6W8?!TE)owrT-~Nnko>TO{jp>v9;M4Qd_aEbL z&rknQo=?AhmhbQ-vn#HDK>si6m$q9$e40M3zkSSamhhkN}+Vh!);tfc@S5>f~0#dThR7glr}Oh{(Hz5EilSfpVY=!>!e3@b|4#1l--M zwfdJu_aK}246g`@$P~-!=EZ@w_I-tgtUb5zh}Iv?(*AAje$97~5)_eA>NKBD{ppma zItD;N11jmcjCUn9*?>$0f5Bps9rOlfrGmQ=K^dC!tg;MzD5aLOmNPU(nqXTCxw)wK zX5Lo54l(!Ani;IS3R5CQ8bUVe1_U@*SIz^4rW(b0iWt-x?!X%Gwl_NfG^yb{%Pk>@ z!RR0(!DMvw1Sx>Pi9vuE37et;BOrpVcqlf-D(C?vDDMpC9Rj37P8$;{` zrmXHeV4}0z9cHn+M(lu-VnZ}p7VOns-fmy}x^BD-dq*Rm1k>sKyTAL5dC|l@1%}_0 zZPW1R{jF2JarU9l`@{eIZ9dQE=ViV<#4W|RZewfBbNV#pkH}wJi|;;{Z3i&TGm;LY zq%=>pZ`(MSdCv=oRLkfO43I1qU%O?8iOhR{kkYQ9fradE-4+6hvmh%ibu|zhTy$Hy(3v!@1za?tKG=(S8U2>j*uq20(b&t^1 z@8Dg+ZUe-)ueJ|vTNC@4+hYa0G&iU0ZM*ILvhTMqukPlpt+tI3oF+AU{&0Tg^XJdM z`aN`H!JACZxhkoi;C{rvlR`46>U+`Yca z*B2l=xi}*J%3eew39Cjx|C%}<>G)KVa@h0WW!_WNGy&moi-(Vg2ljp6 z_bd2A`jp5VMI$xeSAXQl?J`nXnhUnQ$F?`~tR;yg(ZrM_Qe_yTN*kd9Vyd1+l2WoR zWj>!znCGPyot7mzkaHe_q?C{nKCE5W$NKizR%MOw-5`kJwdMqcvQn$l5i2|yNsbf* z=?oF(B6XT_f+i77lE6e^uBB?YNQyY+s3*?lqUAJK);Uj-A>!Ff5LyXWkdfjYAt!(Z zvLgd3_%${ukn}FiJ3_{!DHu?JOHv4U5MEMfisN+$k)*<$L=>P(vPeV;;c@psI8~Gm z=^~koZLqaH5yN_q)y-k#3KmO4bgK1~#d5ASQJ4aS*|MgRQYlH3NFq6NIfQ zDbOHAVr(OgzRfu`45%952m%QhOnD0J0Wjn^v!ZDk&3aJD65YbH`;HvpD@qj^?#iGf zim0Sa>?)-NCW!&5>$8>}tmF>N2q7?)oJ_@#>I|yLNzz`VPK*#}oWlo^!-r^~XGWrC zMWi@>@Qpo3+7+pf(f1Xjk1;~dN$#7=lD)^Z`hxrzL*~rl%A{dM+3FSe`D4?f%=3pXBrS z9-mM3J7xY?TJ-%m$!<|v?C}^q+(uZ!VDtzDd=F359`BDp_pauV4q4q`nG=V@BQ&`U zN(6jZKG49B1PEOuH0W?m(tC+xiuX=0pwe^k5vuyv3jnnQR7i2lAsQf?C7pgT0cV9E zGy(=B>((-f@p#k`u%7*se`JxB`zdO_U0=qIJ{YE!MO1L?-VQ>WlPQOVroxhvB5BoH z^HOR`DyLjZnmvc6uvoV0(`fX*@6m1aZ6A(ddj*LQg&MSoAgxJelL3*4ra=v5WF?40 z?UNj8reZ{71i?&8>Pb**$+ad3^IRr7FKIcKrAV2JCZS3aDyX^emt&IgfG4DMqfTZ zOi@5eI8cI4CME+j1<+7Eq__eFOe0B-Y)K$g7bxjY@c@<0BDF^}-Yh;o5awYXiT~Zj zVn9eC0D)btQ7R?B+NQdgy#XX@zy#TU(G%|7y7$YZ6=jTYpwXw$2klZm<9mGn@#%K{ z{^e)1d~DSG)L%;cU>0XhzxDk8!SMA9Jw4U)x9rlJQks`lymb4+RKF|J&qJ1vPrtc8 zcJO82mRH`kfA+usyNlnz6r>1GPnSfBOeIM>JNZ$Uxvh_{`~FkkWSP(B=kE4q@*RAA zyCR&6PB)j~O)CM{QudH^x;0>IOgV~JVHPzXBL*IUOdMwP?Y`@j(|}TjwW?sY5o)dE z*mqk>G>bWP(F94P7~sM=OB4i&ir~{bb`miK0@T8SrmpPjnL9AAZpqGjrm^JVn~a)9 zcn!}U$s3vGl3kFXNhSn}E-c$i)2ATB%uXL!XAhRl7bLz z&gENg-SNlQsL9%ScDbNrH=jmdQBShow?+KzhN6AbDG?SC63_;NEtoodz*{#5dXET^ z2GpZ8S6~6nfyBUQkx8A(Feb6J+q&)C;Q)y_2_^M_?rn!<_+C|pQxToHgzYe}qFb5% z&0`iwzIO>Vb|6|Okn3?42Y`Z+* zP`%L@DlAC{oMER9f|HvtAxjpVW5B}=$fhKc4midx14(qGG^?kSC5;br{r>Y=IHj!1 z{AsS9vZ&-)Qc0Rrsh)*`PFSG$QGS91^sq%i9ifTdBdJ6<@vgcZjv*9Dd2pvH5iZOe zR7609VWD(~rf`BD2q1E1gyj4l2?D4%2>D78ae9c99!^$|9{yL$`nTmlOW_*bc(#wj0J>81L9W_SOnVeMTik3sK0c8 zfC}X$ogU?hPxAbNZ&!A8;G{tncgiDvtC(|rN_@@FOQ~_JJyk>#+o&-_;_^@{_w{GH z{q2WdPjo*&);*1~k9~U`DKk!p!zF@TIM-BEBjZb|wY^LqKH~8+zM^Bi`Z#lX&XPqy zp(9qi?|=R6hybnuP6$7RM2kw>QZB-tbuOnqrT~_Gp==0mmrW^EnSs zkS?O`s~o`;5wH#b(a$WHmw6I0a0X}BQcnH>&r#6Brb~ji$l#cs4Qu!uo4bJ=9u_>$ zGd2$2&t7)GeK1sn(f80IN}Lkzzu*ZRCB5yN|Fn%-Yf?gTLUYK9oUjwIavE_PU4_mF z_K2(u^%{neBkf@VY7ggoZLqNylo&vjU}72pB}PzN@XTQ)?Vg1!VFMh06;j@t)ohzc z0wb)1QM8JBF{9*4&|2sm?thiY{q=DRQh=IO0!IFoW(pBNsEwp`0OE*dJyZ7mQ9}*w z-tR4XY7&uDxf62)jP5Z+Bmf~JRXTHqM9E93I@PlfHE~2L;;7R|JMTXFXtCa#_C5qL znwcFT(?g#4evB`^d#(&jkpi6@8|uuBsm{=X@KnOQR9##U=)zQ(m;_8LL-u8!YnnCJ zn&(o>lx984Br%uJ%%r&#$x2GliT5#u`up(ueK2Fm8RUB#fa(~6{Fj8uVT&3^9+r`u zophl(91-rql;F-RY9mM(Rbn`y&=CZTOoz^wWKMJ^a~L@qV#F_`%>WcJ!wnr0j@$zh zQd1P&GN*<-Nd&V9YGsrh0(B9EzJECk0*W9wgjt>BP{0s^(9j?u!9WJ=xJ4KkM>_Mk zHahkXfBn04h+CkV&g3^R`Tvj>^nnqf;1*sQgoqI=3=3vhM+3@xWhi1lAaE7iz>)%Y zrdl9-)4Z^?M2xv3&gHy3FJ-ux$3hR)>xRN@fcMbL`FXm$bZ`5cmI+VYUe4q|QCZE; zQ>l!`?X6#abHDpEooeCVe3*Z_-)>)j{_&^$En)dMFDh~x5%=cZdw6@EOTtZ}K7BfW z`Sw$Z^n3qSL~G=>Je#BhzZ>$vJYidZ{Z6r@NQ%3~5^vXd_QADk_K`MkIm)x>12-)f z>s*7WZ<=!zh)9e_T266)5dRV27|BT(Y2J9$5-+oKa+M9ESU4}-*7Bcs? z%;jdt_RDdz`lqtJ&bRGHz1_ntmy*SMx-dE2EGQf$(sjg$1nybVbR*ZK) zAcho-2od^j$W|h^(1h?|P93DINJU}>N2rQCQs|%o1hx?o_JEX>61`i3Ar}~gWP*T^ zz@Yelr?6#=1Oy5YP-^l@?Suy-Dt!w~rc4JqAq)+qMs^M$ZOyiQT}o6-)DO#Yez}z_ zVi~qUh+$C^39VVHrc$e_&ht|)CCwQgHPTzY+k=bLa9r)t*IR6lyAO2;AW_3nYmIR0 zU~=y;9UUMef|8B{g9vbZlpJYc+d+g3VW^=A`Vx7iIweDB zo{%8KLS)XCv=&og5`aOGNaAu}fdD`QL3nX7sEDf}2sMH7Xo~SDzC*wP`(>q&V1EP94)dDD97w8!2G1?wXb9mERH)Ve|0Yv4mM;#P^ETL z@-fx4+o+R7oNbuOb3WyCO7a@?F1feyq;a`FZuhtOQa+8#O_nLQKUHmvKF9Nl*K*eV z_MB!8Y*W1L8ve)Yw-5KzzJB-lA4hu3^+VU!ou}4wYFp&x67ak8gb+Z?d}e=K%5UaR zFY&-n|Mc|Z_YMAhc{-O|B?27XGrR@$BwXLc>3e67ps=JlA|!>vl+YXx^0EaZW*;DofECjx{{58IKkkdXk#gGYGs5l@j{!ELFZAlEtNu`1QeMTae?q*;R)eH6bZ zE@#O(51|x|eeJnhi@qZKb{i5VJR=^vO+w8C0*?T=D<+-2Ar?nAL@k>`h?39_7W#;9 ziQK>K`%gdLL?sX-$}LK4`@scAX;0`oPQi!3nB6>ER38lAGiVWL-j2{V0Y*~Iy+#_- zC|(A=NP`Q|Ei?hcK#Airht8rok`AY}9of)`X@q1)sHjo10Z~vJT8c7auOj>VqlHZN z_x(J!sDhR;1s#+g)vZIFXb?(>gbz&x5}C*d<}4yd?dRcqUb(SDz#; zSyXaKMH9ACma_c+2>a6>$(AKs3|q_0-S-d?nORk5xI=fpejFePAOz|ELw|q}K~WS4 zPW|}0U-uo(IW=TP#2(z;%=BSL)jhYH1oJ~xA+s`L*gM_L*QlDtGH<+V7A|7GYLDH; zZa;^6-&=3qn5Qq^69S{=YGz~F! z4Ka#D4}uU0b+rWau98EuJ3!U01dJUR5@9%+q?|mm!FpJOjs|!1lt~k$1aA@i76C32 znz&nT0JS4Ag?iV{4O-C)%#zBGEDs&|q=TjusvHzLjuIk7feI0fZwq}SKmZpBCZJ11 zBTA&uZjKyZ^GscU1{2WW>1zyY06;oUsT#BZO;b6m)Okh>0`G)Lki zJ5)*Pt#6_yMwqM|G#<3sU379=Nz6kr$I^WS5I9F%OFqLFDtiP?=et)W zPRuRwH<$JPHsP|}-ej0u4<%8xK-g#XGwu2LdB*d9ndBeFWFLOHuJ%s9FXt!jd$i`j z8`eYIT!J;kK5M!X@vC>BSKc=Na@O>>yNs$R_Av^@1zYDWB>IwzjZ9_}z9xTlYS<>;1mBe6P83k=Oq7%(|qm3TykieR_I# z#%lZL;jMi+r|kEj{%QG-+ZW=+LT)~^-MY2fgWw#wUM^|((;y2^1v@krT0{%P-l{okaClUTk&z=( zi~&!AFcfweQBz1Sc@Go~Y4@#Y#7JL7=CKe7wd+_{#1O0vSqe1Rjtn}EHTF?*o_dRuq3=tHkK_lV>10;ON)|n@|6HEh@EJLcbD@>zqKCTv{`?NO0 z$GTNiSoVtyaydoG0+~vApNa9Aj5V)Q&G5+=3yXH?d%#@A?F&s9cAP%9D2>QV=<55q=7H~(6#}J#0O*KqHYXZzj3AJR}EgDe> zP{0%xLd$W;B#{K&Q642GQiKL}BCCqiU3vz=903gzTI&KRC6es9xNQMa=_n2xLti0E zUEnA7nJkbTiYQYVWYUxX3IiG0d03VhLNY)@5D*WdDbXU5Ff`0k5=RKa;Ri*KLIbV` ze1#55BpkWPKmy@(Nj=rYHtdy`ON3~FcSkp0zd1iiqDb`MK*~hsu=S$DtF53?Mnn}z zP!Y{-5k0xxg%F1NLDbqZ=9AdMboP3f@lw2uT8CaNwD|k@P@BrygAg5g4(>)rW;=LFyk*%0~U z;1mF910pd^BXWX-$Tr1EHGqJa=$cQbZKSzd!&%PxvKSH~Nk&q5Rf*mr0(bio*kWOm zzPG63ZC^Gl`evW{vR@zDF4_z<08Q zY*|W*3K>wqo`ZWDiq*ql?nerx0f1sViZ!e?1vq<#QO40~yU1tdZQz;lU>y%YgkcWYi@8>S-c1mM^i_h_jXlrfFEcIr6zgv&TuD5+V z*_ZsNRt!)mC6Dp&e{;$uiNBk3($nR6kl{SM`|v)lInAPw0y>kXQ<|6|N5TwBbrkl@ z;?YEg!@QEiGvSJ$Fei!>hACh`2}TmgQB*6DDk*dAjY)%Qu zh?Ow}tK$h7l8MLx6)m$A<2oJw|&!4y+Lfcyqr6!diHPDiFRI03Fn;aZo5OdfKAS)I0qOijMA^ zAX`|#5KUz^Y;O1C_;Fl=8Wh19Eg&9vV;N%^+l^y;g2b%nt6dS-xIBONiooafHhkgr zedvV| zadux--`i4|0PLc(MhXCN^SETW#%bENd2Vm%?Dy+3&H%6ZNv*}KWp~N4K~BT4x2ifK zHU}4(=C9Z09(V?XIAgi<1ZHrUagASS~?VTfgPty zF1Ly~ma9#+?~jAsQkWwQTtD+tNB#;$NDK%n1>Y#Dom?j_r=@?stV;$*!bW;S3j4OX=@-5Zu=D_L}W~ZOw&|unHP~@X2iKZGvT)1qT6-3Rfw*ev2OG* zZWRGA`m8gC{ZT>8 zVXP1{J-}HX^{|AP?>dgDaP$Zw`p(Fi#TlN2R!TAnBgEkeB2hX)n7{(1f2BhSVTgCj ztcDbnju0ZJ`iQ_jZmY*6AfY^3mJg6NJ1vEqq#SNk6gU|0BVzytxPL8mC6GBFK@bK< z-R_(kdS+4B#@C2O`MTMB10REYU0_2 z2h=CPF&zYf0zo!*L>$%3V2gvP5|Ie&o(xDpjYteI)<<@^(y1z=M}xX1iKD_2AaIL_ zfQJXbF!L1@f7tFNs2=+x!a_&4N%%j7*@_P;{cbK# zs|QZv(4G2$K2PDnVX^7r{jC0ZgGJmyf6FyRPMz99hkwzi#t+>{kXBn@@gUS5y(X#W+N*TUOFG zNh5(tG)Sy|nnm<1tO{G@d;%J&5^y>9g=<;oa>V#;;6$Xy*Aq%ZCK+~6Ot>WPB!#Q6Kl8qUEq@ea6*9hAqc$D@>p+vN=`a z5HauZw`thtT+4Va<#`^TLHO+cv&g`Z%HdP*&m7`}BGS;(rCE9@{a#F}xHNn`rSq5H zapxyrdY$xBa4s?G{xgsIKGFwU0pz17j6}&s|I~ke-Tiae|5j4zU^2rsAOmO!0r%}b z#2YyGukBmkTun|9q+E2Z8;_Db!jgp0BTjH**|8hKq9f3y86fm2EFwjS_oPD~d~C)( zDC?>rI*|LQXiPbwBE!8&z!4I3AXl)!BG}CDK2?Dk6ZFQ*V{B)14k$Jk}4`%^+YRfzOen`{(K77?I` zE41ejk#R0(wc+7~B{E~3#u0jh%!%PMvtcQAw9 zYzxh-Nme3&6s(9d5KKWKP((xrP@#k(3QR>*y3h;AAS67Abfvj__u_CwI@)m$uU-T! zKtYlNm~Y~6qbn0z2L&P#VitH@g6KzpCjjTP<#YxFSedi!ccznSFxu{(WJ(WCSHMSL zeJn>mWyA&<1qq}_CW^|D!zF%Ma-{`rCc`k6&)jajn;s;$ny!{7biUiN*Cd``iCIp8v1y`rrKF z3*s_gPAL_x@_UWz)#csg;`WT{mJ~KT>xs)RUvjm(zkPYbb!chS{`rD0d)qb}43pyx z{H*Dz_b+)6_*44wdAnTn^Y!}SNq_mJ|0b6xQiov%5M_Ga*0(%9)%9thWlguMKR=O0 z(3h4hmE87qJmu5cILqso+Bg(;kDN~78+5u`q-JGG258DX!O2=n>XEl-}|uXHIR!C~*@?H3G1%o-q!y zSpbzNmQ7k>M@EDqh&}cheha5H?a-W4%E;qw2R1;|OFen%;+}J^W=$3WDvzJF|KSG zL6)j90+C^JEFPWg-Qxx0c^IEB^Z(`V)6=_uk0F@^J&4V*sYO6Cro>%U4r?t%A%qY; zAP^OlQ8fU};UNqrm6Rg7rZJQ*;z^`4tGhht#;@{8`x+JQk7ALQU~YP3hX~vy5C%S! z;0i*55(4^BLBarg9O}_SsR9wGu+jq>kFD7SJw#1K!YQh51ET}YQ3xGD0yRPtb9X0^ z0C9-3pTxZ|vJ?&@r|FdPOdAWv5>!($(F`CRbzz8*&PWs$h=UC6Ip7`UO*$wcP2)%a z`nF|3AQFNO94$qL2Q2v26Dxo|YDpqkJ0^5s2*_@^faY;j{K#LL#{nTkm^|7y`Mp3L z9iarxmt!}q1~OC#gdighw%@8*l)L zLgYTB@`nUhFB zn<05`rylV%NOu`p8lMm>z*KFS=n>k6ap-s7Nn_;5-g2d7m98chZ6|c=8|uCq?{Dqe zwg7YqNA*;;HzBH@awu&c-`(2hc~QwqvBjb#wCX|~8*Ztk|8 z^ZPH`?Rh+7PR4eTn!;Pjv<5Au$vtqh``q$|Z5rM)K63xygQfiZ673#emOgM|>Z#jy zA+LM+E-lf9F0$zFhQ`H1 zctwXPk&$eS9B9!UyG0TYXv&-QS#bB16y4wnaAbj3SVDAzr&O%BNBI^b6YY?{vDcP# z+hQVC_M*x8}K4tjmq+-lEqKL1->{Y!WJr2PPBQQcL&T zr=&13mtjDl2pG~+G@(>pF1n4=_}$CX(`oqO`8f@}^^oKUsD!W_QhvDQ3KkU^JR-s* z5(1RKL^|M3l_A15=Gw3lddSjwDYg4>E!TLnFb0A;DuYmqXbZ(nXHpALQ2qsy`I{ z-;S)ue>mPJ56EM8Ji1DV{rF0UT3teQ6sI|%$zx%DMgz;SUr@$?|L;cjetrM3AbH!9 za2`mEh#E43n757NT%5>UL(6`pMb7Arrv$4ZkefxR&_|M4m>AUOa{N4BCd}`#W$J#lb4v&G{ ztI#V20?wL0%A4+)n#Q!WDZA$%@Aog046swjXTi2Jr}I-;7C+B351g_$SvEYKg~2z~ zX)3y|ewx|4bI7(U+upFtnD2`sa`-x2=KT$Ov!SfJKaEG`D}Xo!MK}%z+X|nG^!_%b zAKKe9ArEWUF?xZhOA z;I=rqEbLs)Ksj@^bCfUZ_|#)O*#pW* zT}8ZFCt)=OC8h9G7~vvpfiUU4f&eWXUybsMv$(@XlI{yHmBwUR)|5 zY{6W!mxa5EC|t65V4(OQgGp<0EC~T@K#Gv&B1Mat7v)Z&*#Uo)N7M^ri;flEVszQg z!*fupInY8PipHLL$8aJ@#NMJ?w{^K~&1~)Nsf-FDxYGtLA!rk%iF5`{cCJ&`t-O^z z*NCaYr*t?>bckkK%Cq!we!k4*Z~o?oJb(E9yC41fzjV0=p%KGAEQ1x%7Q=vR9rsK~ z1Sw2o2vP)d^q?rRK736mf)Hwup^cFe;k}0x9Q>6FR3iSh4Y3m$G`Qb zeYLp-NGCMn+xkT$Jq#`B(HqYcs3Z^`!o$54a!mQ82q@0Z9$!Uz?hz4LU_gR&6kr2t zctD91NvVcVGGssmLw?OF3n6}dSqT?~#qpBAm5&e+wD5dz5IrM1{trLmFMrFoJ(5S^ zFp$b$UOcn+6?w`du&2VIch6sW3>blUBt0B&pgT=)-~Ij5{mYjx^tW?>!eRK^<)?OHU1Yg_{xZkYC_hB`a=z?emV2JcVAe&8 zEu(_(=5)L6KaSY%GQ^;Ne8*+){zYDs;?&>S^?k{g^VCetqfO<~x76&Du2?%D%JU1% zQnFpkqDJ(H&-tWL4cr7&%>}}=^{AVAEYS;ijUlBW?_ds}5YiB3uu=-WNZ(>`O@rU1 zwU*C@^`+joE|^XwHnhC}>k7$CeUm(?$K7qt$#+j#uqk?bsquEnXAp@|5ZheH}Bq#ZcsVlSOnsWr*U&ewlw1E~-y&8*8xj z>sWI1cOE~_5gzvqm#xXf9-hN0WnA@rvpV^AEFaI@vWtm?0*CgE5EzdiOc3(u( z#2f%UP#c6Gz4@PCmw))DKmLcG|GA}px?p9#?}?1;M(X;TcZ;;cq_@iOzH^*fLr+MR&Bz|RfX;-c!ayG5eylcA z(RZg|`tbBT#`AA~KXrS~yYgg*g=TLzRYhAPCWG$Y3If(0{X*bwULy(T$H_rT`RlP~j2}&jBL<12bWSM;yy+ z#xbr^kQ`=68DWqFH&Cc83ETjWV?>rvg5nu$8WJJ}flxqP5V0nO0h-`~iepKgZ+%lW z2@R+7kj`oXvnEZDo&l3clmu}G$zY-*99^i0hr|UzdS(JT&2${&!?&o3a zOIA9|q*jVXF=7*uh@2uN-)(TYZf$R&^RC@H0@;12wfS3*5%sDYn$ggnkVo~wn566INpD%{ z>73^Kym^}6f?LULVm)aau%EYmOzCf`oyYXD)b|bZq~G;6w}#T~^Oid<>(-K;tiAhV z{h`W)I@F%S-TS+xYj5$=RJ3gUl*Ut;o_rWjl!$jL?oaE`S9D4#ObSK=5YcyQj$PoC zdrn{0_3iUI<-Vpcb5)Fe?XHoArju#UvO|26PIV|k&5!1^Oj-{yqO0}p^1G}RU zky{XwSqMTp1VAuJ7&3zN9zDWq)bMI%BdkZu9SL2qP1qE;uosC_v=Q-yP78)5nj$ho zVI{mEj>*m1PF6wN=}q0VhxX9EYp-g%_gh;VZ@q7Jz3ukq5mB>JQZ6!|BTZMw-nj0z z)xPf4{83*dC4XHU7-WTzElZ?SbBJnEu*cr9y^r>kZHz6X?tPJ{8Cy%`<-Gsq$LTkx zr{^C}a`7oU*Dsk(MaH#++mdqfZDb$89l1U7cgcYYPqYxG7=a8Z5#m`a9rf7Ja}uFA zBLzTK6Mx{%ZvS?46+jH#4!3Kl2IT=>!$Bhme<+oX1u-5$zu$m04XK)ns< z0X8Cp-!=_Clv1o^fBU!p_~{>qf9lKp?zf|QX@9-}x21%9{LTEgNW=7Wksg!+7#v)D9kcUw z;h1rK-9>VB=Wl%fg!z2C{c+8wpI%?bKi}VO^DzCFzy0BRmG*i16p@1%cXT5sKvO=Q zXDT%kqu*WT%|BV>>wwD~D zM40PYwIU5V-^&+p0f+(f2pZ%;{n98M|{wvRn?dcM?f z#%*}b{VZ>B2c7$69z=T_Ez4sa_|s3H|JVQdA3lBgytaNGPcy`OyVcyH?x@GTzeh`8 z08^w6Y%mIA??F0Ti|P)c45ztOs|E0?DaWMYz4!Aw@1*WONJW>!S0t}|-FnMhwrW$6z!Xi$O?9%c-II8r`3 z&dH{cB9e;`5&r0j4*yzlYT(y~-5~VfYe`pCvq#)>`a0Kh&nbX`VMU8V_l~|Koul<7 z578_hbG)g{-yk!uMvCa^YeoWq5IrePK&kf7Pg~=9EGXXA|x;X5h;od zFIx2Tztr{f@VY_7rvo_PH;F=cz{5he7^9iDe!`rg zaxTnJRmnNAoTe1BIU)!b_j@4NRY)Ez?z;Hm5n zXZ$F4opb80|+-uNwoIR6rzU~+)5d0RXS)EDO*b(J8P?E z9_Gt!3pd+>t0>813{CD0QQUuhu&-z%x*kBP)Wn=~BNWg?9Z*CXC5Ff;Y0l|152w@k zZvOFy@i+6kx@p0wAl_)`5rsS}Do;t*oCXai_BpzTXUTlnTA{#1NaV^mlBti_At#(D z?1A2oRa7ECT0?-zy?4SDo{mxZuOWDTb-=i%EFuvh(T!oC5nnB&hNB)mj$Jv|HIe}em5@dwJfMy4RKNsbpdk`M@CZIa2zw@&fFM)QNP9dlz{TolBI}S4tw(jR z4|2#<4rVj}DB=*uDBP@>!IBu6P!ReRz`XV5`TcW|I`Q@5 z!|mf@_d%X+ah=*J>g#|3v^H|-$t=CCPtE*)O#U)lp3?e1{_*}k|M~y*ho7HH`r&uK z`7d6EAMW+5p8}(^*oF-1AW<($YtKAF7)i=T!jFBV`{PUmx>;L+{`G=om z>FI5|Q_#CGd~fFPwpHNJiqWQ#<9J4%Pw0U=VFou3&nn_0VuV`GmIlEFD7`^eYyhrk z=+Fjg3^{$9DveloouP*sL6`#F2gH65T76%RW+)>WL*8U!FfnZ2C6Y;fB$sSHWo%2g z!XWmc0KlaGDr&Zl;~{caz+=*e(csxv-?+zp`Se+I9@?dh?fZ*wc`9wea6t_WDI%j8 zy$ncFreKG?i~WusyGq#sOvnX17Yrx)`@jF;X?z)8UY=$TEpThg|I}|OKJTjM%SG@c z0P23bzur!#VJ^e!v-px_QcjP>=T`zruThJnN5ormg`hw{0a!X*Lo#a|N%8_$!YRG> zl+`Tf9A9M&-&T6pIBZ2hs8}5HZy*Ii9vVt~T?ZBHfa72V;w$KJ;5}eSr{|E$4rq#K zj7%qlVL?TN(iIFu`pU?PxJd6v9j4Jcy8@X&A$LGrhfOvqVG&eDk(?+58aM$D{?*Ym zQhu19C`@qgk+N1{I_fbF4%-;of(&>}Fj3BdIWV|283wN#$4Q6TtCIvW zkcHlAxee!dA1dqU_orpeW@V{cgn2iddRsVZavzGXLptpqTxX;lLL!ytkh1wDcaFTN zq$x*<@|4#-6*J$uG*@$7Ue|i(ecOLp+HLE#)ke|Nh259?YQ6qbkMp*$cuO78;j2zK zLE_S`!{@l^f5rJq_-2cfuHxf*Z%o+ zo2T;jx{@-^Wv}KQ?qL>}OSwFa*1h#w_a&=}h+D^B>m)>hOuy2>m>UY}aCWk1-hR*z z-CClL@x6=2QB&=yZwS=8y|q}>ra>l+2H8`|iYcdzaY&kewcIekeJ9}Nb>EU(>K2|- zAtR(k>F6yqVQo-hMIzccl7g~9S_Gp?)I-Nd52A(7Ujt(Uu&Cya-Tt*zlw=vv+UWOV z2~x{ZD?7AOL!e+YCQ1wEUmO2_{hNj12y%C3A2lA^_L?Ne{ znsoJ;C9+Kay9yYNEIC*sfho{n7D5j?au3Fy;ldWjt|^r?X)Iw7M%~;P;r`c)kB*drsM9^n3=(P>|IkwaRU z%Y|g zx(}(Rzz|F$$t=p?#eL!$|iZMkY0vEKq3}+2#Et2wjy-Vrcp7!;oqRS_qWRAD$qJ#SF z^Yci{90c)508h{YpfdKr4~{$5gqJyZM?LYP6e)Qm7tNz@DY4f6YK^{0=$fX}t>alb zBtx6j8eTZ~fPKFkyCk`cweMbrQyaIZB{|-Td-TL$ z>wcT@eg{Ss(RcFHA^CZD-udOzVm&^6z5@99m%UqWR?p+JhbhzRoAq9EDk}W$yVbO zjxhS@r5_H8uY|-`Sv>#?IZRC&VIty=uR#|~L0HO@cNa)E>Ak2nk=bk0ENoSzcn^VK z@Yo}aG9g@na$xTs$1PBZ+%}QqlpW0IiV&t2N)@*XgqGsi+&j{qy4=*5$5NJa@p)wQ zIc}RukIi;-5xjj_j6RN9FqSnI9b(F-ty}daQii0M@4lHx^PC)(>#fh4+rC?-5ny=n z_5}XpHGL@Uu4~Ks%#Sjs@2>t?nTd&&#ts?rBR0@zv zRCtDh&>BeYF%GomxnS?c7_D}S%}iBWLz_4RTgs=Y_t5T0=`mY~j;XDdM@1@w)Fg`Y zB*rWQ+8#J_Ze_e%&%3QDhjodDntU*+dC+*|fKY+5*_J5{TayrnThIf__RO(M3uRIc zOdE<}&8q z>#aqMLQ2k(thJmAg4^0epc?DCX`%`N)Vh{30W_;&etIV@_3UcuX*ebGu-A3cG>Us{ zo=&5JBB@u7wb|!b-yHXfI*u)!-k0@WM{#^#?DZC{$Xipew}=cmMx(C^VIbh$p(!83 zAOh^&v^R{&;IkwL)L4Y8`W~vu!;HzICkY!=&;vp+BJ^QiWE>GnZV-JS$w`r!{V+FK z)Q}?qrXx~9W>~F_MYe{_JecjfD}_{5G3)A&o?3xMAopk~;1EUG;1R>%08|BLfWePO zzP&c6AhM1%McFe=3VbNUe$wR5^F;;Yhv(B}EFYeq=jY*RpV1eodPmkO3JCNg|*f7b0j3k4TK80Xsr?z$)~BA^e?pgfRjtXa-Gj zAR|YFW)BiumSV`dT=nP5fS%BPK?s5Q9UHiiAfEi9piPTKd?xI^+<8 z`q$S(Kn`Ui9u8j#8XrG~Uj_K`)c|%lrvgWu#8GZ@7;BLNiH@A}x97Q(GWG6qWCKAX zPPIlNoSq(WH%CpCcLx|ADbV0WWp^MV1JOEj0?-hMG1>|^Vw7?2%S|$y?W>{pIv_CU z_a9ELQ~B`^pZ{nUzLKo-`TXjCbYDNGt!*MNMfNU2yCVau*s?rnTAAIySZfqMF=DVA zr=Dux(_2NMcUUxkC6_$>_H5TwnmBv?<29AHZN%#|%>Hvcxh5}m`mG|S_uu6jM&9#! zy2U!S+;#VD#xMjwa~O^W*Bg**N$Fjfy4{#h>td&AxZP%<1=1GL9a+*6k@ZMpO**DH zAUi(=HpJl-$yUj&FR-a7o_wpoC{+WY5adq0i;?s7>_OgTy(?en$2oZ8~@et2H@ z+FC?RW7(^nPs6s}*S()lLxj6U;z-dVWuLYV`gXdk_aJqgf5+lkn@YQ!X@0%0|HDte zeEC?n7oJ|qEZp1WWS*>u=kZkUa&EYz25U=6N7D?6Y7{n#Z5UiFBmz=n56TY7&Jod* zKqM)VS;FfyFfF-<(K|vC(bj>m;?mANg*KQXS+r8&8I>H@@DtK8NXY;d^d5B7Iw(+q zXx$U1N@uEtIT>4y4A=l~K#;#UpqYyxm*&K<)n42iI2@1ye2(4dJ6MV&z@sXQI2dYh zD3a}xMc}z`%oe!zYo`Z8MUr}hu&flut`EB7RC^R z9B8p}4S4f>^)?TwYn@^|j0behlOTps&0@ox;AUj+#U@=@mdZOnb(Bw=5pU^-BOl}Mm}9hsdZiqX?y%O*4k z_9NCa=nM_k$72gr3?2^8A&p4jCMsH!9jf{R-SPpu5ecGdaTY4bdrbf_OUO8LmT@T+5Y0R`ak(NFdRpueBuyeP7ixtao=(dQmtj}ODTi( zJ*By|$8pFI2?|(`5STL&MFpfLCNKz!G)f6aSP&EyJw=e3b;7d!;%#e&un1zDT=Ni{ zZNrntFM+-ECvMmKIKF?qf4TkCI?j1s1UfHen|z|5+S8^BX;ONW%U}sSDMw_7o?8$Z;AQrO%t(=d@KWcN&BZiw!Mu+Z+i;rv zkjEh*5-7YfU(A{X0ByBOdMkQjpP<_~D{pkI-K5v0`#jcz84+p)X6Isl>0v#PuqO&I zES2P*hXD;3L5jOKKdappW?3{H1IuW>Cz;u{I#_e~mdyI847a@1Z8RtXW27>qsUUs3 zw+xw-?%QaV^1XjXw~o5p(vIa8NC7iAhZ*(04$)lw&;4yaig@4wj3Vy5%pWuqK+w07 zpFRxvyURRj^E}<^EvI1vf0*7Om!Jw9vaWUA+kL$jJ-6P4oToy@eOE^>dDHKohub~I zkzuj#ux>d!K@R!awvn&5`{#f9_*SpX$gtH}q_HRENi@dk$;fBTmm%H9HZr^?OA|%3QgwRiEcFl1P9CR1WKVawGl9OL5bSkjVV@%$qKS5O7Atnwh%@X z*oHJ99XbBq5rlU%Kyvezi^x{0H%0*BwE>RUk1=c@Jm928ZzUyKOBysVm*5r#=^OxL zh0v-JLDdvJ=iEiIyM}lXE$K3Kly~RCA-9tAyUTPQqYTefeLl1GG)~=@hTT1Nnny6C zOQMj|V(nS>9+^JusSQ0bs8c#`l1$tIwZjwgZN(C>5DrPo3Mq&ACCkSLq)8aW<@jK!iJO-(E|C`%e@PP@Bkld6(7b|WT1U}UJ2}nFbV98 ze2CQ*Afkt&Go%tJw9<#rM&^)yFvo#PE0LYuDJi16L{v$1H@HYd33eC4D10OYL%NT# zR{;{D9Rl6}VzT3Zf`X7h@^GfF+Zg5Rx-4HgRu3Fn%oKD6LliEN{a5dLeDhm9@GU)L zP7!uUvJS|;1F1E%v5N>1CQ86Xl9@J zbAG26%=_q7+wdt%uCdP=>taCL^2WlljJ*)1e4M6hyaGhaa#MI-SrtmvhQV zYqhDDbtlrEr;E1x)ty9PZr0N_Z|D28kD3R`NGv6X8>zv1SS;qSCFBw{0!!c$0azZh zaJmSk;hN{kUUT#=3~L~%>b8ZXjcKZe)57f%lV+#Ey*CZ!vF{uTBEq8GdLQo!b6i3; z5j&2hC7!3l1Gn6er>OU}pR{bgw&b_GKQAMcDUv7|aUyPH$Rxgi<*4 z=QF&JGzb9w9glh9FQ$g$giddEcRc|e6k z%BVtE;uu3rL1Xf$dkrdR=s6~2Wn~PR5H(=f;o%^R7Q@)l1ueN+#L!U_t%Z$%Kp&iK zQUa{qjUc4u40gz5L-z>4c83Rn7Ki&MJt8k7APP~jPD7MoKvY90&`E%anQB-K7`kZD zO;iJiRbRogn2zsW=IQ&Vc0N5N9nYsH9sM-V%#wlLyKVQb($+d>M1TVU3OQ}I%_4ix zEs!TjfL>%2(OM+OC>B0EY7{v#YJ8g>Rhg+3aEFH z)Q?;n3lG&q2$Bcfn|mSu%9_AK^BqV5`VnmK4bQLx`p{!?M+BIF2UPSmX444)CD@q~ ztW$HKGW5tDWkk3`a)`hgYBWFKDpLdDLMj*nhqxakxF7MEVd$Z5rad^x>WGfn(3L`H zL?U2;>PX2DUo$9=o)LtB`Ut0t=AI6DXlQ@LLRtP6O#c9IzF{~=3dZqF9$@`(Xizdm zk7)ia<<*EY6;@?Dg|}0NW^ItW=?MmlHKGK@tqL#*Q+}M{1h_E`;aSIS+e&!~cSv-n znBzv>JeSH2|QgeZs`&wW5+FM2* zd}NP2^|tIc%J8JiTel>MX&&2k#avL&+Vq-h(2Yykp3Bc%Cs$3_%>;`n%!)HL1Iz+FF>N#u?J(+cp9=2ea(!ki=@T}hMyfi}$VcC4pQwiW4e z@_wb(C5r8s=Ul3C97YLOKjrstRmRaY($|`(Aa~%TuM1!*XUcuWdq+xYUH zpS}QBK+p{q%mY`i1Altm-ges)cVcA@OS7yHAJM*372j_3-Q|f5!q@5V;droI3#-+9l$1*A;J!r(+x4uEcOHf$q_?gtDH*$Ju`20&(S1sT(C53 zQAwS!I5TV~N*#Mz277`Ux>6-q1PTLz*nJNhC@VIN$QYW0R$3F%hU~TNxnzMY)d3eK zZeg%o%5^ONp6>FHET>Vg$ZpybpBB&vl@j6Fa^ zbsYo(o5e-uCQ5CkH6BULIe?@Sp&@^fvP2CGL=6;14KMVFnM0>3q%$%Uf@3uG6zV({ zFJuoZ&=lwt_fUv63WW+7svryKZf;%ka723@OIuIDpeXDBo1=f-3Hot9vacj6!2HcO zdla82I_WKV?5<&rs?mUhSE7LuK}e%BSm`O?@a%*JGc}ZS95NU3@Gl7uVOa=b5;KJ` z1lVI3krY;t6BtOO0U-*_&`^2A>mSMV;a`_~dAOy-VBc^jA`Z=u{0i;)>dXtk>4z;t zA|9H{W4TYqx9!_n>mJinMjwXh4i4i9qJ(Jg)=Ud#1<2TsJ{s&1)2IocWH5y<4o=ad zTI3*kk;FZ^}C0!Yw#)It?%TP^3&`)Y)=?YEO+(+C5r^(W?N2^ zGkvN2Fg~wbCXBlb%$IZ2W9+a4$$Ch01vy=v!(waUu)V(d>Adki$^|Hz02>4=@y2|F zp9|oGW}=`m@{uo;rU<{ZTQ|1AbQ%!2183)5!X^6HV>z*4SiDYJVv)dG{_upoKdIu3 z@E8h)@vU8*-ys&K-&7GWi#7O!wcf!orz1Rb&pfAljpqyFVEzM&zS$W&p(e>Ebs9fTz;R^Z!V|weEJTVf7k7w(LcTZ`8WAD?|%5Z z=igw&$L79YuYXwA`3|A|wtV-ym-}hB&vO`d8J>rD``Gi#wGNoiFLfRek^gl2`5$Y( zp8uy$|CES-cY50Pdc}T`98WI8G|OZw8U`3R=XfKDG@UUNo>JRlyS7hO+jgy8hSJf# zz^M5mD9;|9{)#J{vvU=F%2etao+Djj?Sa&MN_znJsz}3Fq*fOqOO$~t#z=^Lhj%jW zu?3J@FLMqmZkq*c&=9UXU{_KecEDcaQ5-O;Mj`G4+ue%o5#Fdtw4dbo^*4JA=M;pp z8f1rUVWm=&gl%(9QBb$u5v@h0pLzdNE#bZ7BZ-G#{8m>HUehV_G-273Eg>)Ybh_mK z^6!86-QT~!!tdeOEpdodEWC##v27!E778L9JTcZ~Dy20U3Wj~+Y4gE68x+ccEMM{X z-29Zr1u`QXv5V|(M`;~(c?y$yG;#YWl>I{WA=@N-x|JT7^kBJrsYe-OmjwoO^I75#X@<5{r!Uzowgu#N! z@XpYO_9i+*K#P>Rgmy-wWl$mqHm6C4dBic-J7vSD+~(+=;;ADecmivU6bK>((4Yl0 znu95Fd#vM3h_Bk2utRvSFelsy8Z=VxtDXKNcY-Ou{JXb5e;&Ygk8(!JJ>~nyWu|L- z7QBW|8rwM!))z{sshtkWqzt;Kv(6KuY3}hf2N6T}N zOd|k9N2^bpT+3Qo-uz;5lbFvg-dLX$jsZqsp8CEC;b&`wi_bK_Hyd z={z1aI*1TsUX#u3y7_rJ85I#{yDdn@N%!0Oy8ZJ{pa1E9`NMzu^bvx;|C=B6HU9nO z!=Fyqd8@-)yZmR*c&|?{Pp2fwA>U=Dq3G#ka&^2L(#tT}sl4q7?|s^0nI;wFbI%jG z_~|zx`l6o@nSnkPD+$nYYQO#6^Lp~FU!z*MKO}rky{bMHi14B~QU_Lz=l8L%Dhx0f zpVqfuK7U+)`SW#YS^YFCQ4FU8`Z8_X(BCi9ru~*qQ*7)R-Y3aB_NN@zhMXO}Ze@HK zQ*s6tKb`K-v(Sd&+}=K4gGe*=9yXdRy)Se$CQ?QnLhsl;My)aA=-VP=-dYa}9V%*e zh;L2VPlLYMNK`~PW8b2ILZ616-@sPJ+FH?6#{DUL--ZnaiBXji4wgZ+*LOqPaGHkv z_k#)Wd>T(rr!-G%A8h^8w&m-`C6#@TN&J4sp5}?;Ii)<}W#gxY6-}MIwVBukcQpcp*qcD-q8-m zRuSb8LI%Kp1*P0(kU_IH-s?8ZZ!M z82)XF<-eg|A9KAO@Q#4)N`77L4=;ipW`Q8YebllLBXbH+#(jP5!*E(QD=9U<=RAa^ z6yD;L#p;nVml+;Z0X*75z1ZG+V90ml6#Yq?TbJqFmb(cFH6f64D11*PtB6DV!;o%2 zJimKaSI>XydwzF%{j!eo8qa?>^*5uS!E3FTedy1yCrqD4Io;*Y+>1=n7V}?+jYPL= zDd#a_OR<|dyd-t=yn7pAwXf6uf~1IA(-=J>!Yb3C@Nc0IoiWGwCO&o#Ilqp}c{{B$ zF6$b(+$A|A7M`Mo%1zTaICehGYLat!*w)=4u`F_aJ{^?^vqzcrZs9lEGS1~bwR255 z)m%pMx|^KefV8dD#P#aW@5ZSp;&uBfxb>Ss6ri4QG$jK?=Q_2x*FyVQ>k~N&VqfcU z&cOvRj^f+CjQ6;MiZ#|BqIeMy5wS1Vj8u9>O1Wf9rGeM$>mPsq^wTeIf{5}2!KorV zT|FGuv2aNunVMx3_d$YQGWTA9&@rinB~F(!CXpJP`tW@TA8SO~$2`^U_wCbEqD+t$ zwMK$uh*;z#QN$^+gW*Gxt;XaL>(-JfBatppcO81vVnM1mblDNaK=bGYX{H z5=dAb8OkDGq9m(lM720-1$@WG%Au4^YegGla5{iWPe*aMpqE30BLepDL}N1PITX;K zTZq6LL`!FAa!UG^xP?Hs$V~=ShaH2l9>PQUnw5aBe^S00Wsl<3{&i=42XFmyCb94h(;M<2oV7? zLs|=s_{;kH&2W~W01w*8fQk&6jC)YT= z;NBpq2vVYBn^xRCBUaJVSgtLeM+k=rqSYd)V0X!(_Xv5J&j0THcjxt&{T=SNRx-zz zLAy?W78ZHCKn>xrGygp1c+L0ku>W#jB@Zdz@;QHRdd>0t>RpEq=S$jIyJBd0JdPav zOGtQlnqz#{>%a7O@7FX9KL>yJ_WEc0m!EGxT%KN@r?)4Ogt`xt=l&EXc0J?eQ{^xs zu!k{7_}1z#pFfi;GLVqRh^%ejrm9Ut0;lel_H6ZCj9qut+;j6G8E4^sNf$AZ@U8|- zK^eyBg#ArsShTIZq5n{akkxtmA@4CFT#&c!(OafTGBk$EF1@@=%*ljEWxPaWNnutk zdbfuCTIH!+-;9P*tBvIt;kIXL&^4Kvr&J}Vsh$fN_MF>v>iaZ}Iy{y4|1L^sqI}5Q zVi*%RkDABl5AWyc-8fEp>aV{P$|k<8Ms8wCPf@jZ)YZL4gU4QNKe+}vTA^B0lTm$j z9C^c(PJy@{Qun`T770d4RDwbp6h6JZ=;qSy|d-Wfk=lD zNdN~cDP%rAHH3svC82r1z$D=&n$^7_h+d%I@Cg#2so6s+9>}1(m`6$kIv#yj8ZaOO z-C+gX!GwqZK|z-&Ml*Co79w~=#N=p=S;BK@r{;)&9!21t1T0I?Btgx4rp00F6Ek&&{JT8j)HKD2Jt*FsEp&q@tg8845p7@wIkB_5i}9sCiebV!42|j z4CmjB_Wn(bN``9smVHJr4RT3E)!hZTS=YRZlxk_XbidVnWv_H;!Nh#+;_|U4Op4y!?Lr zV|h9K&;MZ#5wlO1(LI_mCq)eGZC`f1$RaP!bQw}@`<|oi`KTNNz@kxYj(gp6VeIn! zJba3RKi$ZPwfE;z=YaOX1-Vu2wW%w$s2Y*oUu6&__ZAi>0tuzhRp3JmJ7W6FrMO8s zW3Oc{d+nR(IEwY0kJdhoumGQ6yAF~wd_g$9W<;0azV>&@*S$ZDK5c`K&NdGvFprj0 zJkDXSaT%}6{$2$Awq@E%>s56J`0CqJ3be9`q&>z-77ySc32M4L<)F7A!U-7#4o@@e zx@4v1%lAM2KZXD8n11=`(`#zw%OXGgMv;EZ-_4u{ip>(LcZE;MaasNTc^|#6^T=T? zx^Fid?CE>!pH3-kp1o)$XqV zeSY_Hd7cJQ_ulVqmA98REkFv>Kw*yHy5mo8x1T=#@lE!4zK~uVwPhLFcPMYAp0;)$ z=ZaO6ByQ8WvY0A|aR!;>+~$-yNZt05B8@~opDvXB_j=mz+x3^%bIIp2-L}`>^3ya; zB}Mpc$1mI4H3D@gQdKn+PJJ)&d>)2zdO1xcGZV(Ma?Y-U0?b(JkjZ^GFHS`Py)X@zDl@U?)R5Z1DogzPaXkZUh zYV?reP(kW|G`55g6au-z_k#*u6k;5Q++YI~K|2aQC;%b_gPxH>^(gCR3kf$?QASnG z?9Fl^I+Nz3xbd&&UMQShBSJ(2u1+Ayp-Vg#P7|mBL@$wcx&!$u;h6(nA_*-ZDk2Zo zHa}PoDc%l=V-7h^20#ZiM}R4`1zgO88UPJJ6_*I(uRs-JfPReCiM>WN$b{aRsSbw7 z8axH`aLKyz+gvXY0Xobd-17rux8vEw5yEhgzk&b5h5p#5^3w?* zQF?DJpoDfpZ)rNtmY$(KVv?}vo2Cgt+@sI7ye6OPZK#xCG;&w%_hs|~YA8K-}xluGDxwPo@W*OV5Pd^u#;H&jg0kYkpPR+7>1ZdL-T|Rm$F}=gO zu-!2{(LCJ$_{ZDrKm2$9{Xe{Y`uyqt_P6!lk2-0_9BBgP6gD=;66?D!yN#25cw&!V z_T{a`8_JiSUKmw|8@D+Hd9i_`T>1+qqp$agLya2i96@lu+WoSh`~-IxBLb}sYRwaF zw`EFVgWnf>i%kdn;pyERe|Pf!cmM9U^Let~e);+5pML)IhhP5i^T)QjZZ*UF1kX?7 z_uv0mWc=a%$#QhqRrsjKLyGLYX)&`)_ z1?rxe9-3b;zk2?|Jk3it5vUsg0bP}-Tw-x|GgZBdh%oa!Ts6-qEHh3KPEKS_MrLa2 za#;?a^Bv@*fx0f6h=hfp8&uJOTv!9xbKVU(mjD5~*X22tBDfbST-|CamuQtT)pZjM z4=MtYJ0&739DPLiQRYq}tR3rqs}*qxzd446Iox;K3dg=~qI2JofH0ah;f|09VS)8w zv$YVY=BJz!5k?Gcm1zjDBEk2m=h$=0Ch)uHY@6k}-y!?DN$IC_a+_}ZE}7U828XVc z;Ur=<>>ayv(C9OV9npwL=F@%eQDx9cJ`R?z{<-|sr@&!NR{^w7x-{H5p1w&Fu^Jqhwm$i_e=X^=35(Kx{)B0NE>N<@1gKv$9vZrlm zQHIe+A0nzhr`&YiHjK)_a?i}0-10i|1n1npQRxD4!>*Xm6hRc+Ot0AYH zI|pF)-4>SK(xpS3nLA8UFSuBZW66V1e&$l zRC2fJBwqoQgfP<0!7ZV)+mJ1!mxg){-%yT3jRzb<54T;>>7z4OwbBI>c?5ut&WfDE z$bv)SbY?O6*H`hX}E0$|(?ZpAP zQ-E1$8>%9NQWIN^Ov338k`(!E4(YM1UWUcOL3hs?z?anlZaeTVW zou;-;JF8lwDv}Oc_3{b(&Zut{C8d^Z%D%bGyuH;=ABM%lDyM|VNKT{eTf~&q%SqQd zOS?&dwbI;`Xd!#VjANA~rif@mc56bRwH2Z8Lhh8Q(A#~@C+s`N>C*3S`NIrf!_INv zR8v?1c42^yp_)xueMOy^Vp#9}bkb1Sp<1Rg8f09;d4u)sN^^&9>qV_GaU7FMo%4&W zH{cw1$9dnaSeQltdG~F+mmR4H(OUR!yFj_E!J=utXls-G&oAG%{qpAjaj*A3|J#43 z|N38Xp7ItqyZysI{^P&<{U4tuK7Tm zm``$7j*jTJ1;G!`R_lJfem-6P#nVeEvh+UP@Avzcue$zI#F967_siUSD~`BFivdBd z`)>E2D(+z-^-a&GpieV4w1S#sZ|a0n35NIgV?Ho240N~a&R*n~H#XKy_usAsB7iGY z(1krp9!Wt6VoWlr)!8o$_cl-YEst4r3BkqN4*^BwVium%zp zG=^Z1@Swv%>5&rL0HsqC<0##AgupX}DVdo>wr-CHiv{)~sjIL_N{_kzofsnl=77-& z?WB^@o2d7o!$lx1F+8%+%@gRLg(p9_`Rw}sH6tY8ogtAtOz4D3Xa$AMO^#J17qnbD(Jjpi=MWJAI?F=@nG0dQF}am z@~>bD|7{KD*PsZz-+c@r3}!q9f`KZMYOR`5c+SJyIGOdKH+l?h2PTR}b!I{-nK|G@ z1sR~sAeh}5*BC+{6&3=Jf4|qgtEHBdENFUh7WYys~4=F3L#@xLFaCUk;HK*7U zk=6sS7fWsDs0##aQ22?gh=Lfv6>!MnE-l0m@j}<=NO=}Ic$eHo*euc%Pcz zvb*|zA_OtGC%3>M_)#OBLtKeWIx_a|)r0B2JwvPJQS%%zL(;TVMBmXPcEr_py4|Rf z>TrLhd%xe{J^2+D9J_SsC7Res9F*_FuTaDH=*jjHVY%qj%g0*s^9NP+#QikBCbZhd zuWwPU+kU;>e);nA{l{P0*0EhxdQ*zLc%*1L=F^9*Xek`ecKI#m4-`R!>^C3I(rXN7 zx&G+GGp36!pZlM_OTAC6-W3*DyM0aOOx}B)IwFRzfXlEWb!QqVY453(+n2IW`s2n; zGBIM0wTUOD>#&Sl;VeN=^C+KEz7#n_pzMtn7JKja*wr>j0{p~#^C3u;NCm}uuC8j` z%7|2wrsT`=3;W`=sb7gZS`U-xLx4GCa6RI&=5F45vmVWDRqRBcQRnsER2(KLs2%7` z6KSar=i46SzO(c#p<8Q{Z~pc3Y*A{dfY61b3WTSCTJGuKl`}eF^!q0FT}v*tzD>-7 zTO2*D<{Tm=3X=)8tmwuuq0f*FK1C#q`?fr}@+KO^PTJQ9MpyI)alQu!Qh`GBAPEmF z5j7y7l5rqHT)@trkal(g+J$zwV?rY{@x56yQkXw_FH#&tUJ~L`n$XA=kg7zE?CxkT zL-OcZ=p)2n9<{qSRI2&`@&t~Gq(}iG8e1R%jbU(lQujwgju}05s0I2#Nq{3rX%ZT6 zMsj@XG;u;wP}mclphs%#pfQqCpkRt@5JFV!xXFk+VL|{q(cM}NSg@L^3vz(2vI>}t zAf@}S1)Lxg#Nd2SRHef_Dkce(ZwcA|-Y>ovs~?X*{u-@w5Is{cTD=nA9m(1)>C~;M zYF$t6PT5jE^<9cM_QMcOG61ixLL_Pr^9@97b{hs@-y?<^`;betF7sJ=Yu)R*=WzsC zP8PmzcOz~#grs~K0POIOUtiunejdJTzxmx?{r=DY`STaMoQq0+DQENh>;6_G68rKV zHn`X2so4GN%Tr=C+4Jync_JAI2HSeM@1J|{x`*4EOaJ9oRYgQnw9D7!?ZeAc%B$Ct z#x;Wjp2Mywo!frbG;i%{LV+yyHtAGdJKrXGx*0NfJXE;fK-YXE9wg~Y-(o<&Od;ci zTTb?tpDyfuYNjdGw+@A$hUY{4#NEwF0jhpI0YcfDBb|$ zZ9{kkzOK8~Hn;hb%GVYE9dhIRy;>(U$W=gG!*coSzxn$0^RjE}OmDAO zzfCqsO22*iIO_krwYqrw@%mP;Z~tLg{^|Oq-+z7uWWyi-^zZ-O|N0;Hbvi%wRQ&cO zO{eYlHea5GfAg@SS#E#+_dosN|M-Xh z_y7KXeys8T{QKYh{nPW`fBLJBfA!&6Mrw{KU($Miy8mXMQy%3qIoqG!mLKoG{NbN& z)Ag5n{$Y?|iGWN)lq3wqq_d9VH8?A4Ftu1cPABd)`t!%r(rIsJ*&1rC1Jl$-KFxEM zd6t`>ueZ05;K%{zq44{c`MdA``sc4dj;Nl-mvUe96j+W6u?&qnpVF3wPZ2=uso zhsT6ukgIdh)TcDUw~+HK_7N=weRnxQuk9q$!Qs#o&@QJ=LQnH<{3!9LXM!LYvHr@A zfs)E;nx>pSK2zD&{obv4wUzT_2&YoTE5Ek($!>@_u%R(ZlT!~FFB=z5sVuEvVu*-n zuEC-l@LtkAk^#3sB0VtCTR_SD5G*Lj2ty(w5CsVtW6v=J40MJB;xOPdBOMvrMrcB0 z!WEFj2vK1;hNF^-bO^$wYX)2L+Ded}v`!{-kY2S6fU&2;r`UjGP-hKu|)InpT2t2~rQzXI!<-xdD ziG~OtC2lxTEyY%$v(^CFiun|QK<_#y4=;b2>j`%B1kqp3^@qVoRl*Q1@5|cw@0|GG zd~fMMDjitQgCemZ4ip1=3>$I}-@RuKkf(j$N&!$2r@F&od(eCxb59z(31=<({xk`d z;edLc){O_EJOSHYr~}_i#}EPR1Vls=4s+hV!%aw%&bW?0pQ}rjVJJe*1CsK1F_O+F zwcXhx=0QbBbLr+D-Te(I_j=kF5h1Cf5Nz{7?iUX@qSXpvZPwlH&3`|PV)w}IMZ*yR zZ1Tne(1{wd7G*Z5hw`-BVu*-w^S4PWc@wgMmR$>i-s@G<$^91{S5tjmweA7AB460y zahJ>4sz#mqeHcZsM#S;>9co-_&nnZvc&MS+?&{OP@TYaf+u#xA=<^n8pn!;l6t|_P)?_<*DsprJ1%U`1H;a>Q5CSuiSDS}9rZ19StbI;7iIizp&Er-V zc)$IKTE#Gb&80xaf4SOQTdr^0AOEo;mSy#ITYCTek3Zf2VZZ;w{rZo;Y=8X2{U5&G zfB(n(_HW<*(=Ts-`uX+i=j-d|x3`~v{$=~==N7i(`&azcW%kST<%gF(6g*Lq#Nbbe z-@Z=!RM$(LYMB-rn&PwfYB&D`9fT?@lq6`!2(!jL@b*nR1%udxM#<@%nbRia#RXOU z4EH5s4+{rZoM&DGJCH)oRMRy2I4pG;lCLfh@geOYih2}Iw=Li4OVS_+1&7{dH$a33 zYO%$9^=y4u?>j^o%NZ>ZNRW|!2TQ{xP@Snq5CcU4iXyW8)6kL(xK zy|sPaL8+|^IgO`4UZY%eiQpcxca`XjP|gbunIRD;SQ^cixtFx|m`^J2>qqix^uAJ9hgh75d<$v=({oVfg_y1=@%3>0@ zZ-%Lc&dHl$>b;bde_mp5ub)m&_{nnlEN4}mM{j{sE^n=$O1_G!J|%s%;MR?Xecy%{ z&tsbNj^S^5Zvn}MQ7kTr#9Zs5DKGn&aN7w58`kqW))5{H^iusk$kWxYlazfg3qW#4 z>-|kM`8q*X>ZRSkpcG;EWnG=XTMvwcaI)A(&i*)>uElchyi1&q^*aNx`&+$UpP&Bf z7ewj%@FkGEg%&TVq+OVF?iwQ`GktDIdASgSoPSQsZ}Ae(90HT&BPYpGj=B$9_T66H zrgCkuEZmc>^@`=Yp1-`^3kXUHd@Pf{JU=CUg8qH?E8X{Xx!!)t_0xa&^5^7>kmTj& z`!myr5Aw%f{=*;t@E`x}KiU85e;YrgzI&SU_Df|_-#P>moWFnhS082+jII5BPQUqA zKRo~TZ}N};_dxG!x{O&zJf*w!CI9qSU&pUwyEAP+zkc@Z?fz$k$IGXuD_zjNu4R_) z1vv3z=pd&h96nKP@@bedhv`F5-FB-CfCi^eoF(!DOlzC<$Uq&+AX>M z=KOTozpAZ&iZ(J-o|yCeg0lO^DgD%wU9T%xY!GE0^Rv5o=RRUxh)nDbTU79zFHHFv zao7)UI0Jd0ZF-sD)f)LEs96M1cm%L{I)|M-8&xpFr}0|bc{t+x!U&ag?>i{-w(ThsFm&@SgF>)4jlx0;v`z?mhzUpU^#9WF<8IdO z0&h&yfo55o*E>q$IBDz1gIT0%w;tZ;4W((mBz{l@=EXHuun4R$x6O^i+iFCBnm+lrNH8wDlNMI@{YVJ^7q$JgLSQk$QAu zZedIj&Tw@teSW2fAI9Fp_glPt=vC>xvG-w&F%7caM2Ehr+uZL1$E?|sPqU2`8#Krv z65xvc*V`GdvBD1StjZ|}XbfpAd-r*KiL?AyWbo&@y>;6Hw<6*Z-A|{}r^^TVS3i9C zIQ-Q~&K|!!e=lFcs8Ft?h&mrDKN1d*=se95=yrubKx39$vq&*r>NXBX3 zHzBs5N>eT2os!)a)fth@+cy|LvDFnVrXpjE{^(j{6rDpZU6v`Y-rBmY&6llaEi%M3 zmH%n_>))Th|MK$7@){Nw#4qMpHhXTe&f~oH^5ZZ2-+uh``L_S=x0tJXvTgf(2|`q~xe>ojk95 z7vZ}5X{^6?`FlcK0%?~T()Y`xN{(4(K z{;$9PW$Hiw`OiOvaBLSDG(Z1tNbU0dTrHM`-~V0IYJdLyFUNWNRVBVGzbW^x<d=W<^49C!tXf`rR-rP>o{jTE>1F(Q#l)7sWI2I+^|Y8uY34-okR(VV89f-4rY716sw9*C5RKInvmEXt?O<^c_J9%6QO(Ipg-VDSv;k8b z&iWd`U4ljlXqSNG>;XO6L&CnfO@M#KNO(kz3=#+Vrn`w7DXP+&99>~1|J^zdzK`|{ z#DP8w23;wQk>YU2cn+FC6@cdqv*=JmBh8etWp;;?+F?R8M$x0BF-BxsBb@Hv-132k z`hPq_{;RoO-SExX^#5Z$8}DT;M=mJh(Cd{C))}PenW>0#65beM7UbPqCB4-x=PrVX zLX<#b2~}hnBRp$H%=?a`JxnmPD#QS~9v6uKn4wHk4f{76Py#$8QbC3at;Yan&RO)- z9bRGGc3x!2C;Apx0U->->GLVk>UO#8-+u>rzRg2KYIj>xT4q??Bt+1AwzbtQw#jQ7 zd}Iv5Jjj8P1DQo~jkYgM>hZJn-t$)J?Yc8{yPKBGaE3w}cW@^+X~~;oCjFiR7&lE* zM23W5;ASE47>RG@OA^~t);th4P zXpq_?@KovrSzQ?jp(~qThyBX6Mxc?F%%`>s?`+rwjlYgpM0ZQxLvn;hu*c%qLDbeB zHJD=K7%Dan0W=1@?Y-vfEksYZTiMqX-cv!yzb^La@BZ$8`0MZgm*3>S+JE_&`U`p1 z;RWpn>A%VC)NS1SyD`6Xe>VSM{@atBe)~foPN%1r^?DECuvbxzW2rT;RIqt!A1rc!leyK_C%(tjliJVeceZ54J6eU^fYD;88CM~J=LXFtGC-G2PgaaKKL3O`vJxv3L zG^w7_s4{yDLJQiqZemd>PJw!l7{E*6O0RvawY!jN!5@i=FW4qES{m{pDiEwUl3kQx*rhJXn%M?d~m7fMFg+SOetOPVt!FaQzV z*=y24bb7mYGZ8`)ElBC-aaTD-B!US!7}aBsh_J*i2Sz5Ecqq)7L&hR4OOFs_L?K#< zhVBswdu!ENcW>R>m<5DA@WK1BSW*-uWm1u5Ei~8|kNi^)I7wC^I~qU-5h6iN$Vn}^ zDU9DlXona@LV}t?1PD{v`=dd(0Txli_Fols98Pp0DCixOWAz~D?qNwO-n)sq2#)f; z$31|52`34T!5oOl5fWgEM8u=a59(CF?LeQ05`hpu-en;?nrW!fnYYL#cEXGt!YxQ+ zL^OoSB1E{xdl52^Ol*it7-8^l0H4EfX&n+FM-eI>HM9Ti!QKOI!Eg^h9&8l;s=zCi zDV}nqQwlKky7#J)vPjhwu`<*}j4;EHF!icsH13j1>Fd1=!?yKmLa>Yj_8VkGp8_2i zm_#oLOZNQ+8n=#N%DOuyMWydU!hMT0z$&&2(hsBobISu<(BR*!M-PcxacQYo!#}702aeAqC-}c)$Y(KB11wF9s z?UGi(SU5g2M&j+h#5SPl4MF!iK{qkEPb*tyYs9>WJp(_FdD=ll47h@To&w0j-9be2 zj&M)e?n@GVTAk!wQwraNgSyGo1e4c=Ws~V5)<#IeFz- zFxUN_TV3?N+2n5mwswANRdk->gO4}*<$kTF8@`_|KdvA?{SLo-!gpsG=ILpD+s!_r>|?vsGJHB!-Afh_WqAIf z)b;cIZB86`7kpm&A0S`T(@PJA(_5BShFW{u2Kvb0K13MDG17ipwTxxV5eou=w7%mU>$wM$`K}UGn@q>ryLxaC7l%tm@dajGZyFp6&h`)@5X-RU1{& z%CJy|qCgubD1!~L!zM}9Ytz#+Q6udfMtGH(?6dSBMmmzpNbkW%xMcvLGbf9vK-xq1 zDnR6hl*WZNk&Q9*j{T0rWlGzV?my#7Sl|kb!K9)re1E(%b|YFsHs{@t`c@-At`UfM zme2?=V<4MHmQl6#x5)BfzK&5_t1B2XsZ{NQW<+%qMtzjR_fT1pNq zd5XMyPX{GIyn&)QA6EfdNTPR$ydUDEOQ_N4Lxl(9v0erkNoh_b2~+4UE)9{4Od+79 zzYN(CogOVxZ-XQ|o(#d2lGUVC;6!M|ncj={+$|j-Fz8Xe3rZ-WD+2~JFg+Sq;+vz! z07C7tLGHv|UnUDe4JHc-ONeMd9L z4)bE&5AeRKSl^@Xc^-SOswH}N*ZY-a@bF3c+Dplaio}OqgaH98r6I;W<)>DcIX{1S zyOlEZ(}&aC>y}RIwm~>@%Bfc97@|d=sx3JN*VX`EK%l?dHkQn-YV^10C)1uJ_7GoP z%5D8|S4^UW=6v2QjpzGBA6s91bld87Pi5YBj}+&5xZmlCOc_=|r7{-F$l3Xo#ef;x zt6l~&!*43jYpfZYc!Zqq`+b&6$^n3E))}aQ*MMXle82e~n6brRTTHlb7Nd+Fo=*22 zFRgi#Ak)Bm^K+J^#d(rueOS@P<#j!U?|xm_>}~8%cXO4PhvwWz4lTCLgZf5%gmkRc zre()eXfn|aQLiP%@}O{uVHv01yp*>=;S=$bYYN;$AB{sOX~;SRssJKvtzW->u{@pN zr2Y+aDVLvK#`E5P9`yCg7rv{z1b))=(p&jEd<@aPf=Idi5LTz*w^7c(75ZoP-^EzZ zW6d!rdMd@*7WPH9AIfEx_|t9udgCodk^QoNUB~kXBytUrG^EtoYPyk@55-zQ<3k4HBawVp_qrudQBpB$U!+Dv;CploInW-zzWq=O2v* zC88CJJ)f=j@?>r5#`M%pa_)PQK5(v)KxSq=gp8PIRp7Zt1Rshk)3HZ@osb(_L0KUV zeJ3Cu+sYvzRShQ_wp0ZUpK?BpdET!JLlQ=94Y-qKrOpT@A_PGqa%_%}sMVc-%P81{ zrO*_k9K-iO92}P{gX9PsWe9|48!eLv#jZ8c31p~;x8#K|a{{RnfaR`8mMo$4($qyF zgAv)_0e7+z5J_ZmB0s!&dK5}^iUb94)d#3JA0U@d9;0b|>nsJCT&@;Gbz zv8MK*3j}l#%eLt*fK1#iXg#r}$i9b^*d*MM4-dS8d~cBrBO|`yb>(r-q2gFhB!G%6 z2!YC9Y9AlsQy@Ab5J?39tvltQbH+PQEqRL$E|8Ceif-TuklL4FNc+AH!?bUeoC8*! zf!N!v3{%@dZbeUP_nZP)i9&tawq+Q&H+y^b=hx!qyE8%(ry(CV3L%#2^8heA^_&Kn z=i!vf%=LOd={aSyp2la*>t&DC5vR#TVowTc1QF)A>wSz%9vgrvYL2N^%OjgbM0mTl zGIF`|Ih9w@6GDnwZKHYE2;Ia*%?Gq?*LbpJ)9Lh7ZkpXYb%6IMgRJ}R2P0`2Fz%e@ zh#FAg++tT$@@2DI^tW+?oxvl2f50>F1T4hp{oY{|#Qip$S4o$969a~}3TW3;qo=8x zBQO;mvfOu{Yna<|USGO&o=RyM6uo(r*5A znzj$8CR$W{_Oo!Fr%Ei8Lnr%zOz!`8aOr)jdn)B}UDvRWOs_z_oX&5{FECGzC!v>xN0;PRH6XcdrIHZ#CtUD@#skmPwN#Rvm^(GT2~Va};#d zb8V7V#>sa8HzyazD9lP`VuzxTCAg>sgk=!l{hu$ud2X?luyH@N;W{Yoj(73^bA(ZZ z!?@q~;%(0Qb~1-}9@=)lEc?nat1A|OZ7$fP@@bb10hOv42f z4-vYFrsO5^dwgFGZ8`-TdH7-uUja=-kLynS$}SK`SEGgIzPX+SCXqlhL^>*xAt|)8 zD#8&*X_a_X{bqY{@Sq6TWBxtL3WEq|Bu~zs0f#&2v=r(zcR74Hoo+xvAP50_k*Gzn zI=sX4J~FzOd)vJSGrUUbX&S(hz2NNF1qaLdSiY)LTmtcEa8*PD-=kAUjO}3(auw@w zP?_aX!*aYXJj|F5d3-BK^ax1|@UebI3S#H>8}}Yog-O#|hPn57-*?2=>P7}o5mPxY zUQ@4Z6%HlOG6eS0jpM*HpufG9Jhg_f?Q&(!Z5_+^ojQ7~5*@%=eaMHEwnaAWdojow}=QCjBM*RrQ+^S zAOHGU`7wXr3~$YRPy0Z)edxXKcEd{=z90mU@FW)ZbT*NWaO>0*)~On8@e$JoX$Tn? zkg=-GdqoQ%F_f3x*LI6EBgSceyH4jb`nR{|J>bC2;%KZdjM6*1C&cS1pO+pa&a-1( zs25uZ-d2Ku$6fuD*SNpz^CXL{rM6A4W1|E+&Iw);Rd@G+vTQaB>w5jG-#p)T?2wiE zykC2t%B0tnhNz1j*0fHqwBeRY_7=e>y9BQKHr4K5>ndmBhRZu)5tC;oA7mq)(+?sR1g!$+b-bW z4zvH}DF;hALo$)X*56P4V@*GP6xsGK|MbUSe(GQAmp-TEGS2A?Qt&i*MQm$`q|-bg zIwZxk-tBpWRZ#;*NRpCHtFO2F<@rp`J^b!?8q#l{KMbg!_wDN+{|xl}d@3F5XTMx5 zef(aS=CErEx!t2cJtlDyr);(3+;4O?%rfTSJWfa&*oVv|%OImhb;_}1YXKwXzy`EK zf|Wshb!2K`mZW~ ze)rJOGpn=_CJ5A(yT^pvp$RAlV^+v;xHJ^Z#L>2~1`CIjyYwLvffO1^EJG?M%gCTH zaa4))nNq4tm@MFAlt;R#rnWuwmH{VgqhWrQc=k&Nz-_% zuO8O-&jK=+ebzIH4>r; zjIQ}ttT+P7s!L+eWUNmfCeuQkEmB0@Jr=)ZY55FaJ7klo0AE9wcOT57Mj;-bu7cfJ znSRk}ULErVvrKls>KyHL_qzFJbGXXMmw*VLX0uy<-N$8oxne!BHv;$G48CNm+A zyFbZ7j+1)E20|ubQgQ|&bve~JL?t_n%ZWi88dUV?eJ^w5%hQLazy96jTtfvIH6XRN z*EO1GIxBE`vTg0=Zx)s95FM8>n&K9ht36FD5l0b68pkPTu0{*SKbzd+*A@`~9L1XR zEHY3fRM_>jl$e{ybRR@Eoai194j@lJW`A?WeQc%U)Q1PUK(s_^+B)e^yg`+clXw=u z;elI4_vniOtD(j5_{Eu#g*?Kspr{PY;c>tpH`Jq-b>$9#ow74hP|3!~2ss>l45Cwv zU7AHA-dBKx2^)gqgR53Zq{DcJ>@4{J$v%3d!eL!tkYn%d0*S8S1bYMH+d+T0Zg|v| z5GlgL&36envP3Z4Xry+Uh8j%J4LsCvL^pF!0D|VB9o--uG*69=c*1TWdT47zlV`nOPNKX zI&a%rhJvU-3eM3o>>F0cgj*@-ZSPx4nTX?!@-;dDmG(lxL+pm5JC#nOf9t8|lSop_Vvjk&lxW z3{&5`udUq~amm9xy^P;|H(!2xo+eL<6sC890i6Br9@06N5nDtCB+whfzx@tGD9!*G zkxPoHuQ>}*s$*?66u^YqU>An**e==qw(Do%k9X}8G#+xt)2S)2JzbsH(SKd z>#J4WIVA(%Drl$G zpFe#%mpKXWlBMMHvWIUzRSp5>ebReHhjte4mRKnCQ=Cd4wsF6p=iSW+B1_T@Y@V@O`0iVZ zwqUX-F*GPkn$OCdpPsdxbQm;?gp7}PAQP#P; zJe=Dmu+C((K(EKk-n%gb%~E(01vG(-Py-Z4{UI#iLQ$n0ICho8ZVFEbg#ck8@;L6z z;^DW65MxBh@gyMVKw`K68WH-aW>C09uOK0+?4vm{*YIE`PHZ_U&64Pjh{%X?xcQp` zk5kiWJWg`;4^A@3{kUlP zMdVJIwWM`fReMT#+YVa2F|d0zCva#C>M`_gq_|7p$+YwuZUy2_S!N)hIm{_~v}^6v z0rUuW&n3C0RPPG|`zCkW0^03qxC|7E4CC{7o}WH``1Jj!&=msAD#MY*eLV2*?TeJM zaih#pa#y`FN!QnU>TY*Q<-+H={Vv^xX$+=e@7A(-y~P9~B#Wjn#w6_jHTgLhfkElq zltbz}nK@sR4Uczi1N7*j#wahEPcrY|tDwTdr1Sj;l_9%D+=1i7=WFb}Z?8xVjE1`b zsr$OT1$*6^?KwIDRd$Q+xNBI%TcL23)npi+lfuAo8oRC4yJwvC@UecmqlUtdhRBX3 zvl)Eb01&R`1HF}VuW|FC%h{1;JR>D}D%102n5eEbBIC#?3h_aPG|g`Z>METmm$F4{q}>V`1s-M-oE?rtVzp< zQ;6PAQ`&6zts|;YPC43&`(k_D?yu`yd_3j#psBjYeYb7tf@-j?_avGu70#a9MGBxIaY&qp&&3o6kZ2xfwXGB7E}TgW5JD2yylrV@ST>wN}YyW#C5 zSyDqDS+mZnb1+#i*U5%-TTgkeQ)g;Pol8hJ^OmbD9H9VG{Op!SxU*D4 zH}vXT1f0;21120bRMj*q0*Ac@8W4xM!Si8Icksxs^@n;BLU@2g4?4p^#{u>XAuJ-K z2`G?$NU`eB8jmBn%VB{Lcj;al1vMtk4hh^mZ>H9b=z%;U<>OfeBnNu19qHc#D--fQ z*ZW^Kw8vJ12I~hU&0rn;V7-oUntFsc;^9?GTS0pc{73oazQ^d(zXs zHxWtc_&Rd1B~6eyP!Jh#vZ^woidfxy)~TDTo+6e(;^~c431iZ*77p?#N-&{l&N*zN z147lv%6?J6hcQ%SoDe9fj?C%leERO=I6uR|?q;~#)_voHu|r@4&hERd7Jc2?wA<8- z?zd-KU{MX2q!dWR#=F+9_eJ`;tWlN&HP7N5C;?%RzfK^9YIuv{PZlOMCp5-fdX@uq zF#rvc5i)e!dvDC8hZ;*AJM-2Lz74oyFUBQxue1)};ETZ8TO?aa5~?NTR2U^S8A~(} zMbSDa@D_L8=M~i^UUav$mAOTYW!<@7BY#2ag};FDnm378W^cERXF!6;s4mlq5V-o* zW$J?3^*MP}Uj>&4m3hu?6F6a1WML_2^Au_5h(FNtIJ{2T&NFlFmvJ7R_MFR@Wge#U z$>(7D6TrW4HUe%U+;eY$XKmGF4pS^DZY1ifF+tpEK znd42iI|ubq);;#N57ry~cGO;SAcB%549JYEl5>_I6qsSd4-=N!MoHJfChtIVLasILn8;^atQH2I_MM-hZ7R;1W~vi9>>3YNEs;*4p&O} zL3>C%j^t1eT0%fHL@XKTaB9~MD&v%8%1PjYZgOjU;11gH^i&J@LD)3VAB&y8#0L(k zNFIP|ruPTKH*Jf6GAN)w2%aGHpgilNp8Al1#Js_w1_3)9mIk!(Szx9pvWvC`Kc>MmAp;sAVWNo*$BWr8*h+kExQ}N zg&8Cp9-%{G0S)8oUA8e8?-Ak{tZkB-3mq*b4Obmg^c$xLE}P|qh%h$ZWtYKtKW%f( za_iNi^&~e`icrwsY}43feZ!*O`ziM@%N7z+hC-rc~bLL+&I!2=G z0Y>mIiAXxY9F-;mBNEgBH-Hc*g#w}jNqx?sbCjr;K3z2-1X56t4%12; zP-iGmJA(nU$`D2nL5b{&en`fAKOH0>N7wnIja3YOKmZId$Z`4~A3E3x{;ehi?vQ}T z0}?DA&Uo(@dF*%F0a1g&g|1y4#nty>itu3T(RZ|w^ zYLznY*;5RS!of^bBbZO3%_r)oPK!uj=kTqJ~^+pzck&1LmoX*ss|&LsrLmy&Le;< zn9o^H8KR7BI=U|?f0}yw+Wzx@`uV5r^V|Iw>9+Rol$)yNMP4U;x)1BL_o9g$WC$n* z6~?$vPF|k0b!^+~x@v(`F6wP0Q@WfYl@=Z{Rg-p^@-ILA^7FngXLpZ)@8`Tv-+u@C zV#{}HiP>vMEQlo{^7KLdDpo`LZB0*eB-$SJGGF@Y!5UEjVYHATF{L4Gofj+4!x|mY zIEQWV4sjnOccdB;z9=+g4BR7pkG4|xPL z6ItIKkjMW846r`ZJQ0j25>9}Gf`}~2oM>*=p>AEE+0Bxm!8GXRnSe9W7D!H}V6X_H z0XZU&gQfu|1eC)pWCp8|217{qcf=zj==2n7U{H7z6v7di%{&L~z0$(~B|AJr8ayAm zjv@$;kR&}kQ))-i!$$h}9y>$9?v@ShfuM{)M+gJ|tS|65*n4J6%no!)`c<>kKPwhN z|F1}vaXf7dX7uUq>dnz3cS>LK4UDT(`eS%m>oPy{alOMl3n+*(WJsodct8 zd0Ru(L|1PWF^H1#X03Z~Wf*$PlEN$)wy*9s7uhUI#9jm4cSkX!JAkI?=)>LnzPmXh z!U5ZKn8);C`gqCnIXWqcscZKzZ-zkF)>2aMI|=}I3?gq;F4Z&neCm`zSjIByZnr3% z#_;BV=yi)^w_;!GD!YI4f*lcucn{_U5s|nRLgK>zq7)_r0^g)dw0rFwLq-S0x#!aw zTjy&|rKM)bCVQz=!hM$%9*Ag;LT}!M+%f33huJ_A5K?M$GPFmm9%$q3wDcs~Sb15c zH;ZX;3&xrjc89mXxZQ56h3QgUG-8k_W9c1=My9AUO~BC^v}fv0PVS59Nj*X(jpN+L z1i9pq>e@GH{d{Y>ye_eq?g=sUXGYI@hkq)=rYK`sqsMegnp3m(>60rb=+~F$ldGQ2 z5&}0tQWj-WDf#KS7r+D|Wzf(GloF9%tKH@6+mCNQzusHE*|egg?ss2qX+7QSKG?1W z>C|Mbk7O*M3SNb|8VmAjt6ODc8A`vPS8_j##rofi#Ox zVkhxLh2*Za!jla>=K-6w^SJaHBCw!0o=Qkz++C;0SOijstq$QMlc-pWTWl)cQ>k(e zDzuAqv<)y#6`)y_ZDS+6`o1GLMPcQ{lAu{t70H-G0j#W{5e7?9TwuHL@VfLsJ46to z69EUjGXl~hF+2r@rMV0c=~NLo@hvo!9AO=BI$#Dkg)LlN(cHsnE!5t{Yo?g zN{$>9=@3U&1cTbBCXo!>_2fa6h zL5yymf?Xs>a+*7YU=BG#0Pfk;@%VlquEK)NRaX?SKiu)H6QuDWjxp0ZR`FJ*SWTwMK21M!u;{4 zw|i0jx!&ime!Xq0T$}yU{ngsFuk(d(S&M-2%yQ`CEB>}e~&JoSpgH;bK0gLEv zea9yHEL#;!=91=aPT(e?qkv$m0q1$!Z{W=l*ef{V?Rmf*`?#_oe3a+JmX=BHJEz_| zXP-rRnj4;G?Im(ohjZu?DH+*{I1w;P2?=d~0pe3C0K`Fwkm;o{3R_0DpF?s=T3 z$}BG*?%)P$#JT&LbiO?OvadgE?e*h_Ic{UdFXePjd;K&`TPH^WX&WvAZE}ODS?(Jl z-a6R8d*he*^2^&#|L3*by9s5kX{}+~wq10O7Mo3*)F)0P!NAHkMqL6Vhw#WHzkK{* z?f3P5^Sedhmx_yMk5kU;dhf6Q@Z%M2>Fs$=;h*$x*M8k@%RcUnX!I%=IgaP2ajbDK zHr@JXx!qs4ur_n_P`WaeJh#wiVQ9v_Ln9_d%|b(3@^uvDRuQL7?}Z|MV(jre&?@B2 z2q2f#0#UXua14{)-gj(cjiGS6N12FGT-mEOH1V7k$WC(v2boVp{Mdvcv4@g-3R7PR zkG{S69==Cdceu9EGonRJ4Vf+VLCYb@_z=Y(^1$|lSTCc1-)r11`jrk)F29nG*?R7!-o(=^kfVT^ky2`xB)6qCE+QgiIfzq znj}I+nj^AHAWLotbrENCbPF{T3Oz?U(zcCOLgVNhIS32KV>le$pbnRZR^tLH+#mvP zp+R#TRw)M-a%@dLK%FcJvqw>cujVqEAF$A#)w_qHP#-6I0g z&ECW}&H=t2KlRMDuy0;~KnEi32Dj)4zz8FV41r7f?z$Xly0BbHMx`#kiKIuKLD3eMyZeg{C2qI*%MK zC2|Brx<<+j1romf9tawP0cYsDmn9z1NGcihq)iAliBN?Al#nCp+67MZW1%W>I652& zEx3myM#;^zd6>d~{m(uK5^6|LMJCuG-{$&X0X*#+@R)e?UZ!^^<shpZ=qR*GSvCSgUt`8e}uf z=K-&q4&{gh=T`D8w;P^MFbPNB-`%qbNB6MuSNoFQ_I{sK)&*2VgA6JuMv;iv9pxA= z79fK;&I^S@L)Qi;}Q|EoqL6K!F&w1w^72cdgq?;?>ZouXA>ZHVkZK*o%tv`j_ zE_%N8+pu1?`F6TJyM{l7xLg@exDkIl?|Gr^Ot$mOej`0*-8VFQ^0;0u-!E%Fbtq)f z%U*E-?vId50E48m&Nm7ZVz{xpPbpt#u{Q_yxQjfEz6Q(vwPqPaus$`D=aIu3;#BWh zvZ-3>5Y?S_3h87go)Qqc*B)B0UeLC4A;3iH6q?0a1lFtlVt@X}+g19$uckiFQx5fh z$E)tTr%Q87dz)J4jL0C#8G-QSn{$C#p7G)3-1|r0?1L>9gKUk}7!Xb0{XhQxH+z@% zhd+HD${&9JpO5$c$%hfc-Sg#b)lX9d6jaBT<+iSSI$!!q?BAGMjj^jv&TBO#ZPX|- zMcS^DQKrO-g2}f#$_CNc8uIEvN})E6XV^pnxSsMXNq_-CBp?A1QhKb23_=hm34^Dz zNs4{nee11l0cUw^`4~E+01~L-3+yBJXu-{S5epE+%*>I}IOahbQ%)V0qeMbadq$B; zFtVXJABnkL7%(1}*dRM|aTgxW z@UF<c{#O&Es9vz>Vsk)Ih=fVg(4DLZ76e1`U;SS$>QV*oa1RUDgOQc8nYLF(L zj4fypnJ|ayV-v2Vs~_w$b(%msTmcJlx`#5vO#)C2ST~4xB8}n_DWXOoCA1Z2BFiYY zi9`&-E760s<%z|I_qp)f{1}KpSJ;E>mVZ6h+oQwe*aExYU_&2vrRX?%xd?`89N{U~ zZ!75lEu8F!TPPB+24(NB!J#BXI0%@40@5h}wurp%6X@2gHyP(?Z|V``dD|MXMKB_$ zbFC?22X)^FMDh5ziWUIO;{@n2_fU4uO*kfc5APtbmf~a+9@UMfkcneVLXF%2XaK7*M^7+?!w%fCwZ!MuLPuwY%zz?{Vf~SGK42MhJhU|!c@;t&Y-ZDv zwp<5zij>f!-}zamDmjrtNYIRaVgqkS&tsaPDszt~q}6@6ah<#fZx@qGFt!nUzUiCy zlYG6UT`cn6?Pd_4C)4}5-J|t>7GH1s*imO)v?s$G?auVT&!LwFp0Z{IRZ1O;p9 z4ab6zs)PzT~knEwOLzYs4VqNh6#QSA@Om zsYT@Jh#eh$(1D}VMHJK1-RKmea+)@|_;%K^hmMToYYv-Yi-!V;6BY*UE_|3W5t;X- zD^4dqCOsmAIs1?vxA6xp#y40sb6r~LB0CZlD3?&FY`MoNc?eO+L<2k`01ppXi=I3M zs>Lmu@#x72beAZPfHH$oi@13tu(m*U0Lo+!t6&6B0*Cg3`e;w3OM{eL2C?H!{7~GI zHxu*2x`G6YR3%4rm2SCH^-xfQtPu&CP+CNfP!IEihDl*G+Tnr;3lBhoEg+p<;;5E= zmoO4&2^3dl6G(;=k{{+v8jYb&k4W*@&q|^zCDBO}?J5zSjHHp?*D}f3L#P5YWmcr- zQ?1jc?$($`xC4$xG#~|35Gg|9+g#6N7=R2Fpg|5O9v=eEw=5M8orj* z4;ZE+jxZF6NF*cm5Cqr&btFbjJ1T2#R8gfr^o>M^YA_U;CvW_Q1D_eP!($V2bF zx-o)Q_tV8qLZtLw!w2#Zzb^J=)&j z)2HXYubvzN_qf0P^7HNfGXI;QN|_?H)R8O#9=^N7qpp(AzwGj?KdDKN5)p2O<@GC< z>mS~>03oX}!OQ517}D}}m6Cayhan3B8`VGn9o&K!_dr1KCON}r?|03ETffOr$-MMMc6{P&*g;l;(!tcH7WX)g5@OdZie!^$?wOL4MRcnr zkLMx%r~gl{+v^{%U)B|*zKyHgrf0S7v~an+YCnB3#M?&J=kw*$xrk9tKsd1)&l*U~ z1I8_Fd#iJd12rr7=|#p|o<9EFzx@7R{B0Tf?b>fYuJ`*NL6k91FH?H;FU{;_{2-xl zTkz(a@7KtV=>vXg55X}2i%IR}Bz>Rn(KDk6cT9*SivaiQcz&w2Cs)Dsq+?79oHgz~ zUkZ=z@Hh;+gm4DpLJP13Iy_Y!;aMEQZ~zjj(e5o-pDt4gU6C;gg=fg13e()%X3pJD zFXK2(le~;i`>?OQ9!J>_c}GLTfMm@y0$Xfc4U!Rvi0%{}R5=xF6_R2+(8k^9%FTmV z>z+kNPcel=HTS*`icVVJG{#e65CPATVjv_^&q|d{H|s5ekQQKbCSip}Xa+1nREY2z zQo8q~Lpv^;ZZwBSL(mfjQGo@E=pI5KvIYQYi~=tqK!grZhaVD#NAkyM=^b`#TIHzG zpfi$7OP)iTdk%(%7=sX3&|pTo86L&WK#o-uWOh*EKp{oL`%x^;_rAfS!<~vC)ZT~sL)HmUBD|9bIZUPKhfRql z+QbqVF#x8JqE8+0)SKpE-}}ckV@b7*+qw~lCU{Hbuw{yf0?fl{+m~UO0~A4?hGr(B z?lmG93!vu7ym^l@hBf%{h(qgPhLci~kX_?}39G8rppMjCMD;M*11ezkXXawTTZKlG zu;{5(T10mu29ca});8*HOwc@N3?+?2PN!Vj3rFOPQ)0tuNW0a(JKw5~86r%6R7^%m zWnchEhzs9G^l*la-m7T1ph=+H6d+S@0)&ic6~$D~SsOP8I*pr~`(F-whnf}lQd>*C zbf4UM$R46y!W)@#^Moh|vaWAqJf z_^=F1>UT5Wwq>`qUx7~Hv{ra`iLa-AXXR_VU~124hV@w+MLqV8zT|$GbBFYl!?wu;fy3EHs)+b_ns})(w5evvwLFq-gd8@d#!tGbwARC zphxdgqWny#VpKuN*m2}=N$R$gkF0z1LddCHVt zFsk$EPXWL@!E6KzSiZXbQm*C9f%uG4f zvW{~KIE$AtQ`5Y#SsO!Sac7Ku*|t8;R1@DGpMG=mtE;){09{#?Jb$uUbJ#R{|tR4PM<6p03t$_Z?@|Jy=*Ec`X zIH!P8@Chp;EiZ2lY2WS4WH{1%03Cb7#l9dQv!j~~vG#nd&HEf}T_UJwL(~53B%BxO zFrpG8r2AusHTjJ7SA1SSmqc^kIe3^JJ}&d~DKEYzXWswX`SH-BeCB~g$M-omt6|HU zX+ZS>G18x(qbMJ&cGXYw`3dITHXRT1xrWW!0#{k@E7svPKswt$SheU`FMnt(&xg;s z^!gX{!#Qv9kAJz}|4@E<@~F*rCh)7zkE$i|bGvOlpA$b(b!|=QB(B%f2nU)`-{Xm? z9Qyn(x1;Ztylm_3FqZo6`gI&J*3qWzN5O{c{?^;!sn1_)k+_f{Mgy?6RJDl$_KnUH zIGKuvA9^H4{2l0M?OWOZGgt6tg{NThx#F_>nr2*)dm0De%*C@=&6q^e3{Z-nQRX<( z)pAW_A&#_`D%LCNN(u`ZU91`d^(j^IP<0)FsAHjVDhL+-Esu^?lqN%md4U~>T%9}j zU(f6?Ll!_L4M_?@fstO9Y}Bl8ecER>;jFbj#=dEpoJHkK{#aFa4l)Kug&Vx88Dpsx zmek0J&ag1@{O7jHk!lTxY?f&766_pvsm)$HKSNzO0xkj-6`-YsOO%Y2B{?lY6In<% zJ6({~5UvpX#bUt>W)qD#qr#cN*N$Xz7z%QpBWD={a*F}?0uK(!Nva4*Y68S?`AK^` zHwgg|6a(^8cYpTD`CAs1NaZ=LGn|y}BrK38^*eD|KFL$abc!O8Y@KT!ht()YPM-6& z&B2q=22Qps!d?dH^EsWuZWuwzK!XDO3R*nNxDzNEwDkNx4LrcuBlHL>=dsp+rbiuDGRvSN$J$rQ2X~X7XV-UJ@&M<72)G@VIXf^ zzV7{d@Z#gsh>p7I(d$nF?)h294qX4||LK2OE7tb@&wtr}{q^(bAEEs7qWJky{(gNt z(fXJZ&;P;suw}(T8qE=*($359n*C|@l4Ba)l()KahHSn)vpZiyP{EZ&fpS_bN7#OJD%%$bXf&S z(>i}VkJ{_qezE-4*EU=JiT;2atZ%pRyjftoz15Fzqkh))502nkaf6J|Ln5yazb(P4 z%`fd^j9WZeYGCz1<9aqZrc$SNU#<~;(+R4Izqfywk23cCz3Subk@~*qj|#1>H~YH- zTf}(7H+p@0yN+~%>jE;p^?4*XBbGQm{gSr){7*msdffN#-~VE`)<2nG7c<07+hvZ& zc8kwuWf2p6AB>5OIqI{F+G8A-w+8G$gC?TOwwZ;dDWqtKoyW?LFKBxrbB=xVLqoj! zqLSth9%J1br~5_uymEQ^svH7{%U+Haq3Wg7YpK=0@hz;be1@zqOu*ird~WLnf8#6B zmJ9OGQmBPE@V4N2{;!o`Ecz`8uM#^dMZ;l4h}B-|Xp*Sa)Vz?T)auLFoV%^y59FTn zN?)a?MJHB}sI+d26;VFTzplE@(Nx8#xe_UwlUeGVla&c`c@Z;qX<;+N028IhtY#41 zGie^LX@Ud|?uH5;(B*4IsENTz5EjIesfb{ij3W0Si($G-&;S?GVSc)2RYBxwXoM0x zMUfMf;6r(;L7|}41m-+>J9@dA&M@tG6~0rTh(s1@suX0W3=~65mlmp!eKrbNZ%Z{b5U&n8#)x;8;4q9;V`j}g>+fRpIIEU6A^dH;SQ!C4~l0YR6 zw>IN|ETfxMadhp#+sUVHXjz2-%+tlv4l^%-T=8 z+tc1Q9#x2bU>>3S6c=B%Qrp`$tS3vuW5;?a3r)8C;jPAwN73bZK;yoCyS)GDfBuL6 zQy|uBG@#kap=*Z8C2n`M(G0qoDk`i88^g6pzO=*atJWvj=mARFy1C#Xp zayBah%Oh0b(2Opi71TQq5m;QqWR)he(KvWq!-u2vql07e4${e5L4Frjoi@F+L!FnA z8QvcAAtXl$ zk2hP9>xz7<%lo(W{aR{Uud8p12uNdg5(cD~hfMj{9lt!EKYn@sWzPEC8Ef6vt+rWL zTaIz5+KtyfXU8%>?o_X2w80&Hzwcv5j^pexs2O1>@wR7;SBsRRa$X~B%t{G!56m5z z3TwpTB`{|33*8of$E#1+_DGFT(D6P zSRC-vZb{>bd=E?woq!KXC0TWFYXRbFPTZ%E=3c6X!$oN`D zoj#?ZSbvu;a*6=~U@j4w6R8{%urp;3Q?LXnBjm6mJA3b9Ld6i>U@x?8o+5ICfC!i( z2)Yy&rkpc@FgX#R>D;(l$`TSXU%6E@As8-7Fs5fsW~c;1s$l4}UcJ}^5+h%gGQXtv z_+2Q}zpTi0!i8x#JWjldyhSOQ2bg9@G7@wkJ!LOi@%a~~ATeL3hJWFuf&xM+=IfsF zs^e#0?*awcYm}qk)FCG?;iXMe0l*xJLg6}&z0hJG@6PO|bK{=vI3_XY-jqQMfn!W% z1$Mxo<9s5@+1AVGJCj0Mrey<~X7BLI6-nfhdg_#wo$L0KiH5B#OaQV}&O-;-D52_j z$nQdb+`8Vbx5(x52dOlXi^`?0rsFY=r(E=y$MbQ#|Gf;laHB69Rj}9gckRkQUH|KU zzODSU_cn*Uf#&Efx63sirEHi6iAJj=f(5|4`olfoH|MwL(|BhS0nbY9;ufEGY(Hii z?-z^IF|94-#G_~t31}t@k%=;u+gp#lZTn_W66?t+B`B{@d@%3eebQ=qKQiiy{Q=@x zun`ZBx8SE=w|G7>JhWW4WbfCqO#kaV-Yx&SJg-L@xNdb_O1HM|MTMV0{`&lmLtIjy zzFN6mID~hO=WT4)_S7$L+Z$p6vU(M^y!Z9G1v*)a&xY-uw+pvXXN!Pu`=0<(vU>DC zezNjUSb)Q^;mu*C0!{-2Z#BpC7V`*U0Bw$*v)CV6@_yexfBfhJqdXQUQPh@eJ~@_~ z`?kG5dBgQB`}48-8v;FcAqk0oC5^-0p2-iemQ~fPx72WhQMY zNFkX`<*d`EuRk%Ipe!Mep|Yf<$(YloK_HZ;TG|2m%PYx%3GUEI?D4F@S;$CLB8Zn= z>eMcxI5H6i&(|d9w>j?_Z}nSqFLDYh%zI%IhXf7qaC`Fvr@~I+*U6q?+9?Y~2K5&#SPCq1Y5Ms3GY$n`Kh<;DZ{RuCPe7OsiG&E6fMK($U)+!U3WC(b6cCh# z@9zgoQFU~P$?)f1txW@d3&7kold$_zFKo=6pUwo_Oj)JW1li$wbIth`c^bi z`2O*3Zyz$>*7dFCKs|6N3&m|!HoeYhf;{?$+?3sX&2Z#3$8BlcuCCZJ(3Uc|L7f-C z7gK7`8u-!F9L7~Lo{((h$)l^ndL4Nb4sue4JNN)iK(fCI|2t3FZ_Gy`g@i6K(bLBx zU82zOl-AIv<=ebTpldDg)OvldlQ=LrF|ppJ0I;;O2wJYDt?(8zg{cwaIqpZy&!->H zdn`BJ^BH(MX1TQYV??oZYtL}8a?8SQyuzDU_Pk%bB@3u!Tl`8moO5H{*6Z7!+U2ME zvDWtw`_@+T0=OBh!dheuYmxoD`f7}QJf8)!TeJT7{_OG38H(e58mHOCid`EkuFUEN zz5D*WqNF}C6H+Q|(bZNSlwGU##W2n1VpHhbh@$42@WwZc)~()fIVCWWY=>kIf-q15 zjbJh@(pXW*MnN=i<@G>?rSu$Fx$xv$;>aUH3zW=zLCqYJj@)m8Ni*M46_5;?!a{&F zM-wU_6lg@`_7lLD3nsHGUbWBkPPaY21D2pv-u+pn7Zh?}RB&W(kS)q{zqf7`9st%|bY?jq#$f}BW z9yucC-kqnp6Q|qyHF$2QHm^KKqQ=SK_#+Vni*p}I7k3CIbAj> z7stsFk;$Ck&{R&>c^@FsL?xZmnC0v(O`!q?vb+*kK%Gfegye-eV1XR6$tlwGLh!%X z-8{2bV6qd>!{3i{4AMHX4AFWj7=U|Nim$@pG%a zs8F**iKHas7TTRlWG9>OBq%=Ga+%}N7C&~7*-Lq@{_S>|{ZT|TX=j9SzmrCKst&{n< z|MjQKA8h&dciSKS@TcVuIDY;Q{rH%V|8-v8;%;>_9iAhf7nI_BSrlFYUcMw^QWL%* z2)W@}7Jq({cF?Nkcw3*xSaS|!Pb`M-eKh}m^(wA7KmaSTpXV^45~)XpNXvV23%kq< zO%fS^$GE?Sa!lZR%E5JTH~W^6McKY-e1?DAm`mKFY>|Cs)pMTbIQ}|+kxbMIxb`1o ztvqgJz1~D|fI>xC*nmB<-)ys5KNkCVyZmxIKH55~uYv&Is{HMyJAh6b%U0S)L>@zQ$cn-$Sb=Uc}O3~@7v;&}*%jJ3j|100`R7d~3z2h=M z`o8B+EAv{4d|YTb^vE~l;|)h#*ExPHcFlw^A9(OttoYUGi}{;ZEX&C=(snv;Ox1P0 zpmqEaqP@=F_i3E#=ILzo&2YSp<$26c+yC8qX?5M>*wI_&c8uq?k%GRgOIFr3VzWZ}(p*xfC|jqDXTG3auYH7olF1a@ z1u#;~?tTJE&t0z7@-Zz<`_hNnpx^6{HEb^P=dN=I4~&r|_sSztVApJ}!d zb@oST+ZeAoR}24ke_EZT_&mrU@$x|sB#CJ=STAN(nFc1-J2il9$nCXYhBhq)K6G)ei~*2 zsKFx0WG6Lm2`_synlL0$Ef0S0%wITy4E{~vnMv<5S{`PGxO^l;7SbgLHZ-Iz$Vz|odn`KZ@a%=#{qk( z@c_i7NVw-Qk@tP)ecq@?B4T_#_`LfMx>!cthWaSqg3f|BS=dLr#aur)qsi@X>)zq1 z5SZL(vzBM+^5>{E9%e(AIl2_1@V=OiJb+(Pd*n?^7m<+}$jI4e4rPQ@(G)w%8n!;j zf%1ea^LSuHo7vs2ANDTVj~7oC^iBOrvcRtP28XuryzR5h&OJevv*~&29a|MWsnnmLDwpm+c+RxG%@_37iB9!cq&Lv{*|6Nw+*Gw4CRe{fXha z?YV}x!sTzxtkq)c?;(avun~vgFQ+Spm_}psa!6d@r&{h*gTNJ7vdHaeD_mT(nX+SM zRK^J>f9i#8kO0_KEg+Ykcv72VViR z89~cF_!T}06y;p!o$0xI;9$1bfScA)e@{Ki2{XKDUK)Fg?*pOe%zMCo?-1I@00YmQmt}*OZ>9kFBSLi*tW}cF_^pAW0d`P zsAeBa8?--FG{^~R{P^=f-}=q}A9nn^|MB0IkN=BZ-@di^?WbSf-pXI^3$?emKV8jq z+*A?&N!I?XY-j-#(ASuA%3pu20-Gr!-M~ z(ny*%I$vl~cBF%Z-EpkgDz#pQel*+8Rn4Kkbi=Yd2OvIYUW?7F&Ulhn`F@8v*4u!E zF?qdeP8c<1GZcB*5Z6kkuCf zWj@E(m-Qlj-hO=lyxku^o_$`jJg8N(&qKq~n$fWKoR|+5$Et9&QWt2T@`@-+naEdC zu#;{8R+N=^&QKCLq}#VEYKwrvi~-&NxI(ofKsQ`~q)`z~Tv%HNiJ#uAw5Gf|z~ z)Sf%E4jXzPr~|w6!r?e3%uih-VOvouhJ`zkX$?j*6xD8X)#3)6`6gD(>2=8t6~+FN za+C9vaHJL>PaqBjNlPJ(CUtA2AN};zYM~_(rM757XUj!~mJNY07L!bvOh+}7pY{Ee zxU?5L9u7@pV;pia33tN5S;Wa0bay3Njh-|@@xnPe5XR;?()z_L3|-EU#fVa1sHQpn z1f2XH@9Ut+ru-#MOo3tw4rZ!|A)Nl@1bD_bI=N3R#vwYDqUsX;uDT>qpaUo~;8my6n5$RWRZDv1dQl2h z*r60rTLsPhl6IfAs;n~Xknz^DBsh8D9Q!~2`pfOe=e@*p_3QHSZQ0(Iiy6)Fpda#0 zV|?w|K?#bq8o)HK8QpBjsZ#BJ&*dt!HJ-6;>4kz;?HhBs5L#x)8r-vI8KY(~kmWQ0 z8q1o2IELA8L7japaArrAo)1T18#)u$4xyiI>PQyHkV(wCDD-@0kv7b%?LbC!RH`zI zpYxxg`CER?AaKtv%$(2LJjUaZ*+yrf)gC`L##7nRX^cDu_F(@p?-9ps zALF?PCpmB*jMO6^(P7;TU0E}F4jsqsaonEAqdyYQz?dYMF)o=k=bX=A-6fkDIi65{ z|9*dj?mKSz>3oB})ltL$ayegtr&;q-0 z6K2}yLTv?6x6wNh<_7JFGbTJUwB};j6}u-gbWB2lLewG_L4+E!DeT<8IE$yJi7+!m zeQjl(TV3du-cCBe8Rq)ZZx~K6a5Xyu#aEv{jvP!*gtx`H6@8i#xb~AbLs(%8g_u~X zmcS^;vp+Utsk!h7R^=2_H_kE^)!`kK3g`|)E&gTqKr&LJ0VTSO!gBvD+idWDmUJioqu1=xG2IlQWZmW5#|lWZ!9 z$S{vN%v=r$RG69Uw2qzf{K)c3oPW(p!9*m?&&*l-P1enXN#g_z0ZhpPkW5ItqG-*b za$@o!p0EU%Q^wy+OXq;_CF4hABH>~+s6^l}aO-L(e4m!1MIaNw*1b{gU=q-2PCx+& zFQ9!D`riO&cqN&{?*u@93rf+e{ig#CI?8FiJ3S3B8qmHtJVwyf_Nb=OI<#MsExz5B zv4EJ@AGN-;u61@-a&HhF=t|DxoK;y&kcjoBhbt+n`>M_JsaF;f{b zLi^LKXul6&k6fI!tpHqECYo_;<;=7Ss9Qm?HaDBJ=Y0OSW$IauLcNu}_c_M*Cv-;+ zt_0rO(dmEgpFjW8fBpCW>%ZUi{q4%9V@HsHSJ4v1cz5Q4bu^B)9^qrQawV1^`zIQ(R{Pl65pEdNgJ6iZi zrF4t@F>gO`-yfRCaX+5l^Y)kL+>iVC^`X@F=RSWtelB@8`6KSF`99+a0^3seH01D&MCj&dw+b6 z@z;C#{B{0yzyIRBEWTTHyH1VU^D!Q?j~O_)>V2E8{8*tp{f$dSCEI;9-&gI@Bh-Z{ zl22dKPO5E@hJ~#2&eFNO>pS=F`FY=a4E#6`x(S#w*UYx{?oW)F+TCy(Do^yG zyG_=!=+kVJ4F+S)Qx1MoQQh6Rl;Xay6m$AnjQHix36K~OkS6Qe7A%?`RvkKA9asq! z{b)?rXHZH$T$OO_jsQ;Z*jLwTCf0?$SY03!wz`$#&8mudsRCd+a>Nu85o3-XBQP_v zV;=bx|0K@y;GeT(CLt0slqzunMgUDlVossQ%PS+Es4XVwvLYFj0tSRs|MCs>7nKa- zE3oy<*jrpH*{ZXkOpr7$aWeK{Q|^#t21B~1MUo|z4i$57LIVSeVRC=9)w1~-x&Uby zAen-gg6S3bJ}HCslut;|NuEDHXMo@w@-rVW&+OM%jm=5GjIR;Ygr^H7H1`t$m(@!zQIc@CIf0bB61Lx%;BUxUD}qrjPBI07joWeg!xctw^| zv=KK!o_QvzKk_~?8H~CBthI5v-L51$nIxbn%lbDt6(&?8hWnB^GW9nF($svwDe~E) zgbqNkC4RrF`Z_C;o3IHrAtJ3-_X(}nw*~FIheqbkUF){F9ai!g_nA4&#{;0uN{;7| zQ+<>DbOGvH+9h~-cK!AH_y6$k|HChT=`;POKkDu8|8%)t7u-X>LtFNDs{9RJlZ@G6 zGh&!68BZ&h=sc?e1+h3i`^8|z;O#t*Yb$6MDTp2yxF``+uy=r`DR9U1%n?1y?y zKBcEH;QoAN+B}bNa9Z9t^XB7Oc4>d^f7yYj5}cn?$NgcubM)tZWcp)L;ImE5$Kx?E z^KpWh*&ZWqCr{z|fDMvl+>?RW^OL4WUt{mjL_+sGfZTBa*cJP{TTCr@KJNY3U+<5U z@%VnRB`$CDhQ7z+m;3FJ`5>3=a$V{hw)ey;4%1F4u`)Z3cGr<{KlHTIV-7{`+#e9! zHjw5kz1F%~=J(zIGCxNA(oxTL=R6XNGB$(3*2g@a`)6b?W*4#;!67JxV{my9s3=sB zL=SbMoG?BBk=bWAv!VyE%FDpil$@AK7aAZet@ka*CZ`LdhI?m?CGU;#9l)SH*9@Ikg7S zQ=>!va)Fvqy+q9+gWO1Q2I|O!CQLe0TMa6RsP2jg)Iy}G0xC@Yri^^m=ogOQ{|z=87#gzSZ`Y-YerI&pw^EE8PNE)pLJL9mY^?R9ix9sJ|oBS!j zzkkS5?MQ&PD~?*%ey#CnZ2^$d{+w}2bO^vQ{`LZ#F)Xr2Z41$r#jF~h$da0T;Rp`H z{(U{)j8goI`zg&?9Bx!=8}Yp3slAQ%leurpa*SKUKE?idenPMwz62g?xdgdzyVf^A z#k4fd-2c-*{o_CX_0RqJ&)&BG(*9tz=r0Jw0ZQL_dqBl&1cL|&b;k;7+bM5yyA=yu*KZVX706* z{dx3|9z9ZoH1k1NbrkXp0jlb`>$y)LD0#8r7s#IKdHg^;&LL?4ZSL9UgewQlY_V+- zNhfDWzS1{vcL|dx@k~Y=b2oz>rp0iz2bkw>nC$ z-gf1_JS=CnB`*(9suMA@-ddTxt~};6H=U=KYkPc~Y8z%Q{O;B`W>N>zBXM zF;|Gl0Yul)&uPgQ@kJ>Gia8UG`rvO(nV%aeIbTvr)ZH6O1-(1ycm6p}!%cvA&x*=(SbWVGX#9(Qh#WF2Gh`&?U{FXfXm5B~R zG1Nffq@=<4G9tW24dn%M$giaG7ZWFdB&xt<0bpn=hq3$=yiMEoOSYRm{(x_Hl{BFe z(<&OCwXG4BdfWHcGip-Z&rRYoM^`Sw6IOn_&2Qfm*v+%ny+0g}iMnV4+aqz}C^(L8 zO(o8U)BYK=o7WLj-9~*0h8EMG-|j#C6HVgh{l1yJJeQGkw#Z}f93~xDF2z{u_c7nb zv*T@B@yO*ko)*sf)Bo%L`(OWm|M{Q)&;O@?_dop~|F8dt{~<4L?|=DE|Mchh3sCP# zmleXw88GT66d{do+md#*=MMk~vHuf~wY7b<-0H;<^-Z^ZY|I8;+(+z(6V3Ge zgSRA0ocr=WWSH^9@Fv zElPZ;WAsK<6&n7kdCu7AezxE{xMK;<&_ADn+|1m_N_*hT;gHKb$!H)d*$S)0B; z^kH(^c+m&lu5XtagPlk8^!LrWDN%~Mzg$Sn>Ic*12h{+qZeu|u3mBt~`Ha!&+giFW zjcib%r~Hq`P$+W7jG;c}Ebxx8usF)HEHZXxpqT;LZQhrCZEda{+pU>O(**vq&Nah{{^2v)0&kVR^ce#E7gNfUQOKhoGOp2}n?UaTHD+z;Wg} z0!Ud`OY>ffc^lhzZzaFo%8z%3`|aa=4t{^TJ~UYGMo7=|w$FqGM2IJ8n?Tkj-hYH} zFh%3+H8HVN#MzZ@mCam`YPH+Djq=58K#^XZ#zKwr9!R6Tlew+WWezW8qNcAt3*+Fq zXGT{IIK1SsZ&n|Op&vi@;Q0Q|0=AfW%<;=7{`~W2{QMux?fS7@zO}c_-efIxE@R$v z6w5b_^Nd1Df*%j9*SvoC@%b%&zdUe5WJ>PO7oSZn&;xx zx3)(OqrJZ+qqsJ$@!aE)5m#kiX7Vai0%kUH;tDHPFIAVh$7Ap^>7z6VK~3hVHi$hx z8yD@*_;h_n(-BCmxyuz54NAK!tDh7X>ZHCACd_&*-O5~AAf)sp#W^lQYLREf4T~Sw>R;0a% zl@#4Mn%zgfZ#BK@H|{L_oSC=D3X1)Qg1cP>92f+HIgovJWmwNMa%ydoDSCLfKuqQl%Q%JaDyUs>W4f) z$;>%DU%Bo7dPW~Gq9oi^k1Auc@jwT&$rW>&Bt}^b#JYiJ zeeAN*4Mj99iG3;l7AI8|`;nvJ{_{We|J(ol|M~0V@jv{-zx(*p zKm3pX?*GX5OLaA+fLm=#n|G@T>+rhZk-eAoBky+nz~zJWYM1LZ#|5)0uME%22PVmq zw%*(4@-zVbkVBns} zaktDLN!=1X{QJXq=#kIQ`QuUa00nIboA=8TL+5tip2)`CuU7*l+_I&P%AC__aW&Zf({yptc`rf+s zhjXUkem^401~seh-`4k5H`{RWD36B{OrX*hBC=Gvjm!ZM#hU^1vzB%?qb9VnX#K;x zpYYYp- U7w|GUPoEclhq1o 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 z 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/archive_icon@2x.png b/Signal/Images/archive_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9354084c0cea0f7202e8eb67315753942a107ce8 GIT binary patch literal 505 zcmV;PIq%kLw(Oui8;&jTFFR z7I`De%V*59pDr*yj*;IQp>qNrwG{e`8~6&nUjXnvJUM}vRCWErO_XZWO}1pA;ZeFE z)`Mqq4@cI|k36`hhyR=3h&=5BqX7P`_99QjTfid`F#I*Vk+p~Ngxi|HCPkH}2!Zda zJV{i@KdUlj=h71Z>$}F*_F%f$u7<%)n{79q<=lgFtQ$RH%5s+XI#biF&!=DOCV**k v)=2{_yW6d7?-5tHu`Of&EpogY{}EsSKBRxHi-P(200000NkvXXu0mjfuxsGL literal 0 HcmV?d00001 diff --git a/Signal/Images/backspace.png b/Signal/Images/backspace.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7ac6315f5b1a1f170c3767c39742e8ceb276b5 GIT binary patch literal 3193 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/backspace@2x.png b/Signal/Images/backspace@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..bef69369d26cf1e06713341a61cabc4aa7903c6e GIT binary patch literal 713 zcmV;)0yh1LP)tXJAx+C{FHy*o);Wr2u~oF(A$l_pblU?&nTLKDJNgoLfU5yW0%%qyP9#rx%1$F3vn=+n`K=Y@SE4vaD;7jpn^8vK>6 zeN=f{J-q!fiCm?^+mlV;wl3dB4%Vtw^N?4(A0H!d}!_k?obE9{~mcAH`Ft+Bfp+00000NkvXXu0mjfcDOGC literal 0 HcmV?d00001 diff --git a/Signal/Images/checkbox_checkmark.png b/Signal/Images/checkbox_checkmark.png new file mode 100644 index 0000000000000000000000000000000000000000..9bef98230c1ba54b7adeafffb76de77d93ef4ed4 GIT binary patch literal 3512 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/checkbox_empty.png b/Signal/Images/checkbox_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..25f7a1b25d2ef4c3922bd454905722f412c08f0f GIT binary patch literal 2909 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 Hu0mjf{( zJaZG%Q-e|yQz{EjrrH1%ef4y445_&F=Eg?eLk0q@0qT(rlP0k?89BKvU=36eo-iro z09#_y5{H1zjLH!Qm~tFAW&a;>U9wroqceN&)%;n0`(P-HA{r1~3BGn@3CXt9j%n+Z^*RW$JRbm bKERNf<@Vvk_iIAH0ATQR^>bP0l+XkK#gd7S literal 0 HcmV?d00001 diff --git a/Signal/Images/contact_default_feed.png b/Signal/Images/contact_default_feed.png new file mode 100644 index 0000000000000000000000000000000000000000..6c55926eb5c02e0d5ae0c0afc662f0374916c87c GIT binary patch literal 18662 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 Pvl1Nbo$c=1_$B=pmOcv9 literal 0 HcmV?d00001 diff --git a/Signal/Images/dismiss_notification_icon.png b/Signal/Images/dismiss_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..927932310ea04a49250d7a7d9dfbfdb134faeae3 GIT binary patch literal 3287 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 VA_eQQss#W5002ovPDHLkV1fl{BGmu@ literal 0 HcmV?d00001 diff --git a/Signal/Images/dismiss_notification_icon@2x.png b/Signal/Images/dismiss_notification_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..44a041d1ea3a30eb76ef359b2967c5fcaf34cf0d GIT binary patch literal 718 zcmV;<0x|uGP)OvAT$2ps= z&gHYd0IF%WunIRRg#3Tg|>QhO@h9`Kz7X1?52DiEta`yz2}vscg;X^%Fv!l9ciGg)bSR{%)zMkD zlOq8msmTBok^V+mlY6gsf#3UK!yr9vOw6g*GA0Z^A3NL1J`89+icfNH1srm%*P|DH@FVIT|`wr1N2X$-0h z8y7MbXt!kys7O@b*dEI@1Bi1?VWRZE`pZG%MMP){%oxt6S8v<(u`9km-6 zz&e&JvqY-=a!o!KDO(r%LZ3vPTk>8~jq*g6gVc<=Pe#F_FJk6Y_5OP3kkSJAqaQYG zGY^8*$VGqIl5!O>E;{Vrlt_>&EZUeBBmDPzN9QKmVcpBxD^u@Gi7P<3f9}$~ligC8 zPI8efQDw(O6><|*8c)>AhQ4@xlS;}9u^#~j0My@>60ukzJ^%m!07*qoM6N<$g2AXn AQUCw| literal 0 HcmV?d00001 diff --git a/Signal/Images/drop_down_arrow_icon.png b/Signal/Images/drop_down_arrow_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4552d778e137f01e71fcd2185bd9d9b8dd046508 GIT binary patch literal 2908 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 WbPy+D*=7X*0000%8P8zE<+AJ2N)U> W)P$dk3h@Bl!{F)a=d#Wzp$P!)l1nK7 literal 0 HcmV?d00001 diff --git a/Signal/Images/favourite_false_icon.png b/Signal/Images/favourite_false_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e687830378955d3c07b5d16869443c595b9fdc0 GIT binary patch literal 3168 zcmV-m44?CfP)X1^@s6FWx?200009a7bBm000XU z000XU0RWnu7ytkYPiaF#P*7-ZbZ>KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0004tNklBaT6d{!a ztSo~dq%e>-5HL1EOn)K-V&@wO0p-+y*gOa8v75O$H$ND5=3!>PJ2TIp9cq96B2z2z zT+LZDGyv#FJjZet4QE28FdFUBX@YK6^lJy*DfAzl7U(=~6vnNv)egFgMq%6tTkW9x zCANEYgT~{idUZWKFsUYZBm=Zi5o<}%Eu6~$Rb`JP=yrwub+mr-;fbII z=6h_NIG(4`7VtBX;gg_NM08iX5nmex+eXC8r=FVC$^jLt=UT+oLCL&*%-~h3bd>!C zjYSGM3gG)bonanZI2)y-liE=;676CJ1h%n+5AlwF^mhRC6+zh?mL6gq~ytaIo{|zWDA8qtTUF zAU(nR$7hI#Bfp(I+~1Xw^MQk#mSPLJrP7nR5R}xs9v*Ux+gIh|TS@G|0d7^B4;i^c z^RvMSnrnKk|AO%OQL9_kLhc)!hZ4iS-U><$`}!xS0XK2%zyofg*ntP!M6m-8xQSv1 zo^TVx4m{x|mIi?*+?0*+J5%e~HV*pK@*;}p= zJh4}HR#P7yaN;;-(Oi+Pm{uODc#Be{SK$Yr5X+5nIYUuWG3!@saguGDPAENg7Z1^r zeiYI)XLJM;P~)+?2znG0lsH9B7&m1`Kf6E=aqBb(^bogB*aePoQ!Cb38aopY zwcP}v+A89_7kkN-CqK zNKbuq4wunZu>*^^m0aVmbO*z8Gv31}ZLM~JCENrv4ak{9k`mzN0yAoiZ0@ovYCXSv=UoliJFMVti~*D54DLy3aQ#XF^i(g fO3eDd_AS5wMa{%5@&~jh00000NkvXXu0mjfU1>Qg literal 0 HcmV?d00001 diff --git a/Signal/Images/favourite_true_icon.png b/Signal/Images/favourite_true_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e410f5bb71f835a88c95758f7ddaa473ae7924d GIT binary patch literal 3188 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 Ef>?6LXaE2J literal 0 HcmV?d00001 diff --git a/Signal/Images/home_icon.png b/Signal/Images/home_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a349f3afcd0bb26db9875ebe4d5b43c6885cca7d GIT binary patch literal 5332 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

|{m?dcB@0EiL`m z!i5W6LqbB7XV0E(HyVvS5`+kRX>7qr8Vnrg+h<6Pg92_`G zrBbQa>ve^Ng)57SibO)8a6Cm(%(ZLRvR^9dp0eikV+Dmm@k&gRBsu;+>Y8JSkB{G; zo}Rwm&d%l#K*^{ zhX5Q148s6T(_pn)SudKVVX$X1Ha0dOm&+Fb0097x0pJM$7>2BVmn~Zc03fonvvcm9 z&)Lw~*$GuuRZv-3nPWDa?{GLA8>=3dy+2ca?O30?{?f3M3 zy}tMS!SlWE^E{u=dtH>8iYze!H30+yA(oeu)&S${e>WZu_;mS|tOW*q7dd@*2!xRQ z-;DuD|40Ra5UAQpNvWyXID0s|+c>*0$V*8vxVSl6**aK4Al`GCn$}vHTNEN^OUIIm zk*N2I&KeK!7&IhfP_P7M7DjxiLL_7E>;v`A`!X`vH2pb|SaETvw-3}=iJ}Q+aMu{~ z;zIKyqX(~6{POLmTF!ot+}2KuZhSt;;1Q+BajWy7h)N{s?*9z!`_{X%#x5RA z0C#~v@oHW(y5Bs;fSd;k3qNP-z-@zIcux@GK{}K(Iyh+~?nt*K(~K}sZ!kJM68IJI zP%wzNZ=7TyMEoTND)R%gCL{+NVmf4Dz6w!egP1-I`aJ`X-h#;Kmkzvmu*nSYx z{>P8KA#c(ku$PCr!hfGvKsQ*xPNh`~H^K#Ef(`N5Tyb@E+366Y@|4eEyk?kY>Ei5N zK4}kM1S3g*9t}evISCKIYL9NbM@cG1M|tDwNKDwa+i~ug%*|JC*U@Dz;t;2;RtvTHzmw$UBll!i5c6NPjZARgnq@mfM zZosW|mq~~2jl)Ht@b%fr&$eIi;1|Zhve@T8yM~XI3TV-Z_;1V?e#gsR*Aw1dKbm0r zCU4%T#{yebCv%OHPJes!qJSw%GVxi@lkvOPzm{3Autb-6AYbf6G5iwPij6I>P8FX* z^*lO$ok1Xn&CZ=a5O`Q9o6r@s*WH2Gjcm>n2+B%6(G>!DB@JiS8mtlP!-GJibAnmE zNYEU2Qn7a6(01Ni=p?*0<$WUw|JEf5l_YqLqIENU_Blin5kB(e5sN9u5AjFH4t1OG zBo`9)F6{;q5f{=+GrX)$L`yI}wnRT3xf$bJB!+pk-s3nt%CxX^#!fkGvKTnSKqO3^ zNil&(j!Pq2i$PoN(V>VNra*+z%ccaOe#ncE>+lv?uJ^&!>LNdgYNZ;B`KaH#%p?5% z+Jh@So+2mXyW3Z4k+;0L8{f-pNax~3rTREKHmPvAzd3NS_jXHG@w4J5SqxP%eI_dF zrT(Jx8LK(!!~$LAg-sSL(}B;6?Ip=zMkgVsr=+JcBUj8o3rF6c!{@`tdZXU;lqsoL z@hfxF{flo@<}bJ+^yF9{Pm#rwb3pwfrM~gO$)coQJZ*f3DPQ$TYn*KS<+zi^<1N;b z45S?O`{U%J3ZD22RYL3v*C1hoyN&6#cGrbpBL z#bi2_)bG0ELtC<&she2GUPQ#<4?_DQtq~71sBo#QA9_($eo!iWI1%%(Pm7a}Yv;p8 z8fludwjN6zi~oa!L4st3WaeZV77neF(!$cEQbO$`?IErBl2@7m6)&~9HCIdUf25RY zmu9N%Xwqm}mk1R{lv)=%Yl(dd)W3OJA+S1EFXz{Sdo0ZX?xGem? zV4iQxRU@7+9KE*^!(IL0dj8Qv1%CrklzJ2xy#zaW z_UjovUhPgr`yaB}UZJv-oQsxxS~o6f12}H9ZX7R*PGpJn zaB7I)IP*lKRKh(xuFk@bIQr)Qs!JJni%S)lC7Y$Lyh&&r6V9J}8T*p-t!=N-Dty(k z*ReOB8HbsXSy6dgc|To4xv}tBp^%c0Qs2n%NZ!a)8c&7@*Yt~xjE#)xjOBV$18)P? zy5hP9OZn=`t@i~X%@ z3LYn}#w)(#Cz%;F8C`qD^OgI&`?mWL3)#cS>}Of}-0t7I`=x%WuaAilW+!vnFMkeH zl~N71uHAUdVZo80TR#1zzQC*W%dsTE*m~ZLDlD^3>Uq^j^{g7(6bCyZi zvQ*IHyVrNVcVmzu)DsMAtf*j#U_;ESb64Se#3w9`zVkDv#ILf|UER z^rdu)^iqa3m#RP|n*tAuXeqm$@El(gvPrC$-AaFWQZG|a-a(O8fSsj+*U6!6`Figt z;#cM`!c{-Y4;=l*E-ostMy59KaY00}5C1Rz;48bglbWaAy7b1eK zT%m~x?_4EI8I_ajthcRyTX_zBwhXJj?Qx2!=b%f*9k8!?_xYYa8$q39;ii@WVwwfF z+S*drs(mDCP#~o&>-osuaLwTSJHC$x*>`(nU1TeXbp_#yx@?d1ez$(M6Q~w;^_eGF zeQ@u2xDL)|;fCls7RO1|vOV3@iojN8UuFB?SDk|)owXSDJA~Q<>tO*q3)6eXs(Kvy z%e7{g;h8j|52B(gVDprz4}xY83*PHA5zd3{X$$YamXv*Ke2ummx7zx>#Fk}9l2V%b zJC($Vcy{h9@v_d>y^~X!d5+W(#C&sTom69fyKqB`WzoRsh>)1pC#_?xOD*30@x@xR z>2s&CeHy=(Q{?XCykX1Pjp#tzyf%im@}$+I<(2$Z&6!w5RO!}v%9W#Wxr(FfULV#t z)(!4slDg@}ACr&9MYJ^Hr=IV~lJwEH-DVzmu@UnUYsY@<{q@*I+GlO+41f8Ij#zNc z^R3_K72FAW&qvmeIP4_T57S>}Xr^Bonj1E$4vUUWZ7=y=%g)o2enblE`zRb~4cN|Y zg-j$_AEMu8CT9i&vR^Ei&ga$nZCH#h+mSjSG(kO9#%1neHiTM)MvV>|JPxbp*U5Do zbki#ZO&qs9wuNt#CRF>HKQ^aVG#c4A?)$dZ`dOWA(PZ(BE*`h~{H3^?n#>XqS!_M> zbw2AkJ6Wb!40L;XGdk2f>-BWMyhXLeH>Pw^NBn_wCzEMA8WUZhPpiH#R=N{)mh>-;#c-aOLgo%f_w z-=XtGDqOJ|p-3U4{^`@zs*=fEPgKxm#Qg6Y@);881o}3U?6bCmX9@*m3(4h=`xj+h zDBm@^fg81u+>G+)#crMCFf=eIy_JCx+(WHdBxY<(F~D^c?f%V_anyWJgQ8U zzVFDzH+jXrwP+TK7!BSGOM$qUkeG2|Ki=x6pb`Ey-(YUy{Kn_|GDLC2e9JL0qqc{{BNlnav zm-hruvU1Gktx8c%eo0A5S!3gcnxbNiO#T>ZXdM%Kw#AYyS>c@$bMPwvWBEi`y_C#n zFaiOh?h4EfZFLxTBD{*WFZi4F8_V! zgh8H6O8vd%#%LzTCykQXw6rwR)RYvQi7jMgo*s{w<^$q{k)#nTdoh=v9qz`)#5sB zUMv~)z0Azar%KF8UVU+ons;_~g5mNVuKqjx6PD%bC0aRZh@95eMSCQ*s;_X?X#3sG zfpy;4=GNHQSoBp&G)A6AiFkp=3SIn$Nx6>w&RD)I{q&ZH_{4Dv`W`r(bAQo4-ol%s znL^Fm+uKH~tE-PzT+XH0Q&$f!gtemImwy-#^!~%ZpE;`A)Z84ubK>j5PfO3OnvIj- z{$831qeP3K!nm2@Lwfq<&xJ!S!RT}S zT3cJ&x953qC?d{~`v?yAzD~b*3v24=Y%j+zLH=}%H&$H4+WqtgO z4U!;D3CGIHiXlyD6rMDoAGd0r2wzVeSaT@RN^~;2QU}GSQ=(+MG!{zRWjy z2m+Xlgthh4rG*9m{)4jvb^s_W$<$?O1DB`u?KjoaJ0c!GJ1|mp%GhGC&>KhZ244R> z19cbXEiB55ME>YI@;7&N9m^hCXGk3?KYv%-GB9L71eb4OqliH}@cOmio>bm-Iq-JJ zJrWu`90w>uMMoFj39jtB>y5LRJVbeqfAYxdTm%7Je!KP60b@3LV|>daN26rW&3(&* zx@(1#h=e3-*}>BYv=9^9Ju;Cm^C`n4A~>Y; z)e*Q4m?GxbQwg_scD&~IJf~kaH2s&xO4OZGRds*AaU&}qAj#$k^3>`Hb!=BzW!q)u z*47rOTO+MjQ!ocbOy_KkbvqX1r)P6K@J!0>M*MM70RbYrbG2m=s|}RwuUgLYUHFA- zDpCVQ?$Q|WZi2CJZ!dc|0{<>gX}mO!kxRtZ)YRnU;$nOoAK#uN2gjV;EHpaRtpb=$ zdiys%NG?sO(7{7dN(yUtvH7fF(aCw*u68F{c=}$XLHShR#gNHTyZv-UbxAl;KUnU% z8UkhnxQp5qfrt5~dnKC$oEiruLv{S#<|d?Ua!w%;uIVYxNff%j@3gtKbrH4b#9?pA zk&O;rJ~(*v@L{7KVH^uxeD~(&W)C&&8e--57-k(P^iqhOQWV;gw*Zg>usyS{HF(_TT2gC-WgMD@2{_Zk z)ARISpGE2aM7+du!fb{{_1p3wb`fw zmqorPC`G1RryDR<_9sMiqucu6P6o~=09Hl+ZX^7k8_H`7Y)J#V`3M3^Y>5WrEhyC|j3e{l5emb|je3$H!;Lve=ui#nNfY+3=sBu`+}!P3im0jCaoX*)#4= z!OXbX;&44cN<7!NfL};rdJZED?qLEcRQ?izV!+!x@$Gf0ubi81VPT+-?X}^}c)oNQ zm4}$w^JG(=SVBv^l2nIva}Fo`hcUjeKs4zK)(Bo46ClQ=FvOfUgAH5}Iu;`DCe5vA z`^qRzf$C|{4$`ho9FWXqkWa);)Viodt@6Kv+v>9oS6<|zJp_r1R5E?JU<4QTt~YwD zIKt@cf2=$qjP}}HXsjue=d6yF3L@)sb>wM7!Q}xR%+trdL{I<10L>$eF8cNN9O$T3 zmH-b&fnh5(*h%@rcZG$8SO5>~P)bTlEkb;Jx;6FHYKV?+-@a9eGq~iATSD^3@M2?Q zukb>?mp_Dp!%3-ma;#$ZR04Ww@Fd*L)C@`832^gyz%p2%MhSM9WN^jv-SXevIfy}x zA%hseS?Ps$rZuy3cFJ;7w9rfy+~u2nFAlNG&3xw{zb=7>la=jT1!X978OGs{QH~6A z>udQug)*hWc~wcd;S@r-903X{pr&BZVE?B~C=bfAa(bs)k5!bm@A<31NxRygM;CNe z2IZnYqPjfrj1fsd%`tiXU?0Q6!nopA8|<)t{q;r_+E)6{)h6bR*-&Mpz1|esD)Jm8 z0THXGM@23()@~(UJ-chG4hypjR4>rLHz=q0$MzzAr~d8ZQDBmXmzNj4vz=Y3pmx&G zx<(lnW<54M8!ZI@S|VAdL@Pw4NNUg1uUVtyb7X3=wTms1I%&~q`8aAJ7Q%$rRr+z3 z4Few=6B^TlS?gjqGBV?oqEaVbdn>q9=)Y_yczIvr;mTuqt6-KkOv$wSw>{(8J z;mcHsu(r0AfR=76td&520(s7dM2a^Kh!Sy@5zm(?ZrZk zlC;@beS}xWt${-eD4K}wF>r!|BzVzzfDLQ4GME#dP8LjTxv-~({&xX|pa5OzAz+8P z=3f^L5_vO5rho`~^^A`JFC=G(=eiV&EWVEsyvIEWJuwDCLc(?$I=VKsJ#2NHHvoOZ z&E|&g{S!~LeG+Ap2E$hFJ-ALwER00hm{mX))G=hQF~jtyzw5XySqCy_N6Mg+Km9#PVS;))nWVXcY#2# zrkHY`A08hcTLb~pnYs}u(69xD1P@ch_1CwKo3mx~5A$LJrUX=5TN^oe*)NR5`s}ye zU0q$EJIgLVZCD;*0w;nKE>~;Y?GKtWA`WPc-%Qz4V2XJv{%4k%-<{881+54G0K$ z3XI)avo`Q_UUzzxm9Xy<;U6W%)`cHD`H z3Ho01Dz5axR^ALpWfV}ffU_*2hle(BIXGEIHLH?}N}CW5&&kZ%8vI{D2)Ji~o(CGJ zdX_aq4$jUxaZu!w6(p0ZhH$0z0yPVK)O&@3Vd!UC`UdhD+CGe-YEDtnz-I|^M$4Oj z8;D2tlD)zj7WX~DcXG7+>7pODIw~c`;*+uEvej3g6-jAbCOokf|6KI3zJdjt=b#v18xwa0i&y`Q_qya(%r&-&an=5hP+3Y+| z)LG4Xy{+3W$iv0;S0yOuPT$9D-QZ)8KJpVd?h-A`6w8|)bM@#R^XKJhcp?5u>+8zM z1UYO9N=in+)Kabz@^3u#F0;tYbOU7hwYO8{{4GN-@t$%TgYplshjhk!KY|_MT&^7c&|W8 zFM4eP3N!TP=0*%?S#5!k7WI+TP~wac9KfJg!MJqZc0i3?slkl(l>~Tsarm4T@B+_Q zA~->V*h_ompZ^qttkOt6;dH*qupV-1C>R!8P;sn>!^z_;8%#5D1Kw3<- zqpc>0S2Rk12C}fQSiO}6cz0aqIRAFO-Wivu8?^!4I33urma`?l18JuFcLj>6StGAs zFyM8ia2isD2hZ=}G%wz+>9!pvC#e)ota}J{zJ7rboyRFA_7C{K&(B|3_@v^ak7%Y& zqn&J@Co3@EVag?b!@&$b0{x^~s#RJM9*$#F@dHpZKrS$ExnRsjDX_g=szfUsgfZq} zk`iPxRokL6B=)J(Dx2jxJCE5@?azSgG(swe4!lmha<$xe)aJU{dm+u_+DfL`Fx1wk z<{1r>34WRHdUAYxT|a4e60m1)VxJnEuv`j22gFdz2lO8^GMaBU#O^Ml0BlbR`7P+oSqcx$TJ^?F$*RJ>G)w z1jrO61Wb@we*8I5CF>382IV**4C1+J;((XEg_m?#lHq{5!5(#Qc?b?4`GeOWI5hw1 zhK<1=SIJs0t^Ol&m4AS9*a`gCiihC*!*GoBLJSEgROUWNyub?rMZgLpFplpr*Y(MY zP@vdiY5VBy6G}YDsg%*ZvdQnZ*3WUeJDbmwrbIuatxzzo&}<2MX(*MWy{(~vUkEt* z=C5CmJFT9IdO#U6Rug;rJ2Zg!+MKOSD{v(5arr ziTUb>9S=Zi<*bdBwyS-i!6Sb=Gcz;RjFD>EJ{f^H3&OB24uQDl^=@^R!r64nY8@|3-oxc{Y<=%VhK1eJGcX7Nsm!N;@d^WUlQ>NB7Q}TmC0eC+ z#B@CRE~*nqHQ1!*?o8G6zkm#Q;_vhSkx>xbyFgHVGv|uzTYV-0Z2-XUU3L&}Mz!zR zd`6Cq0V-(Lp>0oN4QQVH{Cq$oKZnz}RllwNT_z?a)xBO>R*M|&U26o+Eq`e6a4i@L9 zJw=aQozuOP1v5Z=6boCMNBQK-N7Xkrk3pqyT|4U}6Ukj_j%K%L|hYL{3cEXm_B;Iii2nr!=24(#1=GvW)%A zTWZCfWnNZq9=L?vc!jr*>81;AkeF9XTr6WiF#EiPJyMnLy=S(=}?do5pd~FX({P$5RjG@5%|#{EPV6- z?m07e&Y8Jq-g)MI;>JJMQ6<2o#RUL>KwV8qADkWjcR;Yfv*%Vi0-SI>)l7W=0FUf{ z2O5y|g&F{GbzR}`=g(a{d_8!;r_(lffjs- zER1_Mdbb%=_+p{sdT-*PX;E&!>KxfHh20Io|D-0YCmM!d4r3(w6*<&DxVgip5RS{{ z2@pdX9H2fZRy5!yR7QrIr5pP@faX7k2LZaZv%3Z8V;_nBz%nh+!lKZ+eN!YfAz|cz zLf{8j380{Y7KZ%HZUE$A0ybmzcH6*nUclzr+r2p;40)3oga(*@gi@g8r2x!ij&VwW zi!@L*W%N!NFcARAoizI;fuFp9fV#1h8t}CR=$<6Ts|O(XfPh|HG$()=1lSC-vibv2 znE<)UnX$|%cMb7A7l>45y-X{cq;j}9gx3q(*qD!zV^W=pn_SEm-8M^suQwo*Mkri_ z=-2r;0OX}mfM0u#@}DHEnVb~+&`fB}`=<-*k;Tq#`(byg(o+EdHhn{99(ed0Xv3tj z!rUJUo*rYmSmEWpzJBjiPo&TUW={V;ReQR9#Y=U(e> zW7NyrP?`Jd%U|EO*usS@!&NbFe)W!DXcf~>rQ$@{t?nhM-nZaA-ZRXx^sC#onsAYC z>ydhWP|8X;7b<3nhoy23Je_@X*xKN^!;sq$1-`n;p#`PzmRZ_kTxmWdHu3G=x(0x= zc8{JP91x5!m&na2zsD1Klxp5nAk0ZU)e8Wul-T$XqYd&y5CBlh3+MW(NO#dg&C`uV z-}7X(2k+iSEDFZf-wPv#;W~uTd)shUMZh>>CcZLo+3^2RU=ZonbBRgwB;@Ng{6;A2 zN%YSalGDS{5srhYI1C}Pg)YaT*}XGi{Q#lLjJ|>Ps9}=6XJa0TBiCcmOc7NR)PILy zHdJFcll4ZIjI~f{OOYN1gd*-^I#dNehS%%K{=jd7x0Z?1MyV9wO*{AsW+hSPWlwuI z(#j@?V(N7Ez;RO&eKucu|in z`H_d2dpLRrxzo2JyhFDGJu}Be+9`My-56|f=#RQ-(CiZLitJ)Jeil+REX>khE6Yca zh$flAs`H!mYxQEw$V6UeNlrvks%sWk7o^Y8x=^`XyVm~pZ6jGrXVl44PD_zhS4XB0#G zd)bQed`Y;lhOlvi{`p5IdF<`aUN>KSb#V4MN;A$w->s9MaBgwZm56Ay3OZW6sQ8re z$)r-PQm<0=uv4t&s1#Y!soZXQ7fz@+r18w+nW}Trt8JWU)l|&Vy1lK`}ZMJRqLHEHzb}V)%yQcOZ?c*$c z?bZ^`5@{`It)YqWiGqoROwnvv!9}6{?EUP;?2Q&1Gk-Il=CbB*j_UP0W+kS_%^k0z z%_L2WO$^N}8&s;1)lV1NtJ_OSN?Uc(bSiTeocq3>Hjgxyx6pju{fV_w)9l_HV(V;2 z<=iq*(|4TKnHJI(l8BPUCWu`ktYk1_2=!_7CYLgjTF;vNax5P*aPjGY-`Qk}cLnaa7O6wMhU zK3p-}9R2PlFc{kPL0)Rb@v465NHHO=Rgpq5DWW=Jx2GEFGW<8W8hX9|c`{|vl`#5w z=lR! zMRX};IUgyH)F%b4I30zqAk&c{p?tTiHY)|q zLHqWT8*W4%CvC*Oo3qM~@AsuUq$e%TzWJWjuk4Z;e>2Xim9lpKjSzR@yWG|&VWGLyz)2x1O% zB;+I|x8%K12lu7$DTgWc9IA(chp`$eO8(HB`NPG@na?x%8D|-3UGBlfs6X9@G_@;( zy@QP3NBo5atuBo2j(Yr7{TNOPH;8n1f|=iPJi6T0T^dZ6-lfr`r9EO|d_KRo7v^07 z^Cq*sn!Yvw1U?0Tut)&7eFWzN0C+6`0Do)%K!y$6Il{>(4=LbxWZ=D$Wqza5=Ef@e<^g=14c?|zk<9xK!%YB zJsjYnY*jaMUMPA6}KZo?#5c6f=H8nML zkq9xoT&jO!9eg^VH346bc!JlgNB*Q7@eW+V5H|TSBl)19qy#ILh$$!jbj*;8V#ixj ziG`%!uFi=aV%p?p=uLtSR2tU~W(qr#%2H7b3=A+s&^JA)!HX#jfoI-;&)&GY98UaZ ziYjAIv1SJ8e#+M+Eb#4sO`XZ}as-7=>A5vv^$%WIMbrDL+NfRT3R{s_fHsl6)#BB1 zyPpRPvef8ma7HHd_92*F!ZYl2;Vvkh2xO2o#8iJde zE?GzRFxy9sBc;VJ*&J@qc0Hw#361UzO9{bpOoEZ2V;Ei%c^nn9P7NqB1$Q$?!K4!^ zJ`0nP&DDV4AoxVG7s)DYr0$Lp#3g3XS5_F{g%;JzO|TiUJR%&-KZM z?u((rg=)5z9Je1^_Q`dS+ra42r$>pm0QF8 z71JU10Lpd z<&g-Nt7s~#&ne>k6sh#SV3P#s@nz^9IAvyS&sjtpli z3JR}=`udU|#!gP`67+I`LOkUn-8iS;Ev(l{m=J z0ceP1fApK=kX;LG(6nxQ17F4X=mn>vh# za?YM->E(a7!Q>Pa?luie;t(hVJ+M>Gb-lMp@Xy#M-bkz3*tJ)&GcHkP)mA*|8c<@$ z@1Q#gdN3D@j=Wq=GjwF&<>V|+5KpG&_m^SZo5;?XaB5fteu@YPgefq$j;jl`b;6=y zdeDrq;_4dX3?(3|@0~(M+u+(s)c?O-S}cj21gwPmiR=qV-HT zEGK^c&N?+Q*$=4dy-etO39M#PCs@^D$M$V%u%)W!8pKm!Dk`v%=I7;g3rI=1Q8O?Q zV3^3m&rh&_VNKHqaY_LH9oBX&$lm|gS39!n4SwXzgOd*B98Ow4Mx99l*jOei;|U$X z+uPg!1$HipW%IU|m%9!fk=hAnPt>NenF@)BG#XaU!>Wz!nNU-0%Bq@AfJEToNixY6VeZ(J;z|?L&}??e14B-3TI+qU>s8Mm^^=) zHj51SP%H(4 zm6e^n=_@4-0*|U+IeNA0-D<#RC4h9Pr3-t~9kIT?uF|!Uq`(}e#&#A9WhKY8b#TB$ zp-{-q&dx-#eIV@HU4);EtO~I=NTS9zix`YD@aaB5v+`-W@J=>!y}6u8qVeS zr*bz;4GopYV2P8;(XFMWv2zOxSBrIZg82xpXbZlsL-kTb|ILj=Ol<7toA#N-#n{Qo z$w$CHZBp)cPPGb9@bBM0qH!mmq2IrMSEVMTq>wZ=HEn|3```l7-@9!j&m8tjNzgk*FuxI8Vwwx*^wHEDOKSH!IUXMwDL1Qk7`bZo z*@v;Du*s5yb}_wbp>t|jdfBp)WcfY#wlFo7RD2G_-n$ip_QSk-q3z)z^!mU51R{ED zW=TmBRY(Y91%-srTUHXmzfueQb4BS4T?lwRV^G5x<981&F2Rr(I9Yn=% zwfg$u{#G0;D|b31HuFFAjf>l0`DcgdLZtD`#E=W?&cN5$TlkQYFH!wE@=cA6{k;Sa z=gP{;u{D>HiHS~Bs`wAL%Pohs-&@|TpvOz4s1>!|UYn?w z6!FUmgT(p`d?~D-UpeSts$O`d*Zn3|3XX+Uy2eS;D@RHU((F zZcVhHHxBxrn05w=0^JTm`E_+Erl9yntsKRy9DO)=>mssfof8uiqwe?3X@XXbEww1o zt8VEf1QQ(;*jo@z_K+_9a!df9fIyL%m|0$)35i6`u||#U4vx~zyyBq1eFCZmt80O; z9SkN0lob|s_V&VnMU0Qv`iu0x0wtE*n${ui7uVOqXUUF^jubMC{0Acd5Uv1ZYB{;Q zcwyhj^nSqmeY0B8`t$^t%I1)`#_X)@xP(=(2s}bhyBuY7W>C#$F)u~kli6^pjm%TP zktl&jSVRlWBv*zSLPo~Nm8`57Z-T$eY?7wN(Q_xNG)QoBbGxpsNiQW=RaJfV5`XqF z88Z*YL9SoU!q9ygDGOpp#>HJnTJJK$QgRodS~!Ccp7-Y76Z%qhMual}%A&>j*gC%iZ;s#K*$mO$o!n5ll!*s#WKJ`u-qm z#ZK@wbcj=8aq;ko-8dNnu_q)TB((CCpig8|k3sv@A5T_0>QGNwyV#!2rvCdfkQh^N zch#Qq{gYOU4>L1*_Ao!rhopcUykZ5M`p%Rl#6cV7O4Ri9;2Wi}eyypmuA}Akfy@-8oiHlak8cmz+OqoP-TLeNmN;Gs(TiA!B$69O z+^vAii#EnVkpo5Li-cG2?Be3}Hj(`Oe`niC@^bU2ql`C4%a_*_+(!3Zu>3Orvyo@f z2GHVf-);hG7tM2tVRE-SWGb|^)z!$4f=qODa5mELq3!UEli0CcH!Rr=zzf3!WZ8GW z_BBHq8X8cI!6e}oVE@0FzBu^d?h_G6UYX>z%)NSjqn2kV9N;q#4vV<8pz+nB-?I z1j{F`vFdyWg1vdRJNDYDwxy}1rA3c}T*$Ddv<{T-$Nx6_B6@p!MFTr?W)FP7Ej9y+ z#Go`pZ=BSs#f{nlE8FT0aVmw7Xc&lT|47=i7e3zJq=vbj6bjW#FXbp9m=BT|)K8C1ZHG1!kZa`)Ze0^KGRB9KE*Lgov$16?G%%IgSzW4ZkTe7f!j?zkX zU?olZSE!m~26}GG-p&pJOf>ao%)|;@=?{}A3J9iPP+5;9A!?2tieKdGrj~k-Y=VWIfokIdj_+Wl<)9mPG=!o8#Z$4F{;sy3f7U1tbU2tTe&pig zqokyuSo01y;fWb>SbiAe?-IC5RARsSQ7prFf<-gqLJkTG91izvwGcGEGGyb(4o>}z z={bDhD@KHm-@Shhr6=4NHe!RI>z5;950^N`)gegJD3!uf5Aj-AP?BR#x*p}gKub$U zM<>o}@lAjx{stLzzZuI!i;IV6Ty5TR{Pz6(911EtIj)|*zTKbWV=*BONMas%dl8IT zV8$}xp#-()VQ+7b;mMOHOwguj+a|LvHy4-b-Mu{{Lj;1l)q>C4#|PXn_$H9Twr$^* z8&!%bvyfPH22yN-F z2;127Y|N1fINJalY2K-UVQge1lOVb|kdRTPECbxW&d$!dudS^m3^bZ>dg^QwJ$Vy| oI~!G$!p^3xM(YZ5!hVnjrtzhz0-Vfy!0kPtuB@X}1G9D($gpnBrhDMMs$$_Dzq+103 z^ZUQ|*1PMRd)D6foPE~$?%vkgiqXz;% z4U0ekAbR{?#{lv_(EtFUp1ZQLzP_7pkZ+)yuOEx1vNDTbfUm2&mkR)dEEXDpjE#1w zWUo-?O4^B0AGCc9pWw3?DkVozW^nSd5|C>pvKB8qG3b7zs)|kj^GhODT3S@f69azY zB*J;TP1cgM*wVzLq1!cZspl->>i6h<^PJpv?O7pY9Ip$X_@joX!Sg8MDkY{ztFZ%p z{cD?o3ekidegHW>#F;hljvWKIj+Bw%W9!1}05C$PiSU80mjzwI3<(cpdrEoM7*TIA zx`HxZXyZpw0t#ViO67op8b(xME~gRj1skv*c5>VR^aTKWt~bA@fvCdkJTL}e^?~&X z#+M9$<*`em3g9LU)Q+1bsRHK00Hv#TpCm9Z00?WExoQC4U_jRxIZ+dUPYehfB*yUo z*kHi^Cp&ux@HP*iR68}3IpM1(-{wUlmDeN#=a5v5w!#3MblkY#8n5wF0D!fi z$jN(tAqZWRG;WmleaX{9Y&Tn?FR!kWU7N@hnt?C7i)Lqj|6!wA{2soru(i24uhpky zoU9Zx`~wecXhGavCa|w%qChL`+Bu!e<~boGBQqKAKs z(`ddT?G};lIU_TX_zA^N_;;X9!f z-fxQZ=iM~?UAPS0I7{6`|Ln!zDslAnD3L1>I!7@C*z?rJC~?P+eq-dd7y6~ZDAr}* z7N6xuD%fMvLMrP=_SXTwsGA!RO@OWV6aTRT>tZ5?W0E<08a{Pi+%;>r1~x@92g_h0 zr2(6E#&Zo3!z5!C6Ai{w*#Jz*1Z%a{4C$Z1vzUMJ2z8MU(M<-jzlfWa;gu3}Z`DeO zCY*yr^3$ol6iftsr;|+)FW#Q0aU)wyms1`P?%JWj6YcX77VPg;YJ9;@kmWSo$W}{S z(@*!!v=*!F-G$S5V=y*Fv}zZDICiiSivyFQhPjTp?z~1N3j>GPqeTJ<0<51=e=I}}PE_ub;GlS?{EQ3!9#gaNtML@Yl-iV!A^R?W zRe_iW9edW`qO)fcp5zC97QUZxn}wUbo1&Zao2;i+goTa@{$H<+*0~LbJhW)H$hX9{ za9whrDVmh#8!lHC8$WuUZmCpP3^i;pNT_@)_9|a;G?q$JyR5Dxdz#LT+U?4{VJE2d z(Q-DEZq9_+)bOtQPR4Mz*ty9WIuO1 zk1WsE#GDt(8~!9?h%j3#n=_l9SID@ky1W`yO=NOrGHje)Wor~suVx}@v{6Oy>tl^c zb)o*g5xo(pO1d(k8dT|PEdMpq;_e-;MXye;p7K|NFO7B^h<+9#Ct64dKdUE6P5i6q z=Y5=wqYL4$dE0XD7~7I%tE-A7l|{8g%^-$nA6(_}Hgf&1KLzO$Y;#xSoJA&~D35s7 zdFacgGDh_CIS#zm_(#LQLw~ZEz zmMaJ=s9wrSUDOL!dzR4`x2Xho`cxd|7IoN073ug^phDZX|1v}f-5K9`|E;)CCpO20 z5Od%niN|PUemwX0m0=VbK%N*V+w>|ZmpNoRrKvrb#*>=Hp!Cb)#%-%A@k{?_G7+v?gX9#z0~vvg~UW!^c++o8xF+}-47F%K97if<|(oe4V>uxsk~~iH7QT@Ia}Csr8ZJeSuYyY zyv;7;B$Q!RH}?%z7F>Pha6~P4DHrg0LvBMx0`&}KVK*Yt1^Wpb*6Z~xvWr_$e#8FY z{1Emq2~3uo<2lb1reTsAEnD5iXYD?U+7kEx5<>aJsT`}ya6 z9qZM0?pVf{J87JL7A?dF#b}ygF9?9h@bs7p@a+ zfT?qZe%knj7LWCu$5l{O2~zM&eN@m%)P3>cSdpB!k}Bot>Eqz1X@sFYj6EPWp-J({ z#Cc_FUMAgB4c?bwnSU}6&qFzWh?enh!QXOKy+`Pf=qwjV6;b8CX7PR|E7dL%o0;+6 zU#Xh)Wfl~)2m0;$dbrjluIc`VPclr1DI0Il6Y{~!4 zeK#cev8IS`^k4)sgnTdY>FD#r0YwkRS|+qCUfWE7(foINt%qcjjDIMSaN`LMU%V-9 ztxQW2l-GMkujarkr#`ZsGwh{jv~BlLOm{Pe=RT1M;Z|J4{?gomjh?xX#Y(fo-}pj$ zxhLM4=bIZxiqb1#K#(I@y0b>=O7_+C}h2X`KKopRm%jS?ubBK=sM^E-#shh$;# zJIRXa_k)W|RisePC^xb#7OD&{?UZRjxKs>|jY`WKe>FZg{%b6LIJMmDFn8@Uc}NdN zT#EgfL0TcM?&JnLkR})=FK1k5Ty8aQAy@MC@2YpNKi+!V)aiQr9}HkkVcp@elS1d< zzh)SxWQ`5eXZiNkNe7ra?hB8C1xUn6Oj1Afue1B9gl_I$5v*J>kw`7RP65}h;Y~BY zW&|+`c_`(d=BpJL<=O=CjG(vjJ`m`nJvQja$P>)Zueh>^0@OjXA$*%$^CFWHI-20_00a<*ktZxan5N@R;PD( z+1*~(K5YZCzo(z6V=zQi#P;0u_Mkg>DVXu7bcIZBGo0lO_k-I_g#cdXCR@MVH zR_@t9Cs6?;y53}Q(lC4p0AWu7ASxCBZXVF%E&#j|27o<#0FcTB0BYY%yB}%*KqjK8 zqG%kl_^0rVepYbNzzQn?E+*?HfpZ447P-a*@8XBqwOVwukb+3a!a6TM&U_3+`c&+@$?BC^?HP<;z7Y65)X2*E9{X|JF~@tZ zKI|f{s{{h-UaY|^88VMs1n60A0#~T)@!%E67FUy>DmE9)6}sM_z@JFd$l|Ejmx%L- zh)s<@(VScaEyX1Muk7hm@6$W#|4dj5<%z6ZA!v8 zsT+~IySsr!BoeHqt}Y@cN19Y3EG5;-tz0oJ@Iq}`jDtdx_lPxX@ceO{l0jyV^dt%u z=^7M7*=CiWEe7z;swJs)zuyrA?XH`7wLfD zk5J!%PQn!>!_$e60#=b7ul8pTosG9bzG z1m?AH3bTUwEN8~63ll4+q@;-5gv;sZ=n$u-rCnmSjAdN){k_;{MnXX2-C{B%y~6n! z8IPPkfzEEi#R0p3yEv_u4(7~XF0#$RN2ryVU%wP8rWK3zS%iFdS$P;pV@tSS6zT>A z2Y0$RTYf&fy(1dqUsqKo%yJmzUAs$%u)GoflnXCnqQUi&*e? zF2crS+Lq;}78Vwm@UgwDTWh`WE=Fri>lPh_X}$$)X;D&9mA9J%=o3b%l`1+pv7d+C zZ2%nwtVbt%dwU5@YgNY8*uK(H%+B+Ce`@RN#c7FgcP{?chLDhuG-qdLa{^k5RzK@) z+Wc0#Fgk4;%;<@6HOdS%I4E?-A0_`-ecdJ^E*{^TKsJZ&EaebS=rR+Xbc9q@aqht3f%~vO`4|ik`kr584pOS zIq+VCgB$G)Xiqk+dCp1w*5%T~!oa|wWP0~{9W8S-Ujx-Qt2~s1#-^r9H8Zn;wAwGI zotIl*wV5^8lEknywa%*;^FB#RPM-Yr>zpzw{qaC@aR|^+;qadL9Nsx%iQ_oQL|;l0M7W>Sb5P@muLegNrSCM5jjrP_Qnik2Q!AnkZd<6 z&y0^F{VE2HnW|uf=OR(2K+9KOO0?!ZhkL2}UyNeGpWA0;OmrpgVQ0@yy~JM1hp$j_ zFp%C}e`hS6jw|9}0)YPS4zOd~7Fyf%-K0g;@ARXXx$6XAJ>|~CMi__=L z4nLODmh*ujjr3&4@zTq8vD1wdzY29+vvEzFYz)w5a#UbNM-91&nL4EX=w?$zr2{U8<#57edtf6F9_JY>v3#EFcaglqnQKU0^l;Q1eA;@ zp9D%3MyJwC&_TA~%RWJ`Uw61Rn#(P9zIoHXp#gn-_=b*&>E1aYfMU&$CXY0x(VQ3p zfgIo7`N6I2NJvSGQhqpUv^Q>U6&Du=E*!h%>mKvR#>RpG9hc*YocQ?oF+E*f$FMLd zJ`{Qr;TJDLsJdDv_Q|c=!=3=Pg#`sfzDVdn+3c*Tg@Xg@RGpu4F}bxMwQiA2==aXf zhg=o5o1_E8xctMXgX4Fl>glsy=;=rBW6Wp(6}nvryS=^D)8NRog`OF%V5FR_$cLS~ zrP?;Ew1Te=P16u|>8YuA5>Z4kdf+T2j1U(;zhjItL%3DVj8d__LY1)+XO<#oR&=@h zgjlLR_ggqzatRUo7!5qSJuqrY*VF!{ilWBF!z=ipwMCX0YFI@X0RNOtjgRfKaB!*T zcY9zhn1^``L7)E3&e4&t%f?g7a6%zR7*gJZoASt|dAtABSi93e+RYo-_wQ&@T07sJ zPWt`(cLcT~O{&_)l?ktjiMu-wcM_(m!G{?SSz3H-wcEdcQS&h0mDIrPvFMUYRXm-K zd1L^K9Ah?5wpIxo9x9|rPNG(NGT76jR8m{3^JqJ+)^vX$TA^!ZZf;IeR8-W$%uFJ0 zHMAXtGd|q(`MLZffW@e0hOeNYU=&;N7kz4fp+73gO^h&;L)oZOzV929v|`xm}vBY_wp&))^>qt(-5jCtti*ZxJrN^^D??#;2_pBIRZ z5yafvFh_$AZ38{J3&%z&6a5LT}4xU$oZuDV>VhH&c~F5KN!<@kB-#AU}@7@ z^JwiHK~@IR-VKXzb%(8??Ao>_v}bxWOIdt~RblJio2jARdfL})40-EPo2%#TeIO%5 zmHae0fcEV4>gwt}^QAei_4)%<01H6*Z$gAR<;F)LZLS3 ztYGOOZ96QSGP~wi+iY29Y-Tp6@Na$l#px{C0c56UXD^-Un7W0D*Zf}O_YVxL1xX3D zEgwE$ldoH*{Qdiv(G_xIPgfN0K$F_LCfpxtr>m>Wi!qOFR|SJYIbAKlmxs$>Hayss z*<8I1IDU&!dRCS_Pv;{92|ieC#;THm+C*RTCsV_6I5d&jd}i3pznE()E9WQX=X0}> z$cc%l{QP`Vz)Ez}TTn>IcPPHXu!;gP+H$wT`D}3Z^fbT;oxeOhJt6C8?w+B*HXyT) zn-EaB{C764cf501pPbJ3(CNw3+MbudlBia+jOyQvN%RpKsI`I}x)<3X;LGRKvcp-K*9;%_O+7 z!ZgGs$YGI&R_l80VtqG*L9a$(;#j{IDfALP#7HkFQOiM4+3+xF!Un`Eq|uzeJ8<3$ z>f^j~DaDYMj9w58Wbg=)<9E42B9dH!z;*-)y+%l=_ibo-y@09#0qiRE{{B9AD?oC; z9LeV6yPd(VJ_K)Rr&L{DyByjI5cD}ZVOLN45aL6(mzMsq$FR`-#~a4HCN4=xu+-t@JueNht*@I~ZYeim z0Gz=G2F7q|^x-jsvb}LLQ>!5F0@H;|+|Lw-9hE!XJ-rU>}*uuZWwhlV~b&rUU zrJ(-gt);D9Y+BRkK3#qX{9fr7;N#Ply#U4$%xt?6gj6Uwxey#2oQeI5`S0%2J^(X; z>HD_T%hNl_?q>H8+{uHNLji42ewhyWoEEL)nD8uXD=L)7|5c;ohI9B}QLx7o?n_9K zs>UO?Y?fcXD;CSYf`E%5-oKas%p&mCfd*yU7}@zi`pVDIk?n#~x>q>;H zLX870INq!_Ha5p$?S4{)W`(*SZmMMO$=#sPXLUeBH=28iQlt9%uu)Y#I1xe<0(867d{*EdXNkB;<8iJ1Swu<2joBGv@I!waV;i zaEW{EU%#djdl}2286v}!lA0PpaOiVH8TC0-ricq++rVQ~v~r|w9e$5B_mnWI^DIQh z7j0$K3(?1A0XeS6ymm1K`@mb<(e?Yz-roA=WxZWzz74D*qBZc5fMjYBO~u4JKqp@SvxA( zH=2Jei{s#m+6n|KpCJF&RzxAFh_P!}6VYkFo9*Hc%R#47L{%AuoLQM;Ksxu(JDf-C kR4V`fI(kF3`G5-u*N1=ptQ#|hzGwrQs=6xmO7=1T1GjwJF#rGn literal 0 HcmV?d00001 diff --git a/Signal/Images/icon_recents.png b/Signal/Images/icon_recents.png new file mode 100644 index 0000000000000000000000000000000000000000..710466a0333ef9da79562f09fb834de871d7cc21 GIT binary patch literal 6764 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 v=_4)czd*NDSs;?00000NkvXXu0mjf3mB99 literal 0 HcmV?d00001 diff --git a/Signal/Images/in_call_phone_icon@2x.png b/Signal/Images/in_call_phone_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..157c4ed8a758806c731adf84243fc4bb177c7295 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^(m-s$!3HEB>}Om6q&N#aB8wRqxP?KOkzv*x380{4 ziEBhjaDG}zd16s2gJVj5QmTSyZen_BP-kKQh6m1KM8I4^r%&R=iI;4X=W zC#&Lmn*u+bird(xzkgQLBkO7MS<-uZ4m2<_v++n6B!C#-;v;U%pSo`Pr3|+F8_t;L z#vkdu9<#gW5?B5XXWf+Yol>#}g|+|y literal 0 HcmV?d00001 diff --git a/Signal/Images/in_call_phrase_icon.png b/Signal/Images/in_call_phrase_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4020da42419e4c390aa5dad93d5536511c2553 GIT binary patch literal 3420 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 B2q*vm literal 0 HcmV?d00001 diff --git a/Signal/Images/incoming_call_icon@2x.png b/Signal/Images/incoming_call_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..339af1f34c4491c3b69d02f4e9433acdbee707bd GIT binary patch literal 557 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 D*Oqr& literal 0 HcmV?d00001 diff --git a/Signal/Images/menu_icon@2x.png b/Signal/Images/menu_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6d3448a674f1e50a9f3b63e9db1a57b1982adb68 GIT binary patch literal 256 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/message_icon.png b/Signal/Images/message_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa796921c0f7d790ab0b2a145ad1e46771b7ac33 GIT binary patch literal 5522 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 UPPxGgi~s-t07*qoM6N<$f?`Kt-v9sr literal 0 HcmV?d00001 diff --git a/Signal/Images/mute_icon.png b/Signal/Images/mute_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..93dc1870e101309a2eb628b97f689bd749e12076 GIT binary patch literal 2889 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/mute_icon@2x.png b/Signal/Images/mute_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d62812ab1d938c9725b4c544e6ae5356c21bf948 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xd_7$pLn>~)xnRh3K!L|0@Np{l(&K^)mtWnwcxkul`+P=W zxoJwCJG-A>_+@)fC;qvh_WONz*6#kVfD9hYZ=5t`ujT;;J$?>@ySMgl0~*ZW>FVdQ I&MBb@0KF7Dj{pDw literal 0 HcmV?d00001 diff --git a/Signal/Images/mute_icon_selected.png b/Signal/Images/mute_icon_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..684da52790737ebb5618f157fa0ae97b31b3a3ca GIT binary patch literal 2889 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/mute_icon_selected@2x.png b/Signal/Images/mute_icon_selected@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f02616aca51144bec3bee4ebb4b140e5566a52b GIT binary patch literal 2877 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/notification_mini_icon.png b/Signal/Images/notification_mini_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4262352e54e993961dc1f71f7844aaa39750957f GIT binary patch literal 3742 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 IM6N<$f`&f`{Qv*} literal 0 HcmV?d00001 diff --git a/Signal/Images/notification_mini_icon@2x.png b/Signal/Images/notification_mini_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..840554e237ae60456fa5ab414b56776722541c68 GIT binary patch literal 1692 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/outgoing_call_icon@2x.png b/Signal/Images/outgoing_call_icon@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..b2610a8d61d8a736fbdf57f2ee94fc1c568a5dd5 GIT binary patch literal 3326 zcmVKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0006hNkl6g|&^5_JGC(4h-mA~jveLRR++k`G8L@1#=4WJ)Ghmh4vA zEsOUklCz{zVZaj%8A=8e5sGYxtOQ6n9r~zJ3fPVux^N^f&&T&3-FxnP&%hHwGE35t zPZo;=NtI-^=JZ)}`XpH;sV-80(H%T+^W=(~Cs*ta9!SzyRPqCo+_qX=J-+1X@g=vd z79|eC##2Hj*8%MBRyruR@BXZp+jrQlbO2yqa-tVNG6$e!HawUO@1OUN)=QEb2qLKI z0Q@Mo`|5|Y)?Dsa?G^IrgbUz}5W8pNC{z!51xq zx9W=qfc44Am{tL^HylNhd~8Y?D+|D!+XCr$Evn>GL}-(fp-gPEdeL@5IX=u}NK@*M zw`)Zv*8muYweHkyHY%764~Mnx*pwOo#N=hd9GCplyVT48->>_B9YWKPPp@Djlkjax zGOm~;*JA*50CqC2U}r5g@4Fn(Rs95PWD?j}OSu5-07cPbLWn?TI7thD7O>j35aNC0 z_kriYQovHcQovHce-{u7Lw@}-hJ1R(m-AfzgcgIt3L5v<02fGxntfYB4*&oF07*qo IM6N<$g2rGlMgRZ+ literal 0 HcmV?d00001 diff --git a/Signal/Images/phone_icon.png b/Signal/Images/phone_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e40f2743e32404463c6c4f7e255ffa1d922fa3e6 GIT binary patch literal 6172 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 Z4FJ^3t3}oovH1W1002ovPDHLkV1md$|K0!q literal 0 HcmV?d00001 diff --git a/Signal/Images/search_cancel@2x.png b/Signal/Images/search_cancel@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a0354eb4cd10d5d9a175d4ce142b1860a439efc1 GIT binary patch literal 3360 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 ks=KzUC*y~`)bafv0IkGUIDyFbpM~AR}Z1HYnYI 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 M07*qoM6N<$f`8#>Pyhe` literal 0 HcmV?d00001 diff --git a/Signal/Images/send_code_icon@2x.png b/Signal/Images/send_code_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3ed011b57ff9eea8a1f05ee5d9bc73b6a69a95 GIT binary patch literal 2984 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 P00000NkvXXu0mjf5C?Hq literal 0 HcmV?d00001 diff --git a/Signal/Images/speaker_icon_selected.png b/Signal/Images/speaker_icon_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..4b43cb39d5a041e099a28e50c2a7861d35f8bd59 GIT binary patch literal 3189 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/spinner_connecting.png b/Signal/Images/spinner_connecting.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f5cd32545968554316b9ce9783f3696da331b2 GIT binary patch literal 3686 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/spinner_connecting@2x.png b/Signal/Images/spinner_connecting@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cedf1f5e3fe4087842f944d0201b512ae3b0584a GIT binary patch literal 3851 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/spinner_connecting_flash@2x.png b/Signal/Images/spinner_connecting_flash@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e1f97e20538bf3970e277ccc55d738b8136eea GIT binary patch literal 590 zcmV-U0FQm->Ad9juHW?3*4Jyr z>h|~d<>u${@$yi*=ilMs-QM4Z&+hB&?4jE7)DB>`0004jNklh(A8vnOcnQt4z2xzw zWc`Nrv>oT6Dj*D<8S9f*g$DZ4W(ThbCG-dPM+3;tV>&i(x>`IgsK;%;8UIXII)vVw z2%lVI6LLA9#$4rU3RLuNy$Yo+Ai-zW>yqh0d&6T*5C!^s{t!m?Oau5`sQGgq!0!Uz zK^emD0v}pu{XZcszgx@pf=bdl_`HtWdP^eJL=|iMqwZo(d07E^BAilG)kLbeXg4PMx7THf_@CuAZ~r*tXpMx5&n%;WaPj c=Klm30API)&RkWI;Q#;t07*qoM6N<$g4S;;{r~^~ literal 0 HcmV?d00001 diff --git a/Signal/Images/spinner_error.png b/Signal/Images/spinner_error.png new file mode 100644 index 0000000000000000000000000000000000000000..82eb4f0007e8ad94970a1488017b9c1ca39700c1 GIT binary patch literal 3704 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 WSpta*oV0-e0000oEh@$M=z@JCJj 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 bj_(5ix0KL}&<6f700000NkvXXu0mjfR&WIX literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_contacts@2x.png b/Signal/Images/tab_icon_contacts@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9869dc31b2f844cbe88535aa838a4415f9b08d4e GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0vp^PC#tP!3HGr9@V!4DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_cg42;#DE{-7;x8BU?%{^ow(E7N8(WuEif%y>I-32)TW*IXY z1RFRd8dI2zn4}M|y({i+5xHsD85_L(z2|Zb`!c=wn&+E8_xzd`;1zo01GAH$^R%Zf z4gxHWAmpF@Y)5_b)_LzWmNP68b<(Mgo8UF~(1j0-jq~?3&AsXz9bx!XCb6aA@Veqk z-r0xS+5&mAmFnJG`>-(~-VOu$fsIF)~wM>^~{!>o0n18P}n$q2aT=a=d3R)zp#s8!w}*v!!HhaKv>cTdT-h zbF9zDCKkE;Hxyp_?P*GJ%9}5d66a3qBtB*C^ty4;sJQ0Qz1vG(bJQ&8Tg!jrl;K*r zk8ApV@_ssMx75nXX6Bh=Jk@vKEpR_2lE%Bvl%q+3qe%%&0vSI{1*#q&)!h4_>p^)z zyah=7Owi-dySKw%ef{*URk>*)FhDLYpXtsRQr@@zO8h6)+*(cp2G$!-zqx0;5(UN; NgQu&X%Q~loCII6(#OVM4 literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_favourites.png b/Signal/Images/tab_icon_favourites.png new file mode 100644 index 0000000000000000000000000000000000000000..999b6d0f1bf3bf8a63b4f6574def216ef4547cc1 GIT binary patch literal 3208 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 M07*qoM6N<$f+zk9KL7v# literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_inbox.png b/Signal/Images/tab_icon_inbox.png new file mode 100644 index 0000000000000000000000000000000000000000..8cc752ce8860310c1fc0515de24b762b6f62f823 GIT binary patch literal 3459 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 M07*qoM6N<$f(z9I3jhEB literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_keypad@2x.png b/Signal/Images/tab_icon_keypad@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe727ab47017c4e80c5438ca56951ac44e99195 GIT binary patch literal 678 zcmV;X0$KfuP)XlQMBg6O<+ z62yi4^F@jgkC`{>#&!1hBwQJm-`x?utra8PZz z)`NHOOyqXqg63ZC(18+4e|8<%wGBing6KibeN z2B8y}qoKKZILQ-0n~1AHNa_IF^{68oIIl2A^l3DDA&b3~NG26fpD*SLRaj=~A%CF0 ztXP3GG*LJ8vA$M`|HW4HuNDJp`RapPOcmTy%(TT+2J|9}+G5&HZD?ZLV#=1Z#T23y zi`rs(Ozp^~Y%zU-gT7|kVycPUeqW;g)BqT`+>bp*`8=x6ZGHt90NZrV5$dqxD*ylh M07*qoM6N<$f~&+R=l}o! literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_menu.png b/Signal/Images/tab_icon_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..29369f75c65b64a4ed96678f04603d46cc9ab579 GIT binary patch literal 3213 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 literal 0 HcmV?d00001 diff --git a/Signal/Images/tab_icon_menu@2x.png b/Signal/Images/tab_icon_menu@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..58d95ac267af5b72041dbdf0188dfeb53090b117 GIT binary patch literal 578 zcmV-I0=@l-P)x-N(232cQwwO|$4!CLD^=5C?c%w+Fpc;0f-6=8#??mE7ZfYAJXHy`XNdtNZe zd4?uLKmz6o%wjrX3&z;jiO0o55Z*fpam|%P56Y!RF)Be$2@0cTtm(uNg!v7Xq=VEb zw!@?_=xLKDU$;UKdeJiANd?$ie5rw9KO2N|ho0jI^HONine5$Bo>`$#C~oSK#*5bL zLBp#34XlJquNBRv*Lwo`%IBEW5xXAb!}=*O#Cz5S#w9U11t*g}62>INad8oZdJr|i z6R8JrWK@wyOl)+b64Y>N9B~9ee$y~EewG@=)fvMQi%jbr4s%IL5&hLQpFp(>{?2VVw5k( zS|jxfGw>p>6c!`o>|)EPIUeLmdNIAKSPhPx!(;UT{gqa43WY*39liw^0NeP5JH^Pj QNB{r;07*qoM6N<$f@&}SBme*a literal 0 HcmV?d00001 diff --git a/Signal/Images/trash_icon.png b/Signal/Images/trash_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8e53c7e2b5b76188e1fa4aeb771d41b8de7408c2 GIT binary patch literal 3119 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 Q0vPlRp00i_>zopr0BMJ@B>(^b literal 0 HcmV?d00001 diff --git a/Signal/Images/volume_high.png b/Signal/Images/volume_high.png new file mode 100644 index 0000000000000000000000000000000000000000..51512f88d3a2bebebddc0ff417a6523379f6b0a1 GIT binary patch literal 3121 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 LNkvXXu0mjfMv~kj literal 0 HcmV?d00001 diff --git a/Signal/Images/volume_high@2x.png b/Signal/Images/volume_high@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd495ee61e1a1a16b7b472e873f517a1d7b1f794 GIT binary patch literal 467 zcmV;^0WAKBP)IArfZcsnLVM|jSX53aD7C}HlC=P?hW-rts~9=**9D) zP-`G+lgz+G&J%&mGloh;5B-~8bIp2I@0gv|c+_5UPAT6{rH%tu!TVBh-jB$KnL-^0 z%nS}`zHEd<&tNR=%coH1gdDRF*+!6uT8L=PY0fu|6ICBAr9fxJXnl0{};lNN(eLnGOH|002ov JPDHLkV1k%w#f<;} literal 0 HcmV?d00001 diff --git a/Signal/Images/volume_low.png b/Signal/Images/volume_low.png new file mode 100644 index 0000000000000000000000000000000000000000..992693639691d21762db021da4b4b0678ab4df4d GIT binary patch literal 2896 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 literal 0 HcmV?d00001 diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist new file mode 100644 index 000000000..802537864 --- /dev/null +++ b/Signal/Signal-Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon29x29 + AppIcon40x40 + AppIcon60x60 + + + + CFBundleIdentifier + org.whispersystems.signal + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSApplicationCategoryType + + LSRequiresIPhoneOS + + UIAppFonts + + HelveticaNeueLTStd-Bd.otf + HelveticaNeueLTStd-Lt.otf + HelveticaNeueLTStd-Md.otf + HelveticaNeueLTStd-Th.otf + + UIBackgroundModes + + remote-notification + fetch + audio + voip + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/Signal/Signal-Prefix.pch b/Signal/Signal-Prefix.pch new file mode 100644 index 000000000..6b81e843d --- /dev/null +++ b/Signal/Signal-Prefix.pch @@ -0,0 +1,10 @@ +// +// Prefix header for all source files of the 'RedPhone' target in the 'RedPhone' project +// + +#import + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/Signal/Signal.entitlements b/Signal/Signal.entitlements new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/Signal/Signal.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/Signal/iTunesArtwork.png b/Signal/iTunesArtwork.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9bee54d175c44bd2b1c0ee51eed27b66e17336 GIT binary patch literal 44263 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 HViW#9RbgN( literal 0 HcmV?d00001 diff --git a/Signal/iTunesArtwork@2x.png b/Signal/iTunesArtwork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ff98abeb9c5d117f5fa86577054d69462e8670ac GIT binary patch literal 66142 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 z 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 literal 0 HcmV?d00001 diff --git a/Signal/main.m b/Signal/main.m new file mode 100644 index 000000000..02b7316b8 --- /dev/null +++ b/Signal/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Signal/src/AppDelegate.h b/Signal/src/AppDelegate.h new file mode 100644 index 000000000..dd17a2be2 --- /dev/null +++ b/Signal/src/AppDelegate.h @@ -0,0 +1,8 @@ +#import +#import "FutureSource.h" + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m new file mode 100644 index 000000000..3744ca6a9 --- /dev/null +++ b/Signal/src/AppDelegate.m @@ -0,0 +1,150 @@ +#import "AppDelegate.h" +#import "AppAudioManager.h" +#import "CallLogViewController.h" +#import "CategorizingLogger.h" +#import "DialerViewController.h" +#import "DiscardingLog.h" +#import "InCallViewController.h" +#import "KeyChainStorage.h" +#import "LeftSideMenuViewController.h" +#import "MMDrawerController.h" +#import "NotificationTracker.h" +#import "PreferencesUtil.h" +#import "PriorityQueue.h" +#import "RecentCallManager.h" +#import "Release.h" +#import "SettingsViewController.h" +#import "TabBarParentViewController.h" +#import "Util.h" + +#define kSignalVersionKey @"SignalUpdateVersionKey" + +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif + +@interface AppDelegate () + +@property (nonatomic, strong) MMDrawerController *drawerController; +@property (nonatomic, strong) NotificationTracker *notificationTracker; + +@end + +@implementation AppDelegate { + FutureSource* futureApnIdSource; +} + +#pragma mark Detect updates - perform migrations + +- (void)performUpdateCheck{ + // We check if NSUserDefaults key for version exists. + NSString *previousVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kSignalVersionKey]; + NSString *currentVersion = [NSString stringWithFormat:@"%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; + + if (!previousVersion) { + [KeyChainStorage clear]; + } else if ([currentVersion compare:previousVersion options:NSNumericSearch] == NSOrderedDescending) { + // The application was updated + } + + [[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:kSignalVersionKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +#pragma mark Disable cloud/iTunes syncing of call log + +- (void)disableCallLogBackup{ + NSString *preferencesPath = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/Preferences"]; + NSString *userDefaultsString = [NSString stringWithFormat:@"%@/%@.plist", preferencesPath,[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]]; + + NSURL *userDefaultsURL = [NSURL fileURLWithPath:userDefaultsString]; + NSError *error; + [userDefaultsURL setResourceValue: [NSNumber numberWithBool: YES] + forKey: NSURLIsExcludedFromBackupKey error: &error]; + + if (error) { + UIAlertView *alert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(@"WARNING", @"") message:NSLocalizedString(@"DISABLING_BACKUP_FAILED", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil, nil]; + [alert show]; + } +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [self performUpdateCheck]; + [self disableCallLogBackup]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.notificationTracker = [NotificationTracker notificationTracker]; + + // start register for apn id + futureApnIdSource = [FutureSource new]; + [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge + | UIRemoteNotificationTypeSound + | UIRemoteNotificationTypeAlert)]; + + CategorizingLogger* logger = [CategorizingLogger categorizingLogger]; + [logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}]; + [Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]]; + [[Environment getCurrent].phoneDirectoryManager startUntilCancelled:nil]; + [[Environment getCurrent].contactsManager doAfterEnvironmentInitSetup]; + [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault]; + + LeftSideMenuViewController *leftSideMenuViewController = [LeftSideMenuViewController new]; + leftSideMenuViewController.centerTabBarViewController.inboxFeedViewController.apnId = futureApnIdSource; + leftSideMenuViewController.centerTabBarViewController.settingsViewController.apnId = futureApnIdSource; + + self.drawerController = [[MMDrawerController alloc] initWithCenterViewController:leftSideMenuViewController.centerTabBarViewController + leftDrawerViewController:leftSideMenuViewController]; + self.window.rootViewController = _drawerController; + [self.window makeKeyAndVisible]; + + //Accept push notification when app is not open + NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; + if (remoteNotif) { + [self application:application didReceiveRemoteNotification:remoteNotif]; + } + + [[[Environment phoneManager] currentCallObservable] watchLatestValue:^(CallState* latestCall) { + if (latestCall == nil) return; + + InCallViewController *callViewController = [InCallViewController inCallViewControllerWithCallState:latestCall + andOptionallyKnownContact:[latestCall potentiallySpecifiedContact]]; + [_drawerController.centerViewController presentViewController:callViewController animated:YES completion:nil]; + } onThread:[NSThread mainThread] untilCancelled:nil]; + + return YES; +} + +- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { + [futureApnIdSource trySetResult:deviceToken]; +} +- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { + [futureApnIdSource trySetFailure:error]; +} + +-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { + id apnLogger = [[Environment logging] getConditionLoggerForSender:application]; + + ResponderSessionDescriptor* call; + @try { + call = [ResponderSessionDescriptor responderSessionDescriptorFromEncryptedRemoteNotification:userInfo]; + [apnLogger logNotice:[NSString stringWithFormat:@"Received remote notification. Parsed session descriptor: %@. Notication: %@.", call, userInfo]]; + } @catch (OperationFailed* ex) { + [apnLogger logWarning:[NSString stringWithFormat:@"Error parsing remote notification. Error: %@. Notifaction: %@.", ex, userInfo]]; + return; + } + + [[Environment phoneManager] incomingCallWithSession:call]; +} + +-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + if([self.notificationTracker shouldProcessNotification:userInfo]){ + [self application:application didReceiveRemoteNotification:userInfo]; + } + completionHandler(UIBackgroundFetchResultNewData); +} + +-(void) applicationDidBecomeActive:(UIApplication *)application { + [[AppAudioManager sharedInstance] awake]; + application.applicationIconBadgeNumber = 0; +} + +@end diff --git a/Signal/src/NotificationManifest.h b/Signal/src/NotificationManifest.h new file mode 100644 index 000000000..4432014b8 --- /dev/null +++ b/Signal/src/NotificationManifest.h @@ -0,0 +1,16 @@ +/** + Stores All String Constants Used for NS Notifications + **/ + +#ifndef RedPhone_NotificationManifest_h +#define RedPhone_NotificationManifest_h + +#define NOTIFICATION_DIRECTORY_UPDATE @"NOTIFICATION_DIRECTORY_UPDATE" +#define NOTIFICATION_DIRECTORY_WAS_UPDATED @"NOTIFICATION_DIRECTORY_WAS_UPDATED" +#define NOTIFICATION_NEW_USERS_AVAILABLE @"NOTIFICATION_NEW_USERS_AVAILABLE" + + +#define NOTIFICATION_DATAKEY_NEW_USERS @"NOTIFICATION_DATAKEY_NEW_USERS" + + +#endif diff --git a/Signal/src/NotificationTracker.h b/Signal/src/NotificationTracker.h new file mode 100644 index 000000000..ba7ebd57a --- /dev/null +++ b/Signal/src/NotificationTracker.h @@ -0,0 +1,12 @@ +#import + +/** + * Tracks which notifications have already been processed, and which are are seen for the first time. + **/ + +@interface NotificationTracker : NSObject + ++(NotificationTracker*) notificationTracker; +-(BOOL) shouldProcessNotification:(NSDictionary*) notification; + +@end diff --git a/Signal/src/NotificationTracker.m b/Signal/src/NotificationTracker.m new file mode 100644 index 000000000..2145c673b --- /dev/null +++ b/Signal/src/NotificationTracker.m @@ -0,0 +1,50 @@ +#import "NotificationTracker.h" +#import "CryptoTools.h" +#import "FunctionalUtil.h" + +#define MAX_NOTIFICATIONS_TO_TRACK 100 +#define NOTIFICATION_PAYLOAD_KEY @"m" + +@implementation NotificationTracker { + NSMutableArray* _witnessedNotifications; +} + ++(NotificationTracker*) notificationTracker { + NotificationTracker* notificationTracker = [NotificationTracker new]; + notificationTracker->_witnessedNotifications = [NSMutableArray new]; + return notificationTracker; +} + +-(BOOL) shouldProcessNotification:(NSDictionary*) notification { + BOOL should = ![self wasNotificationProcessed:notification]; + if (should) { + [self markNotificationAsProcessed:notification]; + } + return should; +} + +-(void) markNotificationAsProcessed:(NSDictionary*) notification { + NSData* data = [self getIdForNotification:notification]; + [_witnessedNotifications insertObject:data atIndex:0]; + + while( MAX_NOTIFICATIONS_TO_TRACK < [_witnessedNotifications count]){ + [_witnessedNotifications removeLastObject]; + } +} + +-(BOOL) wasNotificationProcessed:(NSDictionary*) notification { + NSData* data = [self getIdForNotification:notification]; + + return [_witnessedNotifications any:^int(NSData* previousData) { + return [data isEqualToData:previousData]; + }]; +} + +// Uniquely Identify a notification by the hash of the message payload. +-(NSData*) getIdForNotification:(NSDictionary*) notification { + NSData* data = [[notification objectForKey:NOTIFICATION_PAYLOAD_KEY] dataUsingEncoding:NSUTF8StringEncoding]; + NSData* notificationHash = [data hashWithSha256]; + return notificationHash; +} + +@end diff --git a/Signal/src/async/AsyncUtil.h b/Signal/src/async/AsyncUtil.h new file mode 100644 index 000000000..576bf9d13 --- /dev/null +++ b/Signal/src/async/AsyncUtil.h @@ -0,0 +1,39 @@ +#import +#import "Future.h" +#import "CancelToken.h" +#import "Terminable.h" +#import "TimeoutFailure.h" + +/** + * + * A CancellableOperationStarter launches an operation when called. + * The asynchronous result of the operation is returned as a Future. + * + * The operation should be cancelled when the token passed into the starter is cancelled. + * If the operation has not completed, cancelling the token should fail the returned future right away. + * If the operation has completed, cancelling the token should terminate the successful result. + * + **/ +typedef Future* (^CancellableOperationStarter)(id untilCancelledToken); + +/** + * + * AsyncUtil contains utitility methods used for aggregating and otherwise dealing with asynchronous operations. + * + */ +@interface AsyncUtil : NSObject + ++(Future*) raceCancellableOperations:(NSArray*)cancellableOperationStarters + untilCancelled:(id)untilCancelledToken; + ++(Future*) raceCancellableOperation:(CancellableOperationStarter)operation + againstTimeout:(NSTimeInterval)timeoutPeriod + untilCancelled:(id)untilCancelledToken; + ++(Future*) asyncTry:(CancellableOperationStarter)operation + upToNTimes:(NSUInteger)maxTryCount + withBaseTimeout:(NSTimeInterval)baseTimeout + andRetryFactor:(NSTimeInterval)timeoutRetryFactor + untilCancelled:(id)untilCancelledToken; + +@end diff --git a/Signal/src/async/AsyncUtil.m b/Signal/src/async/AsyncUtil.m new file mode 100644 index 000000000..2efe406b7 --- /dev/null +++ b/Signal/src/async/AsyncUtil.m @@ -0,0 +1,102 @@ +#import "AsyncUtil.h" +#import "Environment.h" +#import "Constraints.h" +#import "FutureSource.h" +#import "FunctionalUtil.h" +#import "CancelTokenSource.h" +#import "TimeUtil.h" +#import "FutureUtil.h" +#import "ThreadManager.h" +#import "AsyncUtilHelperRacingOperation.h" + +@implementation AsyncUtil + ++(Future*) raceCancellableOperations:(NSArray*)cancellableOperationStarters + untilCancelled:(id)untilCancelledToken { + + require(cancellableOperationStarters != nil); + if ([cancellableOperationStarters count] == 0) return [Future failed:@[]]; + + NSArray* racingOperations = [AsyncUtilHelperRacingOperation racingOperationsFromCancellableOperationStarters:cancellableOperationStarters + untilCancelled:untilCancelledToken]; + + Future* futureWinner = [AsyncUtilHelperRacingOperation asyncWinnerFromRacingOperations:racingOperations]; + + // cancel and terminate losers + [futureWinner thenDo:^(AsyncUtilHelperRacingOperation* winner) { + for (AsyncUtilHelperRacingOperation* contender in racingOperations) { + if (contender != winner) { + [contender cancelAndTerminate]; + } + } + }]; + + return [futureWinner then:^(AsyncUtilHelperRacingOperation* winner) { + return [winner futureResult]; + }]; +} + ++(Future*) raceCancellableOperation:(CancellableOperationStarter)operation + againstTimeout:(NSTimeInterval)timeoutPeriod + untilCancelled:(id)untilCancelledToken { + require(operation != nil); + require(timeoutPeriod >= 0); + + FutureSource* futureResultSource = [FutureSource new]; + + AsyncUtilHelperRacingOperation* racer = [AsyncUtilHelperRacingOperation racingOperationFromCancellableOperationStarter:operation + untilCancelled:untilCancelledToken]; + [[racer futureResult] finallyDo:^(Future *completed) { + [futureResultSource trySetResult:completed]; + }]; + + void(^tryFail)(id failure) = ^(id failure){ + if ([futureResultSource trySetFailure:failure]) { + [racer cancelAndTerminate]; + } + }; + + [TimeUtil scheduleRun:^{ tryFail([TimeoutFailure new]); } + afterDelay:timeoutPeriod + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:[futureResultSource completionAsCancelToken]]; + + [untilCancelledToken whenCancelled:^{ tryFail(untilCancelledToken); }]; + + return futureResultSource; +} + + ++(Future*) asyncTry:(CancellableOperationStarter)operation + upToNTimes:(NSUInteger)maxTryCount + withBaseTimeout:(NSTimeInterval)baseTimeout + andRetryFactor:(NSTimeInterval)timeoutRetryFactor + untilCancelled:(id)untilCancelledToken { + + require(operation != nil); + require(maxTryCount >= 0); + require(baseTimeout >= 0); + require(timeoutRetryFactor >= 0); + + if (maxTryCount == 0) return [Future failed:[TimeoutFailure new]]; + + Future* futureResult = [AsyncUtil raceCancellableOperation:operation + againstTimeout:baseTimeout + untilCancelled:untilCancelledToken]; + + return [futureResult catch:^(id error) { + bool operationCancelled = [untilCancelledToken isAlreadyCancelled]; + bool operationDidNotTimeout = ![error isKindOfClass:[TimeoutFailure class]]; + if (operationCancelled || operationDidNotTimeout) { + return [Future failed:error]; + } + + return [self asyncTry:operation + upToNTimes:maxTryCount - 1 + withBaseTimeout:baseTimeout * timeoutRetryFactor + andRetryFactor:timeoutRetryFactor + untilCancelled:untilCancelledToken]; + }]; +} + +@end diff --git a/Signal/src/async/AsyncUtilHelperRacingOperation.h b/Signal/src/async/AsyncUtilHelperRacingOperation.h new file mode 100644 index 000000000..059864f91 --- /dev/null +++ b/Signal/src/async/AsyncUtilHelperRacingOperation.h @@ -0,0 +1,21 @@ +#import +#import "Future.h" +#import "CancelTokenSource.h" +#import "AsyncUtil.h" + +@interface AsyncUtilHelperRacingOperation : NSObject + +@property (readonly,nonatomic) Future* futureResult; +@property (readonly,nonatomic) CancelTokenSource* cancelSource; + ++(AsyncUtilHelperRacingOperation*) racingOperationFromCancellableOperationStarter:(CancellableOperationStarter)cancellableOperationStarter + untilCancelled:(id)untilCancelledToken; + ++(NSArray*) racingOperationsFromCancellableOperationStarters:(NSArray*)cancellableOperationStarters + untilCancelled:(id)untilCancelledToken; + ++(Future*) asyncWinnerFromRacingOperations:(NSArray*)racingOperations; + +-(void) cancelAndTerminate; + +@end diff --git a/Signal/src/async/AsyncUtilHelperRacingOperation.m b/Signal/src/async/AsyncUtilHelperRacingOperation.m new file mode 100644 index 000000000..daecc541d --- /dev/null +++ b/Signal/src/async/AsyncUtilHelperRacingOperation.m @@ -0,0 +1,76 @@ +#import "AsyncUtilHelperRacingOperation.h" +#import "FutureSource.h" +#import "Util.h" + +@implementation AsyncUtilHelperRacingOperation + +@synthesize cancelSource, futureResult; + ++(AsyncUtilHelperRacingOperation *)racingOperationFromCancellableOperationStarter:(CancellableOperationStarter)cancellableOperationStarter + untilCancelled:(id)untilCancelledToken { + require(cancellableOperationStarter != nil); + + AsyncUtilHelperRacingOperation* instance = [AsyncUtilHelperRacingOperation new]; + + instance->cancelSource = [CancelTokenSource cancelTokenSource]; + [untilCancelledToken whenCancelled:^{ + [instance.cancelSource cancel]; + }]; + + @try { + instance->futureResult = cancellableOperationStarter([instance.cancelSource getToken]); + } @catch (OperationFailed* ex) { + instance->futureResult = [Future failed:ex]; + } + + return instance; +} + ++(NSArray*) racingOperationsFromCancellableOperationStarters:(NSArray*)cancellableOperationStarters + untilCancelled:(id)untilCancelledToken { + return [cancellableOperationStarters map:^(CancellableOperationStarter cancellableOperationStarter) { + return [AsyncUtilHelperRacingOperation racingOperationFromCancellableOperationStarter:cancellableOperationStarter + untilCancelled:untilCancelledToken]; + }]; +} + ++(Future*) asyncWinnerFromRacingOperations:(NSArray*)racingOperations { + require(racingOperations != nil); + + FutureSource* futureWinner = [FutureSource new]; + + NSUInteger totalCount = [racingOperations count]; + NSMutableArray* failures = [NSMutableArray array]; + void(^failIfAllFailed)(id) = ^(id failure) { + @synchronized(failures) { + [failures addObject:failure]; + if ([failures count] < totalCount) return; + } + + [futureWinner trySetFailure:failures]; + }; + + for (AsyncUtilHelperRacingOperation* contender in racingOperations) { + Future* futureResult = [contender futureResult]; + [futureResult thenDo:^(id result) { + [futureWinner trySetResult:contender]; + }]; + + [futureResult catchDo:failIfAllFailed]; + } + + return futureWinner; +} + +-(void) cancelAndTerminate { + [cancelSource cancel]; + + // in case cancellation is too late, terminate any eventual result + [futureResult thenDo:^(id result) { + if ([result conformsToProtocol:@protocol(Terminable)]) { + [result terminate]; + } + }]; +} + +@end diff --git a/Signal/src/async/CancelTokenSource.h b/Signal/src/async/CancelTokenSource.h new file mode 100644 index 000000000..cab5e721e --- /dev/null +++ b/Signal/src/async/CancelTokenSource.h @@ -0,0 +1,19 @@ +#import +#import "CancelToken.h" + +@class CancelTokenSourceToken; + +/** + * + * CancelTokenSource is used to create and manage cancel tokens. + * + */ +@interface CancelTokenSource : NSObject { +@private CancelTokenSourceToken* token; +} + ++(CancelTokenSource*) cancelTokenSource; +-(void) cancel; +-(id) getToken; + +@end diff --git a/Signal/src/async/CancelTokenSource.m b/Signal/src/async/CancelTokenSource.m new file mode 100644 index 000000000..9ad7b4e5d --- /dev/null +++ b/Signal/src/async/CancelTokenSource.m @@ -0,0 +1,114 @@ +#import "CancelTokenSource.h" +#import "Constraints.h" +#import "FutureSource.h" +#import "Terminable.h" + +@interface CancelTokenSourceToken : NSObject { +@private NSMutableArray* callbacks; +@private bool isImmortal; +@private bool isCancelled; +} + ++(CancelTokenSourceToken*) cancelTokenSourceToken; +-(void) cancel; +-(void) tryMakeImmortal; + +@end + +@implementation CancelTokenSource + ++(CancelTokenSource*) cancelTokenSource { + CancelTokenSource* c = [CancelTokenSource new]; + c->token = [CancelTokenSourceToken cancelTokenSourceToken]; + return c; +} + +-(void) cancel { + [token cancel]; +} + +-(NSString*) description { + return [[self getToken] description]; +} + +-(void) dealloc { + [token tryMakeImmortal]; +} +-(id) getToken { + return token; +} + +@end + +@implementation CancelTokenSourceToken + ++(CancelTokenSourceToken*) cancelTokenSourceToken { + CancelTokenSourceToken* c = [CancelTokenSourceToken new]; + c->callbacks = [NSMutableArray array]; + return c; +} + +-(void) whenCancelled:(void (^)())callback { + @synchronized(self) { + if (isImmortal) return; + if (!isCancelled) { + [callbacks addObject:[callback copy]]; + return; + } + } + callback(); +} +-(void) whenCancelledTryCancel:(FutureSource*)futureSource { + require(futureSource != nil); + [self whenCancelled:^{ + [futureSource trySetFailure:self]; + }]; +} +-(void) whenCancelledTerminate:(id)terminable { + require(terminable != nil); + [self whenCancelled:^{ + [terminable terminate]; + }]; +} + +-(void) cancel { + NSArray* callbacksToRun; + @synchronized(self) { + requireState(!isImmortal); + if (isCancelled) return; + isCancelled = true; + callbacksToRun = callbacks; + callbacks = nil; + } + for (void (^callback)() in callbacksToRun) { + callback(); + } +} +-(void) tryMakeImmortal { + @synchronized(self) { + if (isCancelled) return; + callbacks = nil; + isImmortal = true; + } +} +-(bool) isAlreadyCancelled { + @synchronized(self) { + return isCancelled; + } +} + +-(NSString*) description { + if ([self isAlreadyCancelled]) return @"Cancelled"; + if (isImmortal) return @"Immortal"; + return @"Not Cancelled Yet"; +} +-(Future*)asCancelledFuture { + FutureSource* result = [FutureSource new]; + __unsafe_unretained id weakSelf = self; + [self whenCancelled:^{ + [result trySetFailure:weakSelf]; + }]; + return result; +} + +@end diff --git a/Signal/src/async/CancelledToken.h b/Signal/src/async/CancelledToken.h new file mode 100644 index 000000000..601824460 --- /dev/null +++ b/Signal/src/async/CancelledToken.h @@ -0,0 +1,11 @@ +#import +#import "CancelToken.h" + +/** + * + * A cancel token that has already been cancelled. + * + */ +@interface CancelledToken : NSObject ++(CancelledToken*) cancelledToken; +@end diff --git a/Signal/src/async/CancelledToken.m b/Signal/src/async/CancelledToken.m new file mode 100644 index 000000000..1591c9dc6 --- /dev/null +++ b/Signal/src/async/CancelledToken.m @@ -0,0 +1,29 @@ +#import "CancelledToken.h" +#import "FutureSource.h" +#import "Util.h" + +@implementation CancelledToken + ++(CancelledToken*) cancelledToken { + return [CancelledToken new]; +} +-(bool) isAlreadyCancelled { + return true; +} +-(void) whenCancelled:(void (^)())callback { + require(callback != nil); + callback(); +} +-(void) whenCancelledTryCancel:(FutureSource*)futureSource { + require(futureSource != nil); + [futureSource trySetFailure:self]; +} +-(void) whenCancelledTerminate:(id)terminable { + require(terminable != nil); + [terminable terminate]; +} +-(Future*)asCancelledFuture { + return [Future failed:self]; +} + +@end diff --git a/Signal/src/async/Future.h b/Signal/src/async/Future.h new file mode 100644 index 000000000..149eb1c32 --- /dev/null +++ b/Signal/src/async/Future.h @@ -0,0 +1,48 @@ +#import + +@protocol CancelToken; + +/** + * + * Future is used to represent asynchronous results that will eventually be available or fail. + * + * If the future has already completed, the has/forceGet methods can be used to access it. + * To register a callback to run on completion (or right away if completed), use the then/catch methods. + * + * Note that, whenever a future would have ended with a result that is itself a Future, it instead unwraps the result. + * That is to say, the eventual result/failure of top-level future will be the same as the bottom-level future. + * e.g. Future(Future(1)) == Future(1) + * + * You can get an already-completed future via the finished/failed static methods. + * You can manage a manually-completed future via the FutureSource class. + * + */ + +@interface Future : NSObject { + bool isWiredToComplete; + + bool hasResult; + id result; + + bool hasFailure; + id failure; + + NSMutableArray* callbacks; +} + ++(Future*) finished:(id)result; ++(Future*) failed:(id)value; ++(Future*) delayed:(id)value untilAfter:(Future*)future; + +-(void) finallyDo:(void(^)(Future* completed))callback; + +-(bool) isIncomplete; +-(bool) hasSucceeded; +-(bool) hasFailed; + +-(id) forceGetResult; +-(id) forceGetFailure; + +-(id) completionAsCancelToken; + +@end diff --git a/Signal/src/async/Future.m b/Signal/src/async/Future.m new file mode 100644 index 000000000..aa6b19ba7 --- /dev/null +++ b/Signal/src/async/Future.m @@ -0,0 +1,77 @@ +#import "Future.h" +#import "FutureSource.h" +#import "Util.h" +#import "CancelTokenSource.h" + +@implementation Future + ++(Future*) finished:(id)value { + FutureSource* v = [FutureSource new]; + [v trySetResult:value]; + return v; +} ++(Future*) failed:(id)value { + FutureSource* v = [FutureSource new]; + [v trySetFailure:value]; + return v; +} ++(Future*) delayed:(id)value untilAfter:(Future*)future { + require(future != nil); + return [future then:^(id _) { + return value; + }]; +} + + +-(void) finallyDo:(void(^)(Future* completed))callback { + require(callback != nil); + @synchronized(self) { + if ([self isIncomplete]) { + [callbacks addObject:[callback copy]]; + return; + } + } + callback(self); +} + +-(bool) isIncomplete { + @synchronized(self) { + return callbacks != nil; + } +} + +-(bool) hasSucceeded { + @synchronized(self) { + return hasResult; + } +} + +-(bool) hasFailed { + @synchronized(self) { + return hasFailure; + } +} + +-(id) forceGetResult { + @synchronized(self) { + requireState([self hasSucceeded]); + } + return result; +} + +-(id) forceGetFailure { + @synchronized(self) { + requireState([self hasFailed]); + } + return failure; +} + +-(id)completionAsCancelToken { + CancelTokenSource* cancelTokenSource = [CancelTokenSource cancelTokenSource]; + [self finallyDo:^(Future*_) { + [cancelTokenSource cancel]; + }]; + return [cancelTokenSource getToken]; +} + +@end diff --git a/Signal/src/async/FutureSource.h b/Signal/src/async/FutureSource.h new file mode 100644 index 000000000..b95397587 --- /dev/null +++ b/Signal/src/async/FutureSource.h @@ -0,0 +1,21 @@ +#import +#import "Future.h" + +/** + * + * FutureSource is a future that can be manually completed/failed. + * + * You can cause the exposed future to complete via the trySet/Wire methods. + * + */ + +@interface FutureSource : Future + ++(FutureSource*) finished:(id)value; ++(FutureSource*) failed:(id)value; + +-(bool) trySetResult:(id)finalResult; +-(bool) trySetFailure:(id)failure; +-(bool) isCompletedOrWiredToComplete; + +@end diff --git a/Signal/src/async/FutureSource.m b/Signal/src/async/FutureSource.m new file mode 100644 index 000000000..b8fcf9652 --- /dev/null +++ b/Signal/src/async/FutureSource.m @@ -0,0 +1,103 @@ +#import "FutureSource.h" +#import "Constraints.h" +#import "Util.h" + +@implementation FutureSource + ++(FutureSource*) finished:(id)value { + FutureSource* v = [FutureSource new]; + [v trySetResult:value]; + return v; +} ++(FutureSource*) failed:(id)value { + FutureSource* v = [FutureSource new]; + [v trySetFailure:value]; + return v; +} + +-(id) init { + if (self = [super init]) { + self->callbacks = [NSMutableArray array]; + } + return self; +} + +-(bool) isCompletedOrWiredToComplete { + @synchronized(self) { + return callbacks == nil || isWiredToComplete; + } +} +-(bool) canBeCompleted { + @synchronized(self) { + return callbacks != nil && !isWiredToComplete; + } +} + +-(bool) trySet:(id)value failed:(bool)failed isUnwiring:(bool)unwiring { + NSArray* oldCallbacks; + @synchronized(self) { + if (!unwiring && ![self canBeCompleted]) return false; + + oldCallbacks = callbacks; + callbacks = nil; + hasResult = !failed; + hasFailure = failed; + isWiredToComplete = false; + result = hasResult ? value : nil; + failure = hasFailure ? value : nil; + } + + for (void (^callback)(Future* completed) in oldCallbacks) { + callback(self); + } + return true; +} +-(bool) trySetResult:(id)value { + if ([value isKindOfClass:[Future class]]) { + return [self tryWireForFutureCompletion:value]; + } + + return [self trySet:value + failed:false + isUnwiring:false]; +} +-(bool) trySetFailure:(id)value { + require(![value isKindOfClass:[Future class]]); + return [self trySet:value + failed:true + isUnwiring:false]; +} + +-(bool) tryWireForFutureCompletion:(Future*)futureResult { + require(futureResult != nil); + + @synchronized(self) { + if (![self canBeCompleted]) return false; + isWiredToComplete = true; + } + + [futureResult finallyDo:^(Future* completed) { + if ([completed hasSucceeded]) { + [self trySet:[completed forceGetResult] + failed:false + isUnwiring:true]; + } else { + [self trySet:[completed forceGetFailure] + failed:true + isUnwiring:true]; + } + }]; + + return true; +} + +-(NSString*) description { + @synchronized(self) { + if (isWiredToComplete) return @"Incomplete Future [Wired to Complete]"; + if ([self isIncomplete]) return @"Incomplete Future"; + if ([self hasSucceeded]) return [NSString stringWithFormat:@"Completed: %@", result]; + return [NSString stringWithFormat:@"Failed: %@", failure]; + } +} + +@end diff --git a/Signal/src/async/FutureUtil.h b/Signal/src/async/FutureUtil.h new file mode 100644 index 000000000..9b81281c8 --- /dev/null +++ b/Signal/src/async/FutureUtil.h @@ -0,0 +1,15 @@ +#import +#import "Future.h" + +@interface Future (FutureUtil) + +-(void) thenDo:(void(^)(id result))callback; +-(void) catchDo:(void(^)(id error))catcher; + +-(Future*) finally:(id(^)(Future* completed))callback; +-(Future*) then:(id(^)(id value))projection; +-(Future*) catch:(id(^)(id error))catcher; + +-(Future*) thenCompleteOnMainThread; + +@end diff --git a/Signal/src/async/FutureUtil.m b/Signal/src/async/FutureUtil.m new file mode 100644 index 000000000..ea82ff9af --- /dev/null +++ b/Signal/src/async/FutureUtil.m @@ -0,0 +1,75 @@ +#import "FutureUtil.h" +#import "FutureSource.h" +#import "Constraints.h" +#import "Operation.h" + +@implementation Future (FutureUtil) + +-(void) thenDo:(void(^)(id result))callback { + require(callback != nil); + void(^callbackCopy)(id result) = [callback copy]; + + [self finallyDo:^(Future* completed){ + if ([completed hasSucceeded]) { + callbackCopy([completed forceGetResult]); + } + }]; +} +-(void) catchDo:(void(^)(id error))catcher { + require(catcher != nil); + void(^callbackCopy)(id result) = [catcher copy]; + + [self finallyDo:^(Future* completed){ + if ([completed hasFailed]) { + callbackCopy([completed forceGetFailure]); + } + }]; +} + +-(Future*) finally:(id(^)(Future* completed))callback { + require(callback != nil); + id(^callbackCopy)(Future* completed) = [callback copy]; + FutureSource* thenResult = [FutureSource new]; + + [self finallyDo:^(Future* completed){ + @try { + [thenResult trySetResult:callbackCopy(completed)]; + } @catch (id ex) { + [thenResult trySetFailure:ex]; + } + }]; + + return thenResult; +} +-(Future*) then:(id(^)(id value))projection { + require(projection != nil); + id(^callbackCopy)(id value) = [projection copy]; + + return [self finally:^id(Future* completed){ + if ([completed hasFailed]) return completed; + + return callbackCopy([completed forceGetResult]); + }]; +} +-(Future*) catch:(id(^)(id error))catcher { + require(catcher != nil); + id(^callbackCopy)(id value) = [catcher copy]; + + return [self finally:^id(Future* completed){ + if ([completed hasSucceeded]) return completed; + + return callbackCopy([completed forceGetFailure]); + }]; +} + +-(Future*) thenCompleteOnMainThread { + FutureSource* onMainThreadResult = [FutureSource new]; + [self finallyDo:^(Future *completed) { + [Operation asyncRun:^{ + [onMainThreadResult trySetResult:completed]; + } onThread:[NSThread mainThread]]; + }]; + return onMainThreadResult; +} + +@end diff --git a/Signal/src/async/ObservableValue.h b/Signal/src/async/ObservableValue.h new file mode 100644 index 000000000..ba09b0787 --- /dev/null +++ b/Signal/src/async/ObservableValue.h @@ -0,0 +1,37 @@ +#import +#import "CancelToken.h" +#import "Queue.h" + +typedef void (^LatestValueCallback)(id latestValue); + +/** + * + * An ObservableValue represents an asynchronous stream of values, such as 'latest state of toggle' or 'latest sensor reading'. + * + */ +@interface ObservableValue : NSObject { +@protected NSMutableSet* callbacks; +@private Queue* queuedActionsToRun; +@private bool isRunningActions; +@protected bool sealed; +} + +@property (readonly,atomic) id currentValue; + +-(void) watchLatestValueOnArbitraryThread:(LatestValueCallback)callback + untilCancelled:(id)untilCancelledToken; + +-(void) watchLatestValue:(LatestValueCallback)callback + onThread:(NSThread*)thread + untilCancelled:(id)untilCancelledToken; + +@end + +@interface ObservableValueController : ObservableValue + ++(ObservableValueController *)observableValueControllerWithInitialValue:(id)value; +-(void)updateValue:(id)value; +-(void)adjustValue:(id(^)(id))adjustment; +-(void)sealValue; + +@end diff --git a/Signal/src/async/ObservableValue.m b/Signal/src/async/ObservableValue.m new file mode 100644 index 000000000..0daf42870 --- /dev/null +++ b/Signal/src/async/ObservableValue.m @@ -0,0 +1,125 @@ +#import "ObservableValue.h" +#import "Util.h" +#import "Environment.h" + +@implementation ObservableValue + +@synthesize currentValue; + +-(ObservableValue*) initWithValue:(id)value { + callbacks = [NSMutableSet set]; + queuedActionsToRun = [Queue new]; + currentValue = value; + return self; +} + +-(void) watchLatestValueOnArbitraryThread:(LatestValueCallback)callback + untilCancelled:(id)untilCancelledToken { + + require(callback != nil); + if ([untilCancelledToken isAlreadyCancelled]) return; + + void(^callbackCopy)(id value) = [callback copy]; + [self queueRun:^{ + callbackCopy(self.currentValue); + [callbacks addObject:callbackCopy]; + }]; + [untilCancelledToken whenCancelled:^{ + [self queueRun:^{ + [callbacks removeObject:callbackCopy]; + }]; + }]; +} +-(void) watchLatestValue:(LatestValueCallback)callback + onThread:(NSThread*)thread + untilCancelled:(id)untilCancelledToken { + + require(callback != nil); + require(thread != nil); + + void(^callbackCopy)(id value) = [callback copy]; + void(^threadedCallback)(id value) = ^(id value) { + [Operation asyncRun:^{callbackCopy(value);} onThread:thread]; + }; + + [self watchLatestValueOnArbitraryThread:threadedCallback + untilCancelled:untilCancelledToken]; +} + +/// used for avoiding re-entrancy issues (e.g. a callback registering another callback during enumeration) +-(void) queueRun:(void(^)())action { + @synchronized(self) { + if (isRunningActions) { + [queuedActionsToRun enqueue:[action copy]]; + return; + } + isRunningActions = true; + } + + while (true) { + @try { + action(); + } @catch (id ex) { + [[[Environment logging] getConditionLoggerForSender:self] + logError:@"A queued action failed and may have stalled an ObservableValue."]; + @synchronized(self) { + isRunningActions = false; + } + [ex raise]; + } + + @synchronized(self) { + action = [queuedActionsToRun tryDequeue]; + if (action == nil) { + isRunningActions = false; + break; + } + } + } +} + +-(void)updateValue:(id)value { + [self queueRun:^{ + if (value == currentValue) return; + requireState(!sealed); + + currentValue = value; + for (void(^callback)(id value) in callbacks) { + callback(value); + } + }]; +} +-(void)adjustValue:(id(^)(id))adjustment { + require(adjustment != nil); + [self queueRun:^{ + id oldValue = currentValue; + id newValue = adjustment(oldValue); + if (oldValue == newValue) return; + requireState(!sealed); + + currentValue = newValue; + for (void(^callback)(id value) in callbacks) { + callback(currentValue); + } + }]; +} + +@end + +@implementation ObservableValueController + ++(ObservableValueController *)observableValueControllerWithInitialValue:(id)value { + return [[ObservableValueController alloc] initWithValue:value]; +} + +-(void)updateValue:(id)value { + [super updateValue:value]; +} +-(void)adjustValue:(id(^)(id))adjustment { + [super adjustValue:adjustment]; +} +-(void) sealValue { + [self queueRun:^{sealed = true; callbacks = nil;}]; +} + +@end diff --git a/Signal/src/async/TimeoutFailure.h b/Signal/src/async/TimeoutFailure.h new file mode 100644 index 000000000..b8ccc4fad --- /dev/null +++ b/Signal/src/async/TimeoutFailure.h @@ -0,0 +1,10 @@ +#import + +/** + * + * Instances of TimeoutFailure are used as the failure stored in Futures that fail due to a timeout. + * + */ +@interface TimeoutFailure : NSObject + +@end diff --git a/Signal/src/async/TimeoutFailure.m b/Signal/src/async/TimeoutFailure.m new file mode 100644 index 000000000..798f16c1c --- /dev/null +++ b/Signal/src/async/TimeoutFailure.m @@ -0,0 +1,8 @@ +#import "TimeoutFailure.h" + +@implementation TimeoutFailure + +-(NSString *)description { + return @"Timeout"; +} +@end diff --git a/Signal/src/async/protocols/CancelToken.h b/Signal/src/async/protocols/CancelToken.h new file mode 100644 index 000000000..72442d434 --- /dev/null +++ b/Signal/src/async/protocols/CancelToken.h @@ -0,0 +1,33 @@ +#import + +@class Future; +@class FutureSource; +@protocol Terminable; + +/** + * + * CancelToken is used to cancel registered operations and terminate registered objects. + * + * A cancellable method should take a cancel token as an argument. + * The method may initially check isAlreadyCancelled to see if it can quickly finish. + * The method should use whenCancelled to register a callback to be run when the token is cancelled. + * When the callback runs, the method should release all resources and stop any dependent operations. + * If the method has a result of type Future, cancelling should transition the Future from incomplete to failed (with the cancel token as a value). + * + * A cancellable object works the same way: take a cancel token in the constructor, register for termination. + * + * Idioms: + * unlessCancelled:cancelToken //operation will complete normally unless the token is cancelled BEFORE completion + * untilCancelled:cancelToken //object or effect will last until the token is cancelled + * + */ + +@protocol CancelToken + +-(void) whenCancelled:(void(^)())callback; +-(void) whenCancelledTryCancel:(FutureSource*)futureSource; +-(void) whenCancelledTerminate:(id)terminable; +-(bool) isAlreadyCancelled; +-(Future*) asCancelledFuture; + +@end diff --git a/Signal/src/audio/AppAudioManager.h b/Signal/src/audio/AppAudioManager.h new file mode 100644 index 000000000..19f8cc0a9 --- /dev/null +++ b/Signal/src/audio/AppAudioManager.h @@ -0,0 +1,40 @@ +#import + +#import "CallProgress.h" +#import "CallTermination.h" + +/** + * The AppAudioManager is a Singleton object used to control audio settings / updates + * for the entire application. This includes playing sounds appropriately, Initializing + * Audio Settings, and interfacing with the OS. The Call Audio Pipeline it self is delegated + * to the RemoteIOAudio Class. + * + * The Audio Profile determines which preset of logic to use for playing sounds, Such as + * which speaker to use or if all sounds should be muted. + **/ + +@interface AppAudioManager : NSObject + +enum AudioProfile { + AudioProfile_Default, + AudioProfile_ExternalSpeaker, +}; + ++(AppAudioManager*) sharedInstance; + +-(void) setAudioProfile:(enum AudioProfile) profile; +-(enum AudioProfile) getCurrentAudioProfile; + +-(void) respondToProgressChange:(enum CallProgressType) progressType forLocallyInitiatedCall:(BOOL) initiatedLocally; +-(void) respondToTerminationType:(enum CallTerminationType) terminationType; + +-(BOOL) toggleSpeakerPhone; +-(void) cancellAllAudio; + +-(void) requestRequiredPermissionsIfNeeded; +-(BOOL) requestRecordingPrivlege; +-(BOOL) releaseRecordingPrivlege; + +-(BOOL) setAudioEnabled:(BOOL) enable; +-(void) awake; +@end diff --git a/Signal/src/audio/AppAudioManager.m b/Signal/src/audio/AppAudioManager.m new file mode 100644 index 000000000..33f897d41 --- /dev/null +++ b/Signal/src/audio/AppAudioManager.m @@ -0,0 +1,184 @@ +#import "AppAudioManager.h" + +#import + +#import "AudioRouter.h" +#import "SoundBoard.h" +#import "SoundPlayer.h" + + +#define DEFAULT_CATEGORY AVAudioSessionCategorySoloAmbient +#define RECORDING_CATEGORY AVAudioSessionCategoryPlayAndRecord + + +AppAudioManager* sharedAppAudioManager; + +@interface AppAudioManager (){ + enum AudioProfile _audioProfile; + BOOL isSpeakerphoneActive; +} +@property (retain) SoundPlayer *soundPlayer; +@end + + +@implementation AppAudioManager + ++(AppAudioManager*) sharedInstance { + @synchronized(self){ + if( nil == sharedAppAudioManager){ + sharedAppAudioManager = [[AppAudioManager alloc] init]; + sharedAppAudioManager.soundPlayer = [SoundPlayer new]; + [sharedAppAudioManager setAudioEnabled:YES]; + } + } + return sharedAppAudioManager; +} + +#pragma mark AudioState Management + +-(void) setAudioProfile:(enum AudioProfile) profile { + [self updateAudioRouter]; + _audioProfile = profile; +} + +-(void) updateAudioRouter{ + if (isSpeakerphoneActive){ + [AudioRouter routeAllAudioToExternalSpeaker]; + }else{ + switch (_audioProfile) { + case AudioProfile_Default: + [AudioRouter routeAllAudioToInteralSpeaker]; + break; + case AudioProfile_ExternalSpeaker: + [AudioRouter routeAllAudioToExternalSpeaker]; + break; + default: + NSLog(@"Unhandled AudioProfile"); + } + } +} + + +-(void) overrideAudioProfile{ + isSpeakerphoneActive = YES; + [self updateAudioRouter]; +} + +-(void) resetOverride{ + isSpeakerphoneActive = NO; + [self updateAudioRouter]; +} + +-(enum AudioProfile) getCurrentAudioProfile{ + return (isSpeakerphoneActive) ? AudioProfile_ExternalSpeaker : _audioProfile; +} + +#pragma mark AudioControl; +-(void) respondToProgressChange:(enum CallProgressType) progressType forLocallyInitiatedCall:(BOOL) initiatedLocally { + switch (progressType){ + case CallProgressType_Connecting: + [_soundPlayer stopAllAudio]; + case CallProgressType_Ringing: + (initiatedLocally) ? [self handleOutboundRing] : [self handleInboundRing]; + break; + case CallProgressType_Terminated: + break; + case CallProgressType_Securing: + [self handleSecuring]; + break; + case CallProgressType_Talking: + [self handleCallEstablished]; + break; + } +} + +-(void) respondToTerminationType:(enum CallTerminationType) terminationType { + if(terminationType == CallTerminationType_ResponderIsBusy) { + [_soundPlayer playSound:[SoundBoard instanceOfBusySound]]; + } + else if([self shouldErrorSoundBePlayedForCallTerminationType:terminationType]){ + [_soundPlayer playSound:[SoundBoard instanceOfErrorAlert]]; + } + else { + [_soundPlayer playSound:[SoundBoard instanceOfAlert]]; + } +} + +-(BOOL) shouldErrorSoundBePlayedForCallTerminationType:(enum CallTerminationType) type{ + [_soundPlayer stopAllAudio]; + if (CallTerminationType_RejectedLocal) {return NO;} + else if (CallTerminationType_RejectedRemote) {return NO;} + else if (CallTerminationType_HangupLocal) {return NO;} + else if (CallTerminationType_HangupRemote) {return NO;} + else if (CallTerminationType_RecipientUnavailable) {return NO;} + + return YES; +} + +-(void) handleInboundRing { + [_soundPlayer playSound:[SoundBoard instanceOfInboundRingtone]]; +} + +-(void) handleOutboundRing { + [self setAudioProfile:AudioProfile_Default]; + [_soundPlayer playSound:[SoundBoard instanceOfOutboundRingtone]]; +} + +-(void) handleSecuring { + [_soundPlayer stopAllAudio]; + [self setAudioProfile:AudioProfile_Default]; + [_soundPlayer playSound:[SoundBoard instanceOfHandshakeSound]]; +} + +-(void) handleCallEstablished { + [_soundPlayer stopAllAudio]; + [self setAudioProfile:AudioProfile_Default]; + [_soundPlayer playSound:[SoundBoard instanceOfCompletedSound]]; +} + +-(BOOL) toggleSpeakerPhone { + isSpeakerphoneActive=!isSpeakerphoneActive; + [self updateAudioRouter]; + + return isSpeakerphoneActive; +} + +#pragma mark Audio Control + +-(void) cancellAllAudio { + [_soundPlayer stopAllAudio]; +} + +-(BOOL) requestRecordingPrivlege { + return [self changeAudioSessionCategoryTo:RECORDING_CATEGORY]; +} + +-(BOOL) releaseRecordingPrivlege{ + return [self changeAudioSessionCategoryTo:DEFAULT_CATEGORY]; +} + +-(void) requestRequiredPermissionsIfNeeded { + [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { + //todo: take action? + }]; +} + +-(BOOL) changeAudioSessionCategoryTo:(NSString*) category { + NSError* e; + [[AVAudioSession sharedInstance] setCategory:category error:&e]; + return (nil != e); +} + +-(BOOL) setAudioEnabled:(BOOL) enable { + NSError* e; + [[AVAudioSession sharedInstance] setActive:enable error:&e]; + [_soundPlayer awake]; + return ( nil !=e ); +} + +-(void) awake { + [_soundPlayer awake]; +} + + +@end diff --git a/Signal/src/audio/AudioRouter.h b/Signal/src/audio/AudioRouter.h new file mode 100644 index 000000000..154a0cabb --- /dev/null +++ b/Signal/src/audio/AudioRouter.h @@ -0,0 +1,16 @@ +#import + +/** + * Interfaces with OS to control which hardware devices audio is routed to + **/ + +@interface AudioRouter : NSObject + + ++(void) restoreDefaults; ++(void) routeAllAudioToInteralSpeaker; ++(void) routeAllAudioToExternalSpeaker; + ++(BOOL) isOutputRoutedToSpeaker; ++(BOOL) isOutputRoutedToReciever; +@end diff --git a/Signal/src/audio/AudioRouter.m b/Signal/src/audio/AudioRouter.m new file mode 100644 index 000000000..00253b126 --- /dev/null +++ b/Signal/src/audio/AudioRouter.m @@ -0,0 +1,49 @@ +#import "AudioRouter.h" + +#import + +#define DEFAULT_CATAGORY AVAudioSessionCategoryPlayAndRecord + +@implementation AudioRouter + ++(void) restoreDefaults { + AVAudioSession* session = [AVAudioSession sharedInstance]; + [session setActive:YES error:nil]; + [AudioRouter routeAllAudioToExternalSpeaker]; +} + ++(void) routeAllAudioToInteralSpeaker { + AVAudioSession* session = [AVAudioSession sharedInstance]; + [session setCategory:DEFAULT_CATAGORY error:nil]; +} + ++(void) routeAllAudioToExternalSpeaker { + AVAudioSession* session = [AVAudioSession sharedInstance]; + [session setCategory:DEFAULT_CATAGORY withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil]; +} + ++(BOOL) isOutputRoutedToSpeaker{ + AVAudioSession* session = [AVAudioSession sharedInstance]; + AVAudioSessionRouteDescription* routeDesc = [session currentRoute]; + + for( AVAudioSessionPortDescription* portDesc in routeDesc.outputs){ + if (AVAudioSessionPortBuiltInSpeaker == [portDesc portType]){ + return YES; + } + } + return NO; +} + ++(BOOL) isOutputRoutedToReciever{ + AVAudioSession* session = [AVAudioSession sharedInstance]; + AVAudioSessionRouteDescription* routeDesc = [session currentRoute]; + + for( AVAudioSessionPortDescription* portDesc in routeDesc.outputs){ + if (AVAudioSessionPortBuiltInReceiver == [portDesc portType]){ + return YES; + } + } + return NO; +} + +@end diff --git a/Signal/src/audio/SoundBoard.h b/Signal/src/audio/SoundBoard.h new file mode 100644 index 000000000..6fdb21373 --- /dev/null +++ b/Signal/src/audio/SoundBoard.h @@ -0,0 +1,21 @@ +#import + +#import "SoundInstance.h" + +/** + * Factory class for generating Instances of specific Sound files. These are then maintained + * and controlled from the SoundPlayer class. This class should mask the use of any specific + * soundFiles. + **/ + +@interface SoundBoard : NSObject + ++(SoundInstance*) instanceOfInboundRingtone; ++(SoundInstance*) instanceOfOutboundRingtone; ++(SoundInstance*) instanceOfHandshakeSound; ++(SoundInstance*) instanceOfCompletedSound; ++(SoundInstance*) instanceOfBusySound; ++(SoundInstance*) instanceOfErrorAlert; ++(SoundInstance*) instanceOfAlert; + +@end diff --git a/Signal/src/audio/SoundBoard.m b/Signal/src/audio/SoundBoard.m new file mode 100644 index 000000000..c7a6d8b32 --- /dev/null +++ b/Signal/src/audio/SoundBoard.m @@ -0,0 +1,54 @@ +#import "SoundBoard.h" + +static NSString* SoundFile_Alert =@"171756__nenadsimic__picked-coin-echo-2.wav"; +static NSString* SoundFile_Busy =@"busy.mp3"; +static NSString* SoundFile_Completed =@"completed.mp3"; +static NSString* SoundFile_Failure =@"failure.mp3"; +static NSString* SoundFile_Handshake =@"handshake.mp3"; +static NSString* SoundFile_Outbound =@"outring.mp3"; +static NSString* SoundFile_Ringtone =@"r.caf"; + +@implementation SoundBoard + ++(SoundInstance*) instanceOfInboundRingtone{ + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Ringtone]; + [soundInstance setAudioToLoopIndefinitely]; + return soundInstance; +} + ++(SoundInstance*) instanceOfOutboundRingtone{ + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Outbound]; + [soundInstance setAudioToLoopIndefinitely]; + return soundInstance; +} + ++(SoundInstance*) instanceOfHandshakeSound { + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Handshake]; + [soundInstance setAudioToLoopIndefinitely]; + return soundInstance; +} + ++(SoundInstance*) instanceOfCompletedSound { + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Completed]; + return soundInstance; +} + ++(SoundInstance*) instanceOfBusySound { + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Busy]; + [soundInstance setAudioLoopCount:10]; + return soundInstance; +} + ++(SoundInstance*) instanceOfErrorAlert { + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Failure]; + return soundInstance; +} + ++(SoundInstance*) instanceOfAlert { + SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Alert]; + return soundInstance; +} + + + +@end diff --git a/Signal/src/audio/SoundInstance.h b/Signal/src/audio/SoundInstance.h new file mode 100644 index 000000000..c8bec5681 --- /dev/null +++ b/Signal/src/audio/SoundInstance.h @@ -0,0 +1,15 @@ +#import + +/** + * Wrapper for system dependant audio interface. + **/ + +@interface SoundInstance : NSObject + ++(SoundInstance*) soundInstanceForFile:(NSString*) audioFile; +-(NSString*) getId; + +-(void) setAudioToLoopIndefinitely; +-(void) setAudioLoopCount:(NSInteger) loopCount; +-(void) setCompeletionBlock:(void (^)(SoundInstance*)) block; +@end diff --git a/Signal/src/audio/SoundInstance.m b/Signal/src/audio/SoundInstance.m new file mode 100644 index 000000000..aa0c1852f --- /dev/null +++ b/Signal/src/audio/SoundInstance.m @@ -0,0 +1,61 @@ +#import "SoundInstance.h" + +@interface SoundInstance (){ + void (^completionBlock)(SoundInstance*); +} +@property (retain) AVAudioPlayer *audioPlayer; + +@end + +@implementation SoundInstance + ++(SoundInstance*) soundInstanceForFile:(NSString*) audioFile { + SoundInstance* soundInstance = [SoundInstance new]; + soundInstance.audioPlayer = [soundInstance.class createAudioPlayerForFile:audioFile]; + [soundInstance.audioPlayer setDelegate:soundInstance]; + return soundInstance; +} + +-(NSString*) getId{ + return [[self.audioPlayer url] absoluteString]; +} + +-(void) play { + [self.audioPlayer play]; +} + +-(void) stop { + [self.audioPlayer stop]; +} + +-(void) setAudioToLoopIndefinitely { + self.audioPlayer.numberOfLoops = -1; +} + +-(void) setAudioLoopCount:(NSInteger) loopCount { + self.audioPlayer.numberOfLoops = loopCount; +} + ++(NSURL*) urlToFile:(NSString*) file { + return [NSURL fileURLWithPath: + [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath],file]]; +} + ++(AVAudioPlayer*) createAudioPlayerForFile:(NSString*) audioFile { + NSURL* url = [self urlToFile:audioFile]; + + NSError *error; + AVAudioPlayer* audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; + if (nil == audioPlayer){ NSLog(@" %@",[error description]);} + return audioPlayer; +} + + +-(void) setCompeletionBlock:(void (^)(SoundInstance* )) block { + completionBlock = block; +} + +-(void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { + completionBlock(self); +} +@end diff --git a/Signal/src/audio/SoundPlayer.h b/Signal/src/audio/SoundPlayer.h new file mode 100644 index 000000000..a098836d4 --- /dev/null +++ b/Signal/src/audio/SoundPlayer.h @@ -0,0 +1,18 @@ +#import +#import "SoundInstance.h" + +/** + * SoundPlayer tracks and controls all Audiofiles being played. Currently only one instance + * of a given sound can be played at a given time. Attemping to play multiple intances of a + * sound is ignored. Multiple different sound instances can be played concurrently. + */ + +@interface SoundPlayer : NSObject + +-(void) playSound:(SoundInstance*) player; +-(void) stopSound:(SoundInstance*) player; + +-(void) stopAllAudio; +-(void) awake; +@end + diff --git a/Signal/src/audio/SoundPlayer.m b/Signal/src/audio/SoundPlayer.m new file mode 100644 index 000000000..df3b254c9 --- /dev/null +++ b/Signal/src/audio/SoundPlayer.m @@ -0,0 +1,68 @@ +#import "SoundPlayer.h" +#import "SoundBoard.h" + +@interface SoundInstance () +-(void) play; +-(void) stop; +@end + +@implementation SoundPlayer + +NSMutableDictionary* currentActiveAudioPlayers; + +-(SoundPlayer*) init{ + currentActiveAudioPlayers = [NSMutableDictionary dictionary]; + return self; +} + +#pragma mark Delegate Implementations + + +-(void) addSoundToManifest:(SoundInstance*) sound { + @synchronized(currentActiveAudioPlayers){ + [sound setCompeletionBlock:^(SoundInstance* soundInst) { + [self removeSoundFromManifest:soundInst]; + }]; + [currentActiveAudioPlayers setValue:sound forKey:[sound getId]]; + } +} +-(void) removeSoundFromManifest:(SoundInstance*) sound { + [self removeSoundFromMainifestById:[sound getId]]; +} + +-(void) removeSoundFromMainifestById:(NSString*) soundId { + @synchronized(currentActiveAudioPlayers){ + [currentActiveAudioPlayers removeObjectForKey:soundId]; + } +} + +-(void) playSound:(SoundInstance*) sound { + if (![self isSoundPlaying:sound]){ + [self addSoundToManifest:sound]; + [sound play]; + } +} + +-(void) stopSound:(SoundInstance*) sound { + SoundInstance* playingSoundInstance = [currentActiveAudioPlayers objectForKey:[sound getId] ]; + [self removeSoundFromManifest:sound]; + [playingSoundInstance stop]; +} + +-(void) stopAllAudio{ + for( SoundInstance* sound in [currentActiveAudioPlayers allValues]){ + [self stopSound:sound]; + } +} + +-(BOOL) isSoundPlaying:(SoundInstance*) sound { + return nil != [currentActiveAudioPlayers objectForKey:[sound getId]]; +} + +-(void) awake { + for( SoundInstance* sound in [currentActiveAudioPlayers allValues]){ + [sound play]; + } +} + +@end diff --git a/Signal/src/audio/incall_audio/AudioPacker.h b/Signal/src/audio/incall_audio/AudioPacker.h new file mode 100644 index 000000000..ddf1dbc00 --- /dev/null +++ b/Signal/src/audio/incall_audio/AudioPacker.h @@ -0,0 +1,34 @@ +#import +#import "Queue.h" +#import "EncodedAudioFrame.h" +#import "EncodedAudioPacket.h" + +#define AUDIO_FRAMES_PER_PACKET 2 + +/** + * + * AudioPacker is used to convert between encoded audio frames and encoded audio packets. + * AudioPacker is also responsible for assigning incrementing sequence numbers to each packet. + * + * When sending, packer is used to combine frames into packets with an appropriate sequence number. + * The initial sequence number is chosen randomly. + * + * When receiving, packer is used to split packets into their frames and grab those frames one at a time. + * Missing packets are split into frames with no audio data. The missing frames are inferred by speex. + * + */ +@interface AudioPacker : NSObject { +@private NSMutableArray* framesToSend; +@private uint16_t nextSequenceNumber; +@private Queue* audioFrameToReceiveQueue; +} + ++(AudioPacker*) audioPacker; + +-(void)packFrame:(EncodedAudioFrame*)frame; +-(EncodedAudioPacket*) tryGetFinishedAudioPacket; + +-(void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket*)potentiallyMissingPacket; +-(EncodedAudioFrame*) tryGetReceivedFrame; + +@end diff --git a/Signal/src/audio/incall_audio/AudioPacker.m b/Signal/src/audio/incall_audio/AudioPacker.m new file mode 100644 index 000000000..cd45d0734 --- /dev/null +++ b/Signal/src/audio/incall_audio/AudioPacker.m @@ -0,0 +1,72 @@ +#import "AudioPacker.h" +#import "CryptoTools.h" +#import "Conversions.h" +#import "Util.h" + +@implementation AudioPacker + ++(AudioPacker*) audioPacker { + AudioPacker* newAudioPackerInstance = [AudioPacker new]; + newAudioPackerInstance->audioFrameToReceiveQueue = [Queue new]; + newAudioPackerInstance->framesToSend = [NSMutableArray array]; + newAudioPackerInstance->nextSequenceNumber = [CryptoTools generateSecureRandomUInt16]; + + // interop fix: + // cut off the high bit (the sign bit), to avoid confusion over signed-ness when peer receives the initial number + // also cut off the next bit, so that at least 2^14 packets (instead of 1) must fail to arrive before confusion can be caused + newAudioPackerInstance->nextSequenceNumber &= 0x3FFF; + + return newAudioPackerInstance; +} + +-(void)packFrame:(EncodedAudioFrame*)frame{ + require(frame != nil); + require(![frame isMissingAudioData]); + [framesToSend addObject:[frame tryGetAudioData]]; +} + +-(EncodedAudioPacket*) tryGetFinishedAudioPacket{ + if ([framesToSend count] < AUDIO_FRAMES_PER_PACKET) return nil; + + uint16_t sequenceNumber = nextSequenceNumber++; + NSData* payload = [framesToSend concatDatas]; + + [framesToSend removeAllObjects]; + return [EncodedAudioPacket encodedAudioPacketWithAudioData:payload + andSequenceNumber:sequenceNumber]; +} + +-(void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket*)potentiallyMissingPacket{ + if (potentiallyMissingPacket != nil) { + [self enqueueFramesForPacket:potentiallyMissingPacket]; + } else { + [self enqueueFramesForMissingPacket]; + } +} + +-(EncodedAudioFrame*) tryGetReceivedFrame{ + return [audioFrameToReceiveQueue tryDequeue]; +} + +#pragma mark - +#pragma mark Private Methods + +-(void) enqueueFramesForPacket:(EncodedAudioPacket*)packet { + require(packet != nil); + + NSData* audioData = [packet audioData]; + requireState([audioData length] % AUDIO_FRAMES_PER_PACKET == 0); + + NSUInteger frameSize = [audioData length] / AUDIO_FRAMES_PER_PACKET; + for (NSUInteger i = 0; i < AUDIO_FRAMES_PER_PACKET; i++) { + NSData* frameData = [audioData subdataWithRange:NSMakeRange(frameSize*i, frameSize)]; + [audioFrameToReceiveQueue enqueue:[EncodedAudioFrame encodedAudioFrameWithData:frameData]]; + } +} +-(void) enqueueFramesForMissingPacket { + for (NSUInteger i = 0; i < AUDIO_FRAMES_PER_PACKET; i++) { + [audioFrameToReceiveQueue enqueue:[EncodedAudioFrame encodedAudioFrameWithoutData]]; + } +} + +@end diff --git a/Signal/src/audio/incall_audio/AudioSocket.h b/Signal/src/audio/incall_audio/AudioSocket.h new file mode 100644 index 000000000..c26c13c0f --- /dev/null +++ b/Signal/src/audio/incall_audio/AudioSocket.h @@ -0,0 +1,20 @@ +#import +#import "EncodedAudioPacket.h" +#import "SrtpSocket.h" + +/** + * + * AudioSocket is used to send audio packets over an SrtpSocket. + * The audio packet's data and sequence number become the payload and sequence number of the encrypted rtp packets. + * +**/ +@interface AudioSocket : NSObject { +@private SrtpSocket* srtpSocket; +@private bool started; +} + ++(AudioSocket*) audioSocketOver:(SrtpSocket*)srtpSocket; +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken; +-(void) send:(EncodedAudioPacket*)audioPacket; + +@end diff --git a/Signal/src/audio/incall_audio/AudioSocket.m b/Signal/src/audio/incall_audio/AudioSocket.m new file mode 100644 index 000000000..e634cff26 --- /dev/null +++ b/Signal/src/audio/incall_audio/AudioSocket.m @@ -0,0 +1,38 @@ +#import "AudioSocket.h" +#import "Constraints.h" +#import "RtpPacket.h" + +@implementation AudioSocket + ++(AudioSocket*) audioSocketOver:(SrtpSocket*)srtpSocket { + require(srtpSocket != nil); + AudioSocket* p = [AudioSocket new]; + p->srtpSocket = srtpSocket; + return p; +} + +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken { + require(handler != nil); + requireState(!started); + started = true; + + PacketHandlerBlock valueHandler = ^(RtpPacket* rtpPacket) { + require(rtpPacket != nil); + require([rtpPacket isKindOfClass:[RtpPacket class]]); + [handler handlePacket:[EncodedAudioPacket encodedAudioPacketWithAudioData:[rtpPacket payload] + andSequenceNumber:[rtpPacket sequenceNumber]]]; + }; + + [srtpSocket startWithHandler:[PacketHandler packetHandler:valueHandler + withErrorHandler:[handler errorHandler]] + untilCancelled:untilCancelledToken]; +} +-(void) send:(EncodedAudioPacket*)audioPacket { + require(audioPacket != nil); + + RtpPacket* rtpPacket = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:[audioPacket sequenceNumber] + andPayload:[audioPacket audioData]]; + [srtpSocket secureAndSendRtpPacket:rtpPacket]; +} + +@end diff --git a/Signal/src/audio/incall_audio/CallAudioManager.h b/Signal/src/audio/incall_audio/CallAudioManager.h new file mode 100644 index 000000000..bdb082688 --- /dev/null +++ b/Signal/src/audio/incall_audio/CallAudioManager.h @@ -0,0 +1,28 @@ +#import +#import "AudioProcessor.h" +#import "AudioSocket.h" +#import "Environment.h" +#import "RemoteIOAudio.h" +#import "SpeexCodec.h" + +/** + * + * CallAudioManager is responsible for creating and managing components related to playing real time audio communicated over a network. + * + * The components are for playing/recording, processing, and transporting audio data. + * + **/ + +@interface CallAudioManager : NSObject { +@private RemoteIOAudio* audioInterface; +@private AudioProcessor* audioProcessor; +@private AudioSocket* audioSocket; +@private bool started; +@private NSUInteger bytesInPlaybackBuffer; +} + ++(CallAudioManager*) callAudioManagerStartedWithAudioSocket:(AudioSocket*)audioSocket + andErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken; +-(BOOL) toggleMute; +@end diff --git a/Signal/src/audio/incall_audio/CallAudioManager.m b/Signal/src/audio/incall_audio/CallAudioManager.m new file mode 100644 index 000000000..12bf0e70e --- /dev/null +++ b/Signal/src/audio/incall_audio/CallAudioManager.m @@ -0,0 +1,70 @@ +#import "AnonymousTerminator.h" +#import "CallAudioManager.h" +#import "PreferencesUtil.h" +#import "ThreadManager.h" +#import "Util.h" + +#define SAFETY_FACTOR_FOR_COMPUTE_DELAY 3.0 + +@implementation CallAudioManager + ++(CallAudioManager*) callAudioManagerStartedWithAudioSocket:(AudioSocket*)audioSocket + andErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken { + require(audioSocket != nil); + + AudioProcessor* processor = [AudioProcessor audioProcessor]; + + CallAudioManager* newCallAudioManagerInstance = [CallAudioManager new]; + newCallAudioManagerInstance->audioProcessor = processor; + newCallAudioManagerInstance->audioSocket = audioSocket; + + [newCallAudioManagerInstance startWithErrorHandler:errorHandler untilCancelled:untilCancelledToken]; + + return newCallAudioManagerInstance; +} + +-(void) startWithErrorHandler:(ErrorHandlerBlock)errorHandler untilCancelled:(id)untilCancelledToken { + require(errorHandler != nil); + require(untilCancelledToken != nil); + @synchronized(self) { + requireState(!started); + started = true; + if ([untilCancelledToken isAlreadyCancelled]) return; + audioInterface = [RemoteIOAudio remoteIOInterfaceStartedWithDelegate:self untilCancelled:untilCancelledToken]; + PacketHandlerBlock handler = ^(EncodedAudioPacket* packet) { + [audioProcessor receivedPacket:packet]; + }; + [audioSocket startWithHandler:[PacketHandler packetHandler:handler + withErrorHandler:errorHandler] + untilCancelled:untilCancelledToken]; + } +} + +-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining { + if (bytesInPlaybackBuffer >= requested) { + bytesInPlaybackBuffer -= requested; + } + + NSUInteger bytesAddedIfPullMore = [audioProcessor.codec decodedFrameSizeInBytes]; + double minSafeBufferSize = MAX(requested, bytesAddedIfPullMore)*SAFETY_FACTOR_FOR_COMPUTE_DELAY; + while (bytesInPlaybackBuffer < minSafeBufferSize) { + NSData* decodedAudioData = [audioProcessor tryDecodeOrInferFrame]; + if (decodedAudioData == nil) break; + [audioInterface populatePlaybackQueueWithData:decodedAudioData]; + bytesInPlaybackBuffer += [decodedAudioData length]; + } +} + +-(void) handleNewDataRecorded:(CyclicalBuffer*)recordingQueue { + NSArray* encodedPackets = [audioProcessor encodeAudioPacketsFromBuffer:recordingQueue]; + for (EncodedAudioPacket* packet in encodedPackets) { + [audioSocket send:packet]; + } +} + +-(BOOL) toggleMute { + return [audioInterface toggleMute]; +} + +@end diff --git a/Signal/src/audio/incall_audio/EncodedAudioFrame.h b/Signal/src/audio/incall_audio/EncodedAudioFrame.h new file mode 100644 index 000000000..d319ba040 --- /dev/null +++ b/Signal/src/audio/incall_audio/EncodedAudioFrame.h @@ -0,0 +1,20 @@ +#import + +/** + * + * A data structure (frame) that stores encoded audio data + * Can be an empty frame to be inferred as missing information + * +**/ + +@interface EncodedAudioFrame : NSObject { +@private NSData* audioData; +} + ++(EncodedAudioFrame*) encodedAudioFrameWithData:(NSData*)audioData; ++(EncodedAudioFrame*) encodedAudioFrameWithoutData; + +-(bool) isMissingAudioData; +-(NSData*) tryGetAudioData; + +@end diff --git a/Signal/src/audio/incall_audio/EncodedAudioFrame.m b/Signal/src/audio/incall_audio/EncodedAudioFrame.m new file mode 100644 index 000000000..8cfff96c8 --- /dev/null +++ b/Signal/src/audio/incall_audio/EncodedAudioFrame.m @@ -0,0 +1,23 @@ +#import "EncodedAudioFrame.h" +#import "Constraints.h" + +@implementation EncodedAudioFrame + ++(EncodedAudioFrame*) encodedAudioFrameWithData:(NSData*)audioData { + require(audioData != nil); + EncodedAudioFrame* frame = [EncodedAudioFrame new]; + frame->audioData = audioData; + return frame; +} ++(EncodedAudioFrame*) encodedAudioFrameWithoutData { + return [EncodedAudioFrame new]; +} + +-(bool) isMissingAudioData { + return audioData == nil; +} +-(NSData*) tryGetAudioData { + return audioData; +} + +@end diff --git a/Signal/src/audio/incall_audio/EncodedAudioPacket.h b/Signal/src/audio/incall_audio/EncodedAudioPacket.h new file mode 100644 index 000000000..21e89e8dd --- /dev/null +++ b/Signal/src/audio/incall_audio/EncodedAudioPacket.h @@ -0,0 +1,19 @@ +#import + +/** + * + * An EncodedAudioPacket takes the audio data concatenated from multiple EncodedAudioFrame objects + * and pairs it with a sequence number, for sending over the network. + * + * Translation from streamed audio data to/from audio packets is done in AudioPacker. + * Translating (trivially) audio packets to/from rtp packets is done in AudioSocket. + * +**/ +@interface EncodedAudioPacket : NSObject + +@property (readonly,nonatomic) NSData* audioData; +@property (readonly,nonatomic) uint16_t sequenceNumber; + ++(EncodedAudioPacket*) encodedAudioPacketWithAudioData:(NSData*)audioData andSequenceNumber:(uint16_t)sequenceNumber; + +@end diff --git a/Signal/src/audio/incall_audio/EncodedAudioPacket.m b/Signal/src/audio/incall_audio/EncodedAudioPacket.m new file mode 100644 index 000000000..3cc063217 --- /dev/null +++ b/Signal/src/audio/incall_audio/EncodedAudioPacket.m @@ -0,0 +1,16 @@ +#import "EncodedAudioPacket.h" +#import "Constraints.h" + +@implementation EncodedAudioPacket + +@synthesize audioData, sequenceNumber; + ++(EncodedAudioPacket*) encodedAudioPacketWithAudioData:(NSData*)audioData andSequenceNumber:(uint16_t)sequenceNumber { + require(audioData != nil); + EncodedAudioPacket* p = [EncodedAudioPacket new]; + p->audioData = audioData; + p->sequenceNumber = sequenceNumber; + return p; +} + +@end diff --git a/Signal/src/audio/incall_audio/RemoteIOAudio.h b/Signal/src/audio/incall_audio/RemoteIOAudio.h new file mode 100644 index 000000000..7f4f130a9 --- /dev/null +++ b/Signal/src/audio/incall_audio/RemoteIOAudio.h @@ -0,0 +1,54 @@ +#import +#import +#import +#import "AudioCallbackHandler.h" +#import "CyclicalBuffer.h" +#import "Environment.h" +#import "RemoteIOBufferListWrapper.h" +#import "Terminable.h" + +enum State { + NOT_STARTED, + STARTED, + TERMINATED +}; + +/** + * + * RemoteIOAudio is responsible for playing audio through the speakers and + * recording audio through the microphone. It sends/receives this information + * to/from its AudioCallbackHandler delegate. + * + * Uses Apple's Remote I/O AudioUnit, for simultaneous input and output of audio. + * The AudioUnit provides format conversion between the hardware audio formats + * and Redphone's audio format. + * + */ +@interface RemoteIOAudio : NSObject { + + AudioUnit rioAudioUnit; + + BOOL isStreaming; + + id delegate; + + NSMutableSet * unusedBuffers; + + id starveLogger; + id conditionLogger; + id playbackBufferSizeLogger; + id recordingQueueSizeLogger; +} + +@property (nonatomic,readonly) enum State state; +@property (strong) CyclicalBuffer* recordingQueue; +@property (strong) CyclicalBuffer* playbackQueue; +@property (assign) AudioUnit rioAudioUnit; + ++(RemoteIOAudio*) remoteIOInterfaceStartedWithDelegate:(id)delegateIn untilCancelled:(id)untilCancelledToken; +-(void) populatePlaybackQueueWithData:(NSData*)data; +-(NSUInteger)getSampleRateInHertz; +-(BOOL) toggleMute; + +@end + diff --git a/Signal/src/audio/incall_audio/RemoteIOAudio.m b/Signal/src/audio/incall_audio/RemoteIOAudio.m new file mode 100644 index 000000000..231666f18 --- /dev/null +++ b/Signal/src/audio/incall_audio/RemoteIOAudio.m @@ -0,0 +1,344 @@ +#import "AppAudioManager.h" +#import "Environment.h" +#import "Constraints.h" +#import "RemoteIOAudio.h" +#import "ThreadManager.h" +#import "Util.h" + +#define INPUT_BUS 1 +#define OUTPUT_BUS 0 +#define INITIAL_NUMBER_OF_BUFFERS 100 +#define SAMPLE_SIZE_IN_BYTES 2 + +#define BUFFER_SIZE 8000 + +#define FLAG_MUTED 1 +#define FLAG_UNMUTED 0 + +@implementation RemoteIOAudio + +@synthesize playbackQueue, recordingQueue, rioAudioUnit, state; + +static bool doesActiveInstanceExist; + ++(RemoteIOAudio*) remoteIOInterfaceStartedWithDelegate:(id)delegateIn untilCancelled:(id)untilCancelledToken { + + checkOperationDescribe(!doesActiveInstanceExist, @"Only one RemoteIOInterfance instance can exist at a time. Adding more will break previous instances."); + doesActiveInstanceExist = true; + + RemoteIOAudio* newRemoteIoInterface = [RemoteIOAudio new]; + newRemoteIoInterface->starveLogger = [[Environment logging] getOccurrenceLoggerForSender:newRemoteIoInterface withKey:@"starve"]; + newRemoteIoInterface->conditionLogger = [[Environment logging] getConditionLoggerForSender:newRemoteIoInterface]; + newRemoteIoInterface->recordingQueue = [CyclicalBuffer new]; + newRemoteIoInterface->playbackQueue = [CyclicalBuffer new]; + newRemoteIoInterface->unusedBuffers = [NSMutableSet set]; + newRemoteIoInterface->state = NOT_STARTED; + newRemoteIoInterface->playbackBufferSizeLogger = [[Environment logging] getValueLoggerForValue:@"|playback queue|" from:newRemoteIoInterface]; + newRemoteIoInterface->recordingQueueSizeLogger = [[Environment logging] getValueLoggerForValue:@"|recording queue|" from:newRemoteIoInterface]; + + while ([newRemoteIoInterface->unusedBuffers count] < INITIAL_NUMBER_OF_BUFFERS) { + [newRemoteIoInterface addUnusedBuffer]; + } + [newRemoteIoInterface setupAudio]; + + [newRemoteIoInterface startWithDelegate:delegateIn untilCancelled:untilCancelledToken]; + + return newRemoteIoInterface; +} + +-(void)setupAudio { + [[AppAudioManager sharedInstance] requestRecordingPrivlege]; + rioAudioUnit = [self makeAudioUnit]; + [self setAudioEnabled]; + [self setAudioStreamFormat]; + [self setAudioCallbacks]; + [self unsetAudioShouldAllocateBuffer]; + [self checkDone:AudioUnitInitialize(rioAudioUnit)]; +} +-(AudioUnit)makeAudioUnit { + AudioComponentDescription audioUnitDescription = [self makeAudioComponentDescription]; + AudioComponent component = AudioComponentFindNext(NULL, &audioUnitDescription); + + AudioUnit unit; + [self checkDone:AudioComponentInstanceNew(component, &unit)]; + return unit; +} +-(AudioComponentDescription) makeAudioComponentDescription { + AudioComponentDescription d; + d.componentType = kAudioUnitType_Output; + d.componentSubType = kAudioUnitSubType_VoiceProcessingIO; + d.componentManufacturer = kAudioUnitManufacturer_Apple; + d.componentFlags = 0; + d.componentFlagsMask = 0; + return d; +} +-(void)setAudioEnabled { + const UInt32 enable = 1; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + INPUT_BUS, + &enable, + sizeof(enable))]; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + OUTPUT_BUS, + &enable, + sizeof(enable))]; +} +-(void)setAudioStreamFormat { + const AudioStreamBasicDescription streamDesc = [self makeAudioStreamBasicDescription]; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + OUTPUT_BUS, + &streamDesc, + sizeof(streamDesc))]; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_BUS, + &streamDesc, + sizeof(streamDesc))]; +} +-(AudioStreamBasicDescription) makeAudioStreamBasicDescription { + const UInt32 framesPerPacket = 1; + AudioStreamBasicDescription d; + d.mSampleRate = SAMPLE_RATE; + d.mFormatID = kAudioFormatLinearPCM; + d.mFramesPerPacket = framesPerPacket; + d.mChannelsPerFrame = 1; + d.mBitsPerChannel = 16; + d.mBytesPerPacket = SAMPLE_SIZE_IN_BYTES; + d.mBytesPerFrame = framesPerPacket*SAMPLE_SIZE_IN_BYTES; + d.mReserved = 0; + d.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; + return d; +} +-(void)setAudioCallbacks { + const AURenderCallbackStruct recordingCallbackStruct = {recordingCallback, (__bridge void *)(self)}; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + INPUT_BUS, + &recordingCallbackStruct, + sizeof(recordingCallbackStruct))]; + + const AURenderCallbackStruct playbackCallbackStruct = {playbackCallback, (__bridge void *)(self)}; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Global, + OUTPUT_BUS, + &playbackCallbackStruct, + sizeof(playbackCallbackStruct))]; +} +-(void)unsetAudioShouldAllocateBuffer { + const UInt32 shouldAllocateBuffer = 0; + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAudioUnitProperty_ShouldAllocateBuffer, + kAudioUnitScope_Output, + INPUT_BUS, + &shouldAllocateBuffer, + sizeof(shouldAllocateBuffer))]; +} + +-(RemoteIOBufferListWrapper*) addUnusedBuffer { + RemoteIOBufferListWrapper* buf = [RemoteIOBufferListWrapper remoteIOBufferListWithMonoBufferSize:BUFFER_SIZE]; + [unusedBuffers addObject:buf]; + return buf; +} +-(RemoteIOBufferListWrapper*) tryTakeUnusedBuffer { + RemoteIOBufferListWrapper* buffer = (RemoteIOBufferListWrapper*)[unusedBuffers anyObject]; + if (buffer == nil) return nil; + [unusedBuffers removeObject:buffer]; + return buffer; +} +-(void) returnUsedBuffer:(RemoteIOBufferListWrapper*)buffer { + require(buffer != nil); + if (state == TERMINATED) return; // in case a buffer was in use as termination occurred + [unusedBuffers addObject:buffer]; +} + +-(void) startWithDelegate:(id)delegateIn untilCancelled:(id)untilCancelledToken { + require(delegateIn != nil); + @synchronized(self){ + requireState(state == NOT_STARTED); + + delegate = delegateIn; + [self checkDone:AudioOutputUnitStart(rioAudioUnit)]; + state = STARTED; + } + + [untilCancelledToken whenCancelled:^{ + @synchronized(self) { + state = TERMINATED; + doesActiveInstanceExist = false; + [self checkDone:AudioOutputUnitStop(rioAudioUnit)]; + [[AppAudioManager sharedInstance] releaseRecordingPrivlege]; + [unusedBuffers removeAllObjects]; + } + }]; +} + +static OSStatus recordingCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberSamples, + AudioBufferList *ioData) { + + + @autoreleasepool { + + RemoteIOAudio *instance = (__bridge RemoteIOAudio*)inRefCon; + + RemoteIOBufferListWrapper* buffer; + @synchronized(instance) { + buffer = [instance tryTakeUnusedBuffer]; + } + if (buffer == nil) { + // All buffers in use. Drop recorded audio. + return 1; // arbitrary error code + } + AudioBufferList bufferList = *[buffer audioBufferList]; + [instance checkDone:AudioUnitRender([instance rioAudioUnit], + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberSamples, + &bufferList)]; + buffer.sampleCount = inNumberSamples; + + [instance performSelector:@selector(onRecordedDataIntoBuffer:) + onThread:[ThreadManager lowLatencyThread] + withObject:buffer + waitUntilDone:NO]; + + } + return noErr; +} +-(void) onRecordedDataIntoBuffer:(RemoteIOBufferListWrapper*)buffer { + @synchronized(self){ + if (state == TERMINATED) return; + NSData* recordedAudioVolatile = [NSData dataWithBytesNoCopy:[buffer audioBufferList]->mBuffers[0].mData + length:[buffer sampleCount]*SAMPLE_SIZE_IN_BYTES + freeWhenDone:NO]; + [recordingQueue enqueueData:recordedAudioVolatile]; + [self returnUsedBuffer:buffer]; + } + + [recordingQueueSizeLogger logValue:[recordingQueue enqueuedLength]]; + [delegate handleNewDataRecorded:recordingQueue]; +} + +-(void)populatePlaybackQueueWithData:(NSData*)data { + require(data != nil); + if ([data length] == 0) return; + @synchronized(self){ + [playbackQueue enqueueData:data]; + } +} +static OSStatus playbackCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberSamples, + AudioBufferList *ioData) { + RemoteIOAudio* instance = (__bridge RemoteIOAudio*)inRefCon; + NSUInteger requestedByteCount = inNumberSamples * SAMPLE_SIZE_IN_BYTES; + NSUInteger availableByteCount; + @synchronized(instance) { + availableByteCount = [[instance playbackQueue] enqueuedLength]; + + if (availableByteCount < requestedByteCount) { + NSUInteger starveAmount = requestedByteCount - availableByteCount; + [instance->starveLogger markOccurrence:[NSNumber numberWithDouble:starveAmount]]; + } else { + NSData* audioToCopyVolatile = [[instance playbackQueue] dequeuePotentialyVolatileDataWithLength:requestedByteCount]; + memcpy(ioData->mBuffers[0].mData, [audioToCopyVolatile bytes], [audioToCopyVolatile length]); + } + } + + [Operation asyncRun:^{[instance onRequestedPlaybackDataAmount:requestedByteCount + andHadAvailableAmount:availableByteCount];} + onThread:[ThreadManager lowLatencyThread]]; + + if (availableByteCount < requestedByteCount) { + return 1; // arbitrary error code + } + + return noErr; +} +-(void) onRequestedPlaybackDataAmount:(NSUInteger)requestedByteCount andHadAvailableAmount:(NSUInteger)availableByteCount { + @synchronized(self) { + if (state == TERMINATED) return; + } + NSUInteger consumedByteCount = availableByteCount >= requestedByteCount ? requestedByteCount : 0; + NSUInteger remainingByteCount = availableByteCount - consumedByteCount; + [playbackBufferSizeLogger logValue:remainingByteCount]; + [delegate handlePlaybackOccurredWithBytesRequested:requestedByteCount andBytesRemaining:remainingByteCount]; +} + +-(void) dealloc{ + if (state != TERMINATED) { + doesActiveInstanceExist = false; + } +} + +-(NSUInteger)getSampleRateInHertz { + return SAMPLE_RATE; +} + +-(void)checkDone:(OSStatus)resultCode { + if (resultCode == kAudioSessionNoError) return; + + NSString* failure; + if (resultCode == kAudioServicesUnsupportedPropertyError) { + failure = @"unsupportedPropertyError"; + } else if (resultCode == kAudioServicesBadPropertySizeError) { + failure = @"badPropertySizeError"; + } else if (resultCode == kAudioServicesBadSpecifierSizeError) { + failure = @"badSpecifierSizeError"; + } else if (resultCode == kAudioServicesSystemSoundUnspecifiedError) { + failure = @"systemSoundUnspecifiedError"; + } else if (resultCode == kAudioServicesSystemSoundClientTimedOutError) { + failure = @"systemSoundClientTimedOutError"; + } else if (resultCode == errSecParam){ + failure = @"oneOrMoreNonValidParameter"; + }else { + failure = [[NSNumber numberWithInt:resultCode] description]; + } + [conditionLogger logError:[NSString stringWithFormat:@"StatusCheck failed: %@", failure]]; +} + +-(bool) isAudioMuted { + UInt32 currentMuteFlag; + UInt32 propertyByteSize; + [self checkDone:AudioUnitGetProperty(rioAudioUnit, + kAUVoiceIOProperty_MuteOutput, + kAudioUnitScope_Global, + OUTPUT_BUS, + ¤tMuteFlag, + &propertyByteSize)]; + return (FLAG_MUTED == currentMuteFlag); +} + +-(BOOL) toggleMute { + BOOL shouldBeMuted = ![self isAudioMuted]; + UInt32 newValue = shouldBeMuted ? FLAG_MUTED : FLAG_UNMUTED; + + [self checkDone:AudioUnitSetProperty(rioAudioUnit, + kAUVoiceIOProperty_MuteOutput, + kAudioUnitScope_Global, + OUTPUT_BUS, + &newValue, + sizeof(newValue))]; + + return shouldBeMuted; +} + + +@end diff --git a/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.h b/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.h new file mode 100644 index 000000000..d9401b600 --- /dev/null +++ b/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.h @@ -0,0 +1,18 @@ +#import +#import +#import + +/** + * + * RemoteIOBufferListWrapper is used by RemoteIOAudio to manage an audio buffer list. + * + **/ +@interface RemoteIOBufferListWrapper : NSObject + +@property (nonatomic, assign) NSUInteger sampleCount; +@property (nonatomic, readonly) AudioBufferList* audioBufferList; + ++(RemoteIOBufferListWrapper*) remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize; + + +@end diff --git a/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.m b/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.m new file mode 100644 index 000000000..e3ec7df51 --- /dev/null +++ b/Signal/src/audio/incall_audio/RemoteIOBufferListWrapper.m @@ -0,0 +1,22 @@ +#import "RemoteIOBufferListWrapper.h" + +@implementation RemoteIOBufferListWrapper + +@synthesize sampleCount, audioBufferList; + ++(RemoteIOBufferListWrapper*) remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize { + AudioBufferList* audioBufferList = malloc(sizeof(AudioBufferList)); + audioBufferList->mNumberBuffers = 1; + audioBufferList->mBuffers[0].mNumberChannels = 1; + audioBufferList->mBuffers[0].mDataByteSize = bufferSize; + audioBufferList->mBuffers[0].mData = malloc(bufferSize); + + RemoteIOBufferListWrapper* w = [RemoteIOBufferListWrapper new]; + w->audioBufferList = audioBufferList; + return w; +} +-(void) dealloc { + free(audioBufferList->mBuffers[0].mData); +} + +@end diff --git a/Signal/src/audio/incall_audio/SpeexCodec.h b/Signal/src/audio/incall_audio/SpeexCodec.h new file mode 100644 index 000000000..a2e81ed9f --- /dev/null +++ b/Signal/src/audio/incall_audio/SpeexCodec.h @@ -0,0 +1,37 @@ +#import +#import +#import +#import "Logging.h" + +/** + * + * SpeexCodec is responsible for encoding and decoding audio using the speex codec. + * + **/ +@interface SpeexCodec : NSObject { + + void* decodingState; + SpeexBits decodingBits; + spx_int16_t decodingFrameSize; + spx_int16_t* decodingBuffer; + + void* encodingState; + SpeexBits encodingBits; + spx_int16_t encodingFrameSize; + NSUInteger encodingBufferSizeInBytes; + + BOOL terminated; + + id logging; + NSUInteger cachedEncodedLength; +} + ++(SpeexCodec*) speexCodec; ++(NSUInteger)frameSizeInSamples; +-(NSUInteger)encodedFrameSizeInBytes; +-(NSUInteger)decodedFrameSizeInBytes; + +-(NSData*)decode:(NSData*)encodedData; +-(NSData*)encode:(NSData*)rawData; + +@end diff --git a/Signal/src/audio/incall_audio/SpeexCodec.m b/Signal/src/audio/incall_audio/SpeexCodec.m new file mode 100644 index 000000000..6dddd80b4 --- /dev/null +++ b/Signal/src/audio/incall_audio/SpeexCodec.m @@ -0,0 +1,168 @@ +#import "Environment.h" +#import "Constraints.h" +#import "SpeexCodec.h" + +@implementation SpeexCodec + +#define MAX_FRAMES 512 +#define DECODED_SAMPLE_SIZE_IN_BYTES 2 +#define FRAME_SIZE_IN_SAMPLES 160 +#define NUMBER_OF_CHANNELS 1 +#define PERPETUAL_ENHANCER_PARAMETER 1 +#define VARIABLE_BIT_RATE_PARAMETER 0 +#define QUALITY_PARAMETER 3 +#define COMPLEXITY_PARAMETER 1 + ++(SpeexCodec*) speexCodec { + SpeexCodec* c = [SpeexCodec new]; + c->logging = [[Environment logging] getConditionLoggerForSender:self]; + [c openSpeex]; + return c; +} +-(void) dealloc { + [self closeSpeex]; +} + ++(NSUInteger)frameSizeInSamples { + return FRAME_SIZE_IN_SAMPLES; +} + +-(NSUInteger)encodedFrameSizeInBytes { + requireState(cachedEncodedLength != 0); + return cachedEncodedLength; +} +-(NSUInteger)decodedFrameSizeInBytes { + return FRAME_SIZE_IN_SAMPLES*DECODED_SAMPLE_SIZE_IN_BYTES; +} + +-(void) determineDecodedLength { + NSData* encoded = [self encode:[NSMutableData dataWithLength:[self decodedFrameSizeInBytes]]]; + cachedEncodedLength = [encoded length]; +} + +-(NSData*)encode:(NSData*)rawData { + require(rawData != nil); + require([rawData length] == FRAME_SIZE_IN_SAMPLES*DECODED_SAMPLE_SIZE_IN_BYTES); + speex_bits_reset(&encodingBits); + speex_encode_int(encodingState, (spx_int16_t*)[rawData bytes], &encodingBits); + + NSMutableData* outputBuffer = [NSMutableData dataWithLength:encodingBufferSizeInBytes]; + int outputSizeInBytes = speex_bits_write(&encodingBits, [outputBuffer mutableBytes], (int)encodingBufferSizeInBytes); + checkOperation(outputSizeInBytes > 0); + [outputBuffer setLength:(NSUInteger)outputSizeInBytes]; + + return outputBuffer; +} + +-(NSData*)decode:(NSData*)potentiallyMissingEncodedData { + NSUInteger encodedDataLength = [potentiallyMissingEncodedData length]; + if (potentiallyMissingEncodedData == nil) { + encodedDataLength = [self decodedFrameSizeInBytes]; // size for infering audio data + } + if(encodedDataLength == 0) return nil; + + SpeexBits *dbits = [self getSpeexBitsFromData:potentiallyMissingEncodedData andDataLength:(int)encodedDataLength]; + + int decodedLength = [self decodeSpeexBits:dbits withLength:(int)encodedDataLength]; + + return [NSData dataWithBytes:decodingBuffer length:(NSUInteger)decodedLength*sizeof(spx_int16_t)]; +} + +-(NSUInteger) encodedDataLengthFromData:(NSData*)potentiallyMissingEncodedData{ + if (potentiallyMissingEncodedData != nil) { + return [potentiallyMissingEncodedData length]; + } + return [self decodedFrameSizeInBytes]; +} + +- (SpeexBits *)getSpeexBitsFromData:(NSData *)encodedData andDataLength:(int)encodedDataLength { + SpeexBits *dbits=NULL; + char* encodingStream; + if([encodedData bytes] != NULL){ + encodingStream = (char*)[encodedData bytes]; + speex_bits_read_from(&decodingBits, encodingStream, encodedDataLength); + dbits = &decodingBits; + } + return dbits; +} + +- (int)decodeSpeexBits:(SpeexBits *)dbits withLength:(int)encodedDataLength { + int decodingBufferIndex = 0; + int decodingBufferLength = (int)[self decodedFrameSizeInBytes]; + int count = 0; + while(0 == speex_decode_int(decodingState, dbits, decodingBuffer + decodingBufferIndex)){ + count++; + decodingBufferIndex += decodingFrameSize; + if(decodingBufferIndex + decodingFrameSize > decodingBufferLength){ + [logging logWarning:[NSString stringWithFormat:@"out of space in the decArr buffer, idx=%d, frameSize=%d, length=%d", decodingBufferIndex,decodingFrameSize,decodingBufferLength]]; + break; + } + if(decodingBufferIndex + decodingFrameSize > decodingFrameSize * MAX_FRAMES){ + [logging logWarning:[NSString stringWithFormat:@"out of space in the dec_buffer buffer, idx=%d", decodingBufferIndex]]; + break; + } + if(dbits == NULL){ + break; + } + } + return decodingBufferIndex; +} + +-(void) openSpeex { + [self initiateEncoderAndDecoder]; + [self applySpeexSettings]; + [self initiateSpeexBuffers]; + [self determineDecodedLength]; +} + +- (void)initiateEncoderAndDecoder { + + encodingState = speex_encoder_init(&speex_nb_mode); + decodingState = speex_decoder_init(&speex_nb_mode); + + checkOperationDescribe(encodingState != NULL, @"speex encoder init failed"); + checkOperationDescribe(decodingState != NULL, @"speex decoder init failed"); +} + +-(void)applySpeexSettings{ + spx_int32_t tmp; + tmp=PERPETUAL_ENHANCER_PARAMETER; + speex_decoder_ctl(decodingState, SPEEX_SET_ENH, &tmp); + tmp=VARIABLE_BIT_RATE_PARAMETER; + speex_encoder_ctl(encodingState, SPEEX_SET_VBR, &tmp); + tmp=QUALITY_PARAMETER; + speex_encoder_ctl(encodingState, SPEEX_SET_QUALITY, &tmp); + tmp=COMPLEXITY_PARAMETER; + speex_encoder_ctl(encodingState, SPEEX_SET_COMPLEXITY, &tmp); + + speex_encoder_ctl(encodingState, SPEEX_GET_FRAME_SIZE, &encodingFrameSize); + speex_decoder_ctl(decodingState, SPEEX_GET_FRAME_SIZE, &decodingFrameSize); + + int sampleRate = (int)SAMPLE_RATE; + speex_encoder_ctl(encodingState, SPEEX_SET_SAMPLING_RATE, &sampleRate); + speex_decoder_ctl(decodingState, SPEEX_SET_SAMPLING_RATE, &sampleRate); +} + +- (void)initiateSpeexBuffers { + speex_bits_init(&encodingBits); + speex_bits_init(&decodingBits); + + encodingBufferSizeInBytes = (NSUInteger)encodingFrameSize * MAX_FRAMES; + decodingBuffer = (spx_int16_t *) malloc( sizeof( spx_int16_t ) * (NSUInteger)decodingFrameSize*MAX_FRAMES ); + + checkOperationDescribe(decodingBuffer != NULL, @"buffer allocation failed"); +} + +-(void) closeSpeex { + terminated = true; + + speex_encoder_destroy( encodingState ); + speex_decoder_destroy( decodingState ); + + speex_bits_destroy( &encodingBits ); + speex_bits_destroy( &decodingBits ); + + free(decodingBuffer); +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/AudioProcessor.h b/Signal/src/audio/incall_audio/processing/AudioProcessor.h new file mode 100644 index 000000000..dda0d9160 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/AudioProcessor.h @@ -0,0 +1,42 @@ +#import +#import "AudioPacker.h" +#import "AudioStretcher.h" +#import "BufferDepthMeasure.h" +#import "CyclicalBuffer.h" +#import "DecayingSampleEstimator.h" +#import "EncodedAudioFrame.h" +#import "Logging.h" +#import "SpeexCodec.h" +#import "StretchFactorController.h" + +/** + * + * AudioProcessor is responsible for transforming audio as it travels between + * the network and the hardware. + * + * Processing involves: + * - encoding and decoding using the Speex codec + * - packing/unpacking audio into/from EncodedAudioPackets + * - stretching audio using spandsp + * - buffering audio in a jitter queue + * - infering gaps in audio. + * + **/ + +@interface AudioProcessor : NSObject { +@private StretchFactorController* stretchFactorController; +@private AudioStretcher* audioStretcher; +@private AudioPacker* audioPacker; +@private JitterQueue* jitterQueue; +@private bool haveReceivedDataYet; +} + +@property (nonatomic,readonly) SpeexCodec* codec; + ++(AudioProcessor*) audioProcessor; + +-(void) receivedPacket:(EncodedAudioPacket*)packet; +-(NSArray*) encodeAudioPacketsFromBuffer:(CyclicalBuffer*) buffer; +-(NSData*) tryDecodeOrInferFrame; + +@end diff --git a/Signal/src/audio/incall_audio/processing/AudioProcessor.m b/Signal/src/audio/incall_audio/processing/AudioProcessor.m new file mode 100644 index 000000000..8ec6003f3 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/AudioProcessor.m @@ -0,0 +1,65 @@ +#import "AudioProcessor.h" +#import "Environment.h" +#import "Constraints.h" +#import "SpeexCodec.h" +#import "Util.h" + +@implementation AudioProcessor + +@synthesize codec; + ++(AudioProcessor*) audioProcessor { + JitterQueue* jitterQueue = [JitterQueue jitterQueue]; + + AudioProcessor* newAudioProcessorInstance = [AudioProcessor new]; + newAudioProcessorInstance->codec = [SpeexCodec speexCodec]; + newAudioProcessorInstance->stretchFactorController = [StretchFactorController stretchFactorControllerForJitterQueue:jitterQueue]; + newAudioProcessorInstance->audioStretcher = [AudioStretcher audioStretcher]; + newAudioProcessorInstance->jitterQueue = jitterQueue; + newAudioProcessorInstance->audioPacker = [AudioPacker audioPacker]; + return newAudioProcessorInstance; +} + +-(void)receivedPacket:(EncodedAudioPacket *)packet { + [jitterQueue tryEnqueue:packet]; +} +-(NSArray*) encodeAudioPacketsFromBuffer:(CyclicalBuffer*) buffer { + require(buffer != nil); + + NSMutableArray* encodedFrames = [NSMutableArray array]; + NSUInteger decodedFrameSize = [codec decodedFrameSizeInBytes]; + while([buffer enqueuedLength] >= decodedFrameSize){ + NSData* rawFrame = [buffer dequeueDataWithLength:decodedFrameSize]; + requireState(rawFrame != nil); + NSData* encodedFrameData = [codec encode:rawFrame]; + [encodedFrames addObject:[EncodedAudioFrame encodedAudioFrameWithData:encodedFrameData]]; + } + + NSMutableArray* encodedPackets = [NSMutableArray array]; + for (EncodedAudioFrame* frame in encodedFrames) { + [audioPacker packFrame:frame]; + EncodedAudioPacket* packet = [audioPacker tryGetFinishedAudioPacket]; + if (packet != nil) [encodedPackets addObject:packet]; + } + + return encodedPackets; +} +-(EncodedAudioFrame*) pullFrame { + EncodedAudioFrame* frame = [audioPacker tryGetReceivedFrame]; + if (frame != nil) return frame; + + EncodedAudioPacket* potentiallyMissingPacket = [jitterQueue tryDequeue]; + [audioPacker unpackPotentiallyMissingAudioPacket:potentiallyMissingPacket]; + return [audioPacker tryGetReceivedFrame]; +} +-(NSData*) tryDecodeOrInferFrame { + EncodedAudioFrame* frame = [self pullFrame]; + haveReceivedDataYet |= ![frame isMissingAudioData]; + if (!haveReceivedDataYet) return nil; + + NSData* raw = [codec decode:[frame tryGetAudioData]]; + double stretch = [stretchFactorController getAndUpdateDesiredStretchFactor]; + return [audioStretcher stretchAudioData:raw stretchFactor:stretch]; +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/AudioStretcher.h b/Signal/src/audio/incall_audio/processing/AudioStretcher.h new file mode 100644 index 000000000..bf24a84d5 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/AudioStretcher.h @@ -0,0 +1,21 @@ +#import +#import "AudioStretcher.h" +#import "private/time_scale.h" + +/** + * + * AudioStretcher is used to make queued audio play faster or slower, without affecting pitch. + * This capability allows the amount of buffered audio to be controlled. + * + * Internally, uses the same spandsp used by the android version of RedPhone. + * + **/ + +@interface AudioStretcher : NSObject { +@private struct time_scale_state_s timeScaleState; +} + ++(AudioStretcher*) audioStretcher; +-(NSData*) stretchAudioData:(NSData*)audioData stretchFactor:(double)stretchFactor; + +@end diff --git a/Signal/src/audio/incall_audio/processing/AudioStretcher.m b/Signal/src/audio/incall_audio/processing/AudioStretcher.m new file mode 100644 index 000000000..648a1fd90 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/AudioStretcher.m @@ -0,0 +1,53 @@ +#import "AudioStretcher.h" +#import "Constraints.h" +#import "time_scale.h" +#import "Util.h" + +#define MIN_STRETCH_FACTOR 0.1 +#define MAX_STRETCH_FACTOR 10 + +@implementation AudioStretcher + ++(AudioStretcher*) audioStretcher { + AudioStretcher* s = [AudioStretcher new]; + checkOperation(time_scale_init(&s->timeScaleState, SAMPLE_RATE, 1.0) != NULL); + return s; +} + +-(NSData*) stretchAudioData:(NSData*)audioData stretchFactor:(double)stretchFactor { + require(stretchFactor > MIN_STRETCH_FACTOR); + require(stretchFactor < MAX_STRETCH_FACTOR); + + if (audioData == nil) return nil; + + checkOperationDescribe(time_scale_rate(&timeScaleState, (float)stretchFactor) == 0, @"Changing time scaling"); + + int inputSampleCount = [audioData length]/sizeof(int16_t); + int maxOutputSampleCount = [self getSafeMaxOutputSampleCountFromInputSampleCount:inputSampleCount]; + + int16_t* input = (int16_t*)[audioData bytes]; + + NSMutableData* d = [NSMutableData dataWithLength:(NSUInteger)maxOutputSampleCount*sizeof(int16_t)]; + int outputSampleCount = time_scale(&timeScaleState, [d mutableBytes], input, inputSampleCount); + checkOperationDescribe(outputSampleCount >= 0 && outputSampleCount <= maxOutputSampleCount, @"Scaling audio"); + + return [d take:(NSUInteger)outputSampleCount*sizeof(int16_t)]; +} + +-(int) getSafeMaxOutputSampleCountFromInputSampleCount:(int)inputSampleCount{ + // WARNING: In some cases SpanDSP (time_scale.h v 1.20) underestimates how much buffer it will need, so we must pad the result to be safe. + // Issues has been notified upstream and once it is patched the padding can be removed + int unsafe_maxOutputSampleCount = time_scale_max_output_len(&timeScaleState, inputSampleCount); + const int BUFFER_OVERFLOW_PROTECTION_PAD = 2048; + const int BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER = 2; + int expandedMaxCountToAvoidBufferOverflows = BUFFER_OVERFLOW_PROTECTION_PAD + (unsafe_maxOutputSampleCount * BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER); + + checkOperation(expandedMaxCountToAvoidBufferOverflows >= 0); + return expandedMaxCountToAvoidBufferOverflows; +} + +-(void) dealloc { + checkOperation(time_scale_release(&timeScaleState) == 0); +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.h b/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.h new file mode 100644 index 000000000..38a917809 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.h @@ -0,0 +1,28 @@ +#import +#import "DecayingSampleEstimator.h" +#import "DropoutTracker.h" +#import "Environment.h" +#import "JitterQueue.h" +#import "Terminable.h" +#import "SpeexCodec.h" + +/** + * + * DesiredBufferDepthController is used to determine how much audio should be kept in reserve, in case of network jitter. + * + * An instance must be registered to receive notifications from the network jitter queue in order to function correctly. + * When packets arrive at a consistent rate without dropping, the desired buffer depth tends to decrease. + * When packet delays vary significantly and when packets drop before arriving, the desired buffer tends to increase. + * + **/ + +@interface DesiredBufferDepthController : NSObject { +@private DropoutTracker* dropoutTracker; +@private DecayingSampleEstimator* decayingDesiredBufferDepth; +@private id desiredDelayLogger; +} + ++(DesiredBufferDepthController*) desiredBufferDepthControllerForJitterQueue:(JitterQueue*)jitterQueue; +-(double) getAndUpdateDesiredBufferDepth; + +@end diff --git a/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.m b/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.m new file mode 100644 index 000000000..42bc0bdef --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/DesiredBufferDepthController.m @@ -0,0 +1,65 @@ +#import "DesiredBufferDepthController.h" +#import "Constraints.h" +#import "PreferencesUtil.h" +#import "Util.h" +#import "AudioPacker.h" + +#define MAX_DESIRED_FRAME_DELAY 12 +#define MIN_DESIRED_FRAME_DELAY 0.5 +#define DROPOUT_THRESHOLD 10 + +#define DESIRED_BUFFER_DEPTH_DECAY_RATE 0.01 + +@implementation DesiredBufferDepthController + ++(DesiredBufferDepthController*) desiredBufferDepthControllerForJitterQueue:(JitterQueue*)jitterQueue { + require(jitterQueue != nil); + + NSTimeInterval audioDurationPerPacket = (NSTimeInterval)(AUDIO_FRAMES_PER_PACKET*[SpeexCodec frameSizeInSamples]) + / SAMPLE_RATE; + double initialDesiredBufferDepth = [[Environment preferences] getCachedOrDefaultDesiredBufferDepth]; + + DropoutTracker* dropoutTracker = [DropoutTracker dropoutTrackerWithAudioDurationPerPacket:audioDurationPerPacket]; + + DecayingSampleEstimator* decayingDesiredBufferDepth = + [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:initialDesiredBufferDepth + andDecayPerUnitSample:DESIRED_BUFFER_DEPTH_DECAY_RATE]; + + DesiredBufferDepthController* result = [DesiredBufferDepthController new]; + result->dropoutTracker = dropoutTracker; + result->decayingDesiredBufferDepth = decayingDesiredBufferDepth; + result->desiredDelayLogger = [[Environment logging] getValueLoggerForValue:@"desired buffer depth" from:self]; + + [jitterQueue registerWatcher:result]; + return result; +} + +-(double) getAndUpdateDesiredBufferDepth { + double r = [decayingDesiredBufferDepth currentEstimate]; + [decayingDesiredBufferDepth updateWithNextSample:[dropoutTracker getDepthForThreshold:DROPOUT_THRESHOLD]]; + [decayingDesiredBufferDepth forceEstimateTo:[NumberUtil clamp:[decayingDesiredBufferDepth currentEstimate] + toMin:MIN_DESIRED_FRAME_DELAY + andMax:MAX_DESIRED_FRAME_DELAY]]; + [desiredDelayLogger logValue:r]; + return r; +} + +-(void) notifyArrival:(uint16_t)sequenceNumber { + [dropoutTracker observeSequenceNumber:sequenceNumber]; +} +-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType { +} +-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type { +} +-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount { +} +-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { +} +-(void) notifyDiscardOverflow:(uint16_t)sequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { +} + +-(void) terminate { + [[Environment preferences] setCachedDesiredBufferDepth:[decayingDesiredBufferDepth currentEstimate]]; +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/DropoutTracker.h b/Signal/src/audio/incall_audio/processing/DropoutTracker.h new file mode 100644 index 000000000..c1481fc04 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/DropoutTracker.h @@ -0,0 +1,50 @@ +#import +#import "EventWindow.h" +#import "Queue.h" +#import "SequenceCounter.h" + +/** + * + * This is a direct implementation of Redphone's DropoutTracker for android: + * + * > When a network dropout occurs packet latency will increase quickly to a maximum latency before + * > quickly returning to a normal value. These dropouts create local peaks in latency that we can + * > detect. + * + * > DropoutTracker registers when packets with given sequence numbers arrive and + * > attempts to predict when additional packets should arrive based on this information. + * + * > The predicted arrival times allow the estimation of an arrival lateness value for each packet + * > The last several lateness values are tracked and local peaks in lateness are detected + * + * > Peak latencies above a the "threshold of actionability" (300msec) are discarded since we never + * > want to buffer more than 300msec worth of audio packets. + * + * > We track how many peaks occurred in several latency ranges (expressed as a packet count) and + * > provide the ability to answer the question: + * + * > If we wanted to have only N buffer underflows in the past M seconds, how many packets would need + * > to be stored in the buffer? + * + * > @author Stuart O. Anderson + * + * Link to android implementation with comments: + * https://github.com/WhisperSystems/RedPhone/blob/2a6e8cec64cc457d2eb02351d0f3adf769db7a84/src/org/thoughtcrime/redphone/audio/DropoutTracker.java + * + */ + +@interface DropoutTracker : NSObject { +@private Queue* priorLatenesses; +@private NSMutableArray* lateBins; +@private SequenceCounter* sequenceCounter; +@private NSTimeInterval audioDurationPerPacket; +@private bool startTimeInitialized; +@private NSTimeInterval startTime; +@private NSTimeInterval drift; +} + ++(DropoutTracker*) dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket; +-(void) observeSequenceNumber:(uint16_t)seqNum; +-(double)getDepthForThreshold:(NSUInteger)maxEvents; + +@end diff --git a/Signal/src/audio/incall_audio/processing/DropoutTracker.m b/Signal/src/audio/incall_audio/processing/DropoutTracker.m new file mode 100644 index 000000000..4fe32fd2c --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/DropoutTracker.m @@ -0,0 +1,91 @@ +#import "DropoutTracker.h" +#import "Util.h" +#import "Constraints.h" +#import "Environment.h" +#import "TimeUtil.h" + +#define maxActionableLatency 0.3 +#define binsPerPacket 2.0 +#define PRIOR_LATENESS_LENGTH 6 +#define LATE_BINS_LENGTH 20 +#define LATE_BIN_WINDOW_IN_SECONDS 30.0 + +@implementation DropoutTracker + ++(DropoutTracker*) dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket { + + DropoutTracker* d = [DropoutTracker new]; + + d->audioDurationPerPacket = audioDurationPerPacket; + d->sequenceCounter = [SequenceCounter sequenceCounter]; + d->priorLatenesses = [Queue new]; + d->lateBins = [NSMutableArray array]; + + for (NSUInteger i = 0; i < PRIOR_LATENESS_LENGTH; i++) { + [d->priorLatenesses enqueue:[NSNumber numberWithDouble:0]]; + } + for (NSUInteger i = 0; i < LATE_BINS_LENGTH; i++) { + [d->lateBins addObject:[EventWindow eventWindowWithWindowDuration:LATE_BIN_WINDOW_IN_SECONDS]]; + } + + return d; +} + +-(NSTimeInterval) detectPeak { + NSTimeInterval possiblePeakLatency = [[priorLatenesses peekAt:PRIOR_LATENESS_LENGTH/2] doubleValue]; + + for (NSUInteger i=0; i < PRIOR_LATENESS_LENGTH-1; i++) { + if ([[priorLatenesses peekAt:i] doubleValue] > possiblePeakLatency) { + return -1; + } + } + + return possiblePeakLatency; +} + +-(void) observeSequenceNumber:(uint16_t)sequenceNumber { + int64_t expandedSequenceNumber = [sequenceCounter convertNext:sequenceNumber]; + if( !startTimeInitialized ) { + startTime = [TimeUtil time]; + startTimeInitialized = true; + } + + NSTimeInterval expectedTime = startTime + drift + expandedSequenceNumber * audioDurationPerPacket; + NSTimeInterval now = [TimeUtil time]; + NSTimeInterval secLate = now-expectedTime; + [priorLatenesses enqueue:[NSNumber numberWithDouble:secLate]]; + [priorLatenesses dequeue]; + + // update zero time + // if a packet arrives early, immediately update the timebase + // if it arrives late, conservatively update the timebase + drift += MIN(secLate, secLate / 50); + + //Was the last packet a local peak? + NSTimeInterval peakLatency = [self detectPeak]; + if (peakLatency > 0) { + NSUInteger lateBin = (NSUInteger)[NumberUtil clamp:peakLatency/(audioDurationPerPacket / binsPerPacket) + toMin:0 + andMax:LATE_BINS_LENGTH - 1]; + + if (peakLatency <= maxActionableLatency) { + [[lateBins objectAtIndex:lateBin] addEventAtTime:now]; + } + } + +} + +/// How many packets would we have needed to buffer to stay below the desired dropout event count +-(double)getDepthForThreshold:(NSUInteger)maxEvents { + NSUInteger eventCount = 0; + NSTimeInterval now = [TimeUtil time]; + for (NSUInteger depth = LATE_BINS_LENGTH; depth > 0; depth--) { + eventCount += [[lateBins objectAtIndex:depth-1] countAfterRemovingEventsBeforeWindowEndingAt:now]; + if (eventCount > maxEvents) { + return (depth-1)/binsPerPacket; + } + } + return -1/binsPerPacket; +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/JitterQueue.h b/Signal/src/audio/incall_audio/processing/JitterQueue.h new file mode 100644 index 000000000..60968c2e7 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/JitterQueue.h @@ -0,0 +1,38 @@ +#import +#import "PriorityQueue.h" +#import "EncodedAudioPacket.h" +#import "Logging.h" +#import "JitterQueueNotificationReceiver.h" +#import "BufferDepthMeasure.h" + +/** + * + * JitterQueue handles the details of organizing and consuming real-time data, which may fail to arrive on time or arrive out of order. + * +**/ + +@interface JitterQueue : NSObject { +@private PriorityQueue* resultPriorityQueue; +@private uint16_t readHeadMin; +@private uint16_t readHeadSpan; +@private NSMutableSet* idsInJitterQueue; +@private NSMutableArray* watchers; +@private uint16_t largestLatestEnqueued; +} + ++(JitterQueue*) jitterQueue; + +-(void) registerWatcher:(id)watcher; + +// Provides a framed audio packet to be placed in sequence. +// Returns true if the packet was successfully enqueued. +// Returns false if the packet has arrived too late, far too early, or is a duplicate. +-(bool) tryEnqueue:(EncodedAudioPacket*)packet; + +// Returns the next framed audio packet in sequence, or nil if the next packet has not arrived in time. +-(EncodedAudioPacket*) tryDequeue; + +// The number of framed audio packets (contiguous or not) in the queue. +-(NSUInteger) count; + +@end diff --git a/Signal/src/audio/incall_audio/processing/JitterQueue.m b/Signal/src/audio/incall_audio/processing/JitterQueue.m new file mode 100644 index 000000000..9dcb9019d --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/JitterQueue.m @@ -0,0 +1,172 @@ +#import "JitterQueue.h" +#import "Util.h" +#import "Environment.h" + +#define TRANSITIVE_SAFETY_RANGE 0x4000 +#define READ_HEAD_MAX_QUEUE_AHEAD 0x1000 +#define READ_HEAD_BAD_SPAN_THRESHOLD 0x100 +#define MAXIMUM_JITTER_QUEUE_SIZE_BEFORE_DISCARDING 25 + +@implementation JitterQueue + ++(JitterQueue*) jitterQueue { + JitterQueue* q = [JitterQueue new]; + q->readHeadSpan = READ_HEAD_BAD_SPAN_THRESHOLD+1; + q->watchers = [NSMutableArray array]; + [q registerWatcher:[[Environment logging] jitterQueueNotificationReceiver]]; + return q; +} + +-(void) registerWatcher:(id)watcher { + if (watcher == nil) return; + [watchers addObject:watcher]; +} + +-(NSUInteger) count { + return [resultPriorityQueue count]; +} + +-(bool) tryEnqueue:(EncodedAudioPacket*)audioPacket { + require(audioPacket != nil); + + uint16_t sequenceNumber = [audioPacket sequenceNumber]; + if (![self tryFitIntoSequence:sequenceNumber]) { + return false; + } + + [idsInJitterQueue addObject:[NSNumber numberWithUnsignedInt:sequenceNumber]]; + [resultPriorityQueue enqueue:audioPacket]; + if ([NumberUtil congruentDifferenceMod2ToThe16From:largestLatestEnqueued to:sequenceNumber] > 0) { + largestLatestEnqueued = sequenceNumber; + } + + for (id watcher in watchers) { + [watcher notifyArrival:sequenceNumber]; + } + + [self discardExcess]; + + return true; +} +-(bool) tryFitIntoSequence:(uint16_t)sequenceNumber { + int16_t sequenceNumberRelativeToReadHead = [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin to:sequenceNumber]; + + enum JitterBadArrivalType badArrivalType; + if ([self tryForceSyncIfNecessary:sequenceNumber]) { + return true; + } else if (sequenceNumberRelativeToReadHead < 0) { + badArrivalType = JitterBadArrivalType_Stale; + } else if (sequenceNumberRelativeToReadHead > READ_HEAD_MAX_QUEUE_AHEAD) { + badArrivalType = JitterBadArrivalType_TooSoon; + } else if ([idsInJitterQueue containsObject:[NSNumber numberWithUnsignedInt:sequenceNumber]]) { + badArrivalType = JitterBadArrivalType_Duplicate; + } else { + return true; + } + + for (id watcher in watchers) { + [watcher notifyBadArrival:sequenceNumber ofType:badArrivalType]; + } + + return false; +} +-(bool) tryForceSyncIfNecessary:(uint16_t)sequenceNumber { + if (readHeadSpan <= READ_HEAD_BAD_SPAN_THRESHOLD) return false; + + if (resultPriorityQueue != nil) { // (only log resyncs, not the initial sync) + for (id watcher in watchers) { + [watcher notifyResyncFrom:readHeadMin to:sequenceNumber]; + } + } + + readHeadMin = sequenceNumber; + largestLatestEnqueued = sequenceNumber; + readHeadSpan = 1; + idsInJitterQueue = [NSMutableSet set]; + resultPriorityQueue = [JitterQueue makeCyclingPacketPriorityQueue]; + + return true; +} ++(PriorityQueue*) makeCyclingPacketPriorityQueue { + return [PriorityQueue priorityQueueAscendingWithComparator:^NSComparisonResult(EncodedAudioPacket* obj1, EncodedAudioPacket* obj2) { + int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:[obj2 sequenceNumber] + to:[obj1 sequenceNumber]]; + requireState(abs(d) <= TRANSITIVE_SAFETY_RANGE); + return [NumberUtil signOfInt32:d]; + }]; +} +-(void) discardExcess { + if ([resultPriorityQueue count] <= MAXIMUM_JITTER_QUEUE_SIZE_BEFORE_DISCARDING) return; + + EncodedAudioPacket* discarded = [resultPriorityQueue dequeue]; + uint16_t discardedSequenceNumber = [discarded sequenceNumber]; + [idsInJitterQueue removeObject:[NSNumber numberWithUnsignedInt:discardedSequenceNumber]]; + + uint16_t oldReadHeadMax = readHeadMin+readHeadSpan-1; + readHeadMin = [[resultPriorityQueue peek] sequenceNumber]; + readHeadSpan = 1; + + for (id e in watchers) { + [e notifyDiscardOverflow:discardedSequenceNumber + resyncingFrom:oldReadHeadMax + to:readHeadMin]; + } +} + +-(EncodedAudioPacket*) tryDequeue { + if ([self checkReactIfOutOfSyncForDequeue] + || [self checkReactIfEmptyForDequeue] + || [self checkReactIfNoDataUnderReadHeadForDequeue]) { + + return nil; + } + + EncodedAudioPacket* result = [resultPriorityQueue dequeue]; + readHeadMin = [result sequenceNumber]+1; + readHeadSpan = 1; + [idsInJitterQueue removeObject:[NSNumber numberWithUnsignedInt:[result sequenceNumber]]]; + + for (id e in watchers) { + [e notifyDequeue:[result sequenceNumber] withRemainingEnqueuedItemCount:[idsInJitterQueue count]]; + } + return result; +} +-(bool) checkReactIfOutOfSyncForDequeue { + bool isOutOfSync = readHeadSpan > READ_HEAD_BAD_SPAN_THRESHOLD; + if (isOutOfSync) { + for (id watcher in watchers) { + [watcher notifyBadDequeueOfType:JitterBadDequeueType_Desynced]; + } + } + return isOutOfSync; +} +-(bool) checkReactIfEmptyForDequeue { + bool isEmpty = [resultPriorityQueue count] == 0; + if (isEmpty) { + readHeadSpan += 1; + for (id watcher in watchers) { + [watcher notifyBadDequeueOfType:JitterBadDequeueType_Empty]; + } + } + return isEmpty; +} +-(bool) checkReactIfNoDataUnderReadHeadForDequeue { + EncodedAudioPacket* result = [resultPriorityQueue peek]; + int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin + to:[result sequenceNumber]]; + bool notUnderHead = d < 0 || d >= readHeadSpan; + if (notUnderHead) { + readHeadSpan += 1; + for (id watcher in watchers) { + [watcher notifyBadDequeueOfType:JitterBadDequeueType_NoDataUnderReadHead]; + } + } + return notUnderHead; +} + +-(int16_t) currentBufferDepth { + if (readHeadSpan > READ_HEAD_BAD_SPAN_THRESHOLD) return 0; + return [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin + readHeadSpan - 1 to:largestLatestEnqueued]; +} + +@end diff --git a/Signal/src/audio/incall_audio/processing/StretchFactorController.h b/Signal/src/audio/incall_audio/processing/StretchFactorController.h new file mode 100644 index 000000000..514d548f4 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/StretchFactorController.h @@ -0,0 +1,26 @@ +#import +#import "BufferDepthMeasure.h" +#import "DecayingSampleEstimator.h" +#import "DesiredBufferDepthController.h" + +/** + * + * StretchFactorController determines when and how much to stretch audio. + * When the amount of buffered audio is more than desired, audio is shrunk (sped up). + * When the amount of buffered audio is less than desired, audio is expanded (slowed down). + * + **/ + +@interface StretchFactorController : NSObject { +@private int currentStretchMode; +@private id bufferDepthMeasure; +@private DesiredBufferDepthController* desiredBufferDepthController; +@private DecayingSampleEstimator* decayingBufferDepthMeasure; +@private id stretchModeChangeLogger; +} + ++(StretchFactorController*) stretchFactorControllerForJitterQueue:(JitterQueue*)jitterQueue; + +-(double) getAndUpdateDesiredStretchFactor; + +@end diff --git a/Signal/src/audio/incall_audio/processing/StretchFactorController.m b/Signal/src/audio/incall_audio/processing/StretchFactorController.m new file mode 100644 index 000000000..cee159117 --- /dev/null +++ b/Signal/src/audio/incall_audio/processing/StretchFactorController.m @@ -0,0 +1,73 @@ +#import "Constraints.h" +#import "StretchFactorController.h" + +#define STRETCH_MODE_EXPAND 0 +#define STRETCH_MODE_NORMAL 1 +#define STRETCH_MODE_SHRINK 2 +#define STRETCH_MODE_SUPER_SHRINK 3 +static double STRETCH_MODE_FACTORS[] = {1/0.95, 1, 1/1.05, 0.5}; + +#define SUPER_SHRINK_THRESHOLD 10 +#define SHRINK_THRESHOLD 3.0 +#define EXPAND_THRESHOLD -2.0 + +#define BUFFER_DEPTH_DECAYING_FACTOR 0.05 + +@implementation StretchFactorController + ++(StretchFactorController*) stretchFactorControllerForJitterQueue:(JitterQueue*)jitterQueue { + + require(jitterQueue != nil); + + DesiredBufferDepthController* desiredBufferDepthController = + [DesiredBufferDepthController desiredBufferDepthControllerForJitterQueue:jitterQueue]; + + StretchFactorController* p = [StretchFactorController new]; + p->desiredBufferDepthController = desiredBufferDepthController; + p->currentStretchMode = STRETCH_MODE_NORMAL; + p->bufferDepthMeasure = jitterQueue; + p->decayingBufferDepthMeasure = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0 andDecayPerUnitSample:BUFFER_DEPTH_DECAYING_FACTOR]; + p->stretchModeChangeLogger = [[Environment logging] getValueLoggerForValue:@"stretch factor" from:self]; + return p; +} + +-(int) reconsiderStretchMode { + int16_t currentBufferDepth = [bufferDepthMeasure currentBufferDepth]; + [decayingBufferDepthMeasure updateWithNextSample:currentBufferDepth]; + double desiredBufferDepth = [desiredBufferDepthController getAndUpdateDesiredBufferDepth]; + + double currentBufferDepthDelta = currentBufferDepth - desiredBufferDepth; + double decayingBufferDepthDelta = [decayingBufferDepthMeasure currentEstimate] - desiredBufferDepth; + + bool shouldStartSuperShrink = currentBufferDepthDelta > SUPER_SHRINK_THRESHOLD; + bool shouldMaintainSuperShrink = currentBufferDepthDelta > 0 && currentStretchMode == STRETCH_MODE_SUPER_SHRINK; + bool shouldEndSuperShrinkAndResetEstimate = !shouldMaintainSuperShrink && currentStretchMode == STRETCH_MODE_SUPER_SHRINK; + + bool shouldStartShrink = decayingBufferDepthDelta > SHRINK_THRESHOLD; + bool shouldMaintainShrink = decayingBufferDepthDelta > 0 && currentStretchMode == STRETCH_MODE_SHRINK; + + bool shouldStartExpand = decayingBufferDepthDelta < EXPAND_THRESHOLD; + bool shouldMaintainExpand = decayingBufferDepthDelta < 0 && currentStretchMode == STRETCH_MODE_EXPAND; + + if (shouldEndSuperShrinkAndResetEstimate) { + [decayingBufferDepthMeasure forceEstimateTo:desiredBufferDepth]; + return STRETCH_MODE_NORMAL; + } + if (shouldStartSuperShrink) return STRETCH_MODE_SUPER_SHRINK; + if (shouldStartShrink) return STRETCH_MODE_SHRINK; + if (shouldStartExpand) return STRETCH_MODE_EXPAND; + if (shouldMaintainShrink || shouldMaintainExpand || shouldMaintainSuperShrink) return currentStretchMode; + return STRETCH_MODE_NORMAL; +} + +-(double) getAndUpdateDesiredStretchFactor { + int prevMode = currentStretchMode; + currentStretchMode = [self reconsiderStretchMode]; + double factor = STRETCH_MODE_FACTORS[currentStretchMode]; + if (prevMode != currentStretchMode) { + [stretchModeChangeLogger logValue:factor]; + } + return factor; +} + +@end diff --git a/Signal/src/audio/incall_audio/protocols/AudioCallbackHandler.h b/Signal/src/audio/incall_audio/protocols/AudioCallbackHandler.h new file mode 100644 index 000000000..a7acf017b --- /dev/null +++ b/Signal/src/audio/incall_audio/protocols/AudioCallbackHandler.h @@ -0,0 +1,12 @@ +#import +#import "CyclicalBuffer.h" + +/** + * + * An AudioCallbackHandler is called when audio is played or recorded. + * + **/ +@protocol AudioCallbackHandler +-(void) handleNewDataRecorded:(CyclicalBuffer*) data; +-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining; +@end diff --git a/Signal/src/audio/incall_audio/protocols/BufferDepthMeasure.h b/Signal/src/audio/incall_audio/protocols/BufferDepthMeasure.h new file mode 100644 index 000000000..ea8a2b9c7 --- /dev/null +++ b/Signal/src/audio/incall_audio/protocols/BufferDepthMeasure.h @@ -0,0 +1,5 @@ +#import + +@protocol BufferDepthMeasure +-(int16_t) currentBufferDepth; +@end diff --git a/Signal/src/audio/incall_audio/protocols/JitterQueueNotificationReceiver.h b/Signal/src/audio/incall_audio/protocols/JitterQueueNotificationReceiver.h new file mode 100644 index 000000000..ca59c2270 --- /dev/null +++ b/Signal/src/audio/incall_audio/protocols/JitterQueueNotificationReceiver.h @@ -0,0 +1,20 @@ +#import + +enum JitterBadArrivalType { + JitterBadArrivalType_Duplicate = 0, // for when two packets with the same sequence number arrive + JitterBadArrivalType_Stale = 1, // for when sequence number is behind read head + JitterBadArrivalType_TooSoon = 2 // for when sequence number is *way* ahead of read head +}; +enum JitterBadDequeueType { + JitterBadDequeueType_Desynced = 0, // for when so many lack-of-datas have accumulated that the read head can skip + JitterBadDequeueType_Empty = 1, // for when there's no data anywhere in the jitter queue + JitterBadDequeueType_NoDataUnderReadHead = 2 // for when there's data in the jitter queue, but it's ahead of the read head +}; +@protocol JitterQueueNotificationReceiver +-(void) notifyArrival:(uint16_t)sequenceNumber; +-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount; +-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType; +-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type; +-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber; +-(void) notifyDiscardOverflow:(uint16_t)discardedSequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber; +@end diff --git a/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.h b/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.h new file mode 100644 index 000000000..2c38b3067 --- /dev/null +++ b/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.h @@ -0,0 +1,18 @@ +#import +#import "AudioCallbackHandler.h" + +/** + * + * AnonymousAudioCallbackHandler implements AudioCallbackHandler with blocks passed to its constructor. + * + **/ + +@interface AnonymousAudioCallbackHandler : NSObject + +@property (readonly,nonatomic,copy) void (^handleNewDataRecordedBlock)(CyclicalBuffer* data); +@property (readonly,nonatomic,copy) void (^handlePlaybackOccurredWithBytesRequestedBlock)(NSUInteger requested, NSUInteger bytesRemaining); + ++(AnonymousAudioCallbackHandler*) anonymousAudioInterfaceDelegateWithRecordingCallback:(void(^)(CyclicalBuffer* data))recordingCallback + andPlaybackOccurredCallback:(void(^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback; + +@end diff --git a/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.m b/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.m new file mode 100644 index 000000000..b75ecbc88 --- /dev/null +++ b/Signal/src/audio/incall_audio/protocols/utilities/AnonymousAudioCallbackHandler.m @@ -0,0 +1,21 @@ +#import "AnonymousAudioCallbackHandler.h" + +@implementation AnonymousAudioCallbackHandler + ++(AnonymousAudioCallbackHandler*) anonymousAudioInterfaceDelegateWithRecordingCallback:(void(^)(CyclicalBuffer* data))recordingCallback + andPlaybackOccurredCallback:(void(^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback { + AnonymousAudioCallbackHandler* a = [AnonymousAudioCallbackHandler new]; + a->_handleNewDataRecordedBlock = recordingCallback; + a->_handlePlaybackOccurredWithBytesRequestedBlock = playbackCallback; + return a; +} +-(void) handleNewDataRecorded:(CyclicalBuffer*)data { + if (_handleNewDataRecordedBlock != nil) + _handleNewDataRecordedBlock(data); +} +-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining { + if (_handlePlaybackOccurredWithBytesRequestedBlock != nil) + _handlePlaybackOccurredWithBytesRequestedBlock(requested, bytesRemaining); +} + +@end diff --git a/Signal/src/call/RecentCall.h b/Signal/src/call/RecentCall.h new file mode 100644 index 000000000..ce5704839 --- /dev/null +++ b/Signal/src/call/RecentCall.h @@ -0,0 +1,38 @@ +#import +#import +#import "PhoneNumber.h" +#import "ContactsManager.h" +#import "Contact.h" + +/** + * + * RecentCall is used for storing a call history with information about + * who (person), what (phone number), when (date) and why (type) + * The object is serialized in a list and managed by RecentCallManager. + * + */ + +typedef enum { + RPRecentCallTypeIncoming = 1, + RPRecentCallTypeOutgoing, + RPRecentCallTypeMissed, +} RPRecentCallType; + +extern NSString *const CALL_TYPE_IMAGE_NAME_INCOMING; +extern NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING; + +@interface RecentCall : NSObject + +@property (nonatomic, readonly) ABRecordID contactRecordID; +@property (nonatomic, readonly) PhoneNumber *phoneNumber; +@property (nonatomic, readonly) RPRecentCallType callType; +@property (nonatomic, readonly) NSDate *date; +@property (nonatomic) BOOL isArchived; +@property (nonatomic) BOOL userNotified; + ++ (RecentCall *)recentCallWithContactID:(ABRecordID)contactID + andNumber:(PhoneNumber *)number + andCallType:(RPRecentCallType)type; + +-(void)updateRecentCallWithContactId:(ABRecordID) contactID; +@end diff --git a/Signal/src/call/RecentCall.m b/Signal/src/call/RecentCall.m new file mode 100644 index 000000000..871bd7991 --- /dev/null +++ b/Signal/src/call/RecentCall.m @@ -0,0 +1,59 @@ +#import "RecentCall.h" +#import "Environment.h" +#import "PreferencesUtil.h" +#import "Util.h" + +static NSString *const DEFAULTS_KEY_CONTACT_ID = @"DefaultsKeyContactID"; +static NSString *const DEFAULTS_KEY_PHONE_NUMBER = @"DefaultsKeyPhoneNumber"; +static NSString *const DEFAULTS_KEY_CALL_TYPE = @"DefaultsKeycallType"; +static NSString *const DEFAULTS_KEY_DATE = @"DefaultsKeyDate"; +static NSString *const DEFAULTS_KEY_IS_ARCHIVED = @"DefaultsKeyDateIsArchived"; +static NSString *const DEFAULTS_KEY_USER_NOTIFIED = @"DefaultsKeyUserNotified"; + +NSString *const CALL_TYPE_IMAGE_NAME_INCOMING = @"incoming_call_icon"; +NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING = @"outgoing_call_icon"; + +@implementation RecentCall + +@synthesize contactRecordID, callType, date, phoneNumber, isArchived, userNotified; + ++ (RecentCall *)recentCallWithContactID:(ABRecordID)contactID + andNumber:(PhoneNumber *)number + andCallType:(RPRecentCallType)type { + + RecentCall *recentCall = [RecentCall new]; + recentCall->contactRecordID = contactID; + recentCall->callType = type; + recentCall->date = [NSDate date]; + recentCall->phoneNumber = number; + recentCall->userNotified = RPRecentCallTypeMissed ? false : true; + return recentCall; +} +-(void)updateRecentCallWithContactId:(ABRecordID)contactID{ + contactRecordID = contactID; +} + +#pragma mark - Serialization + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:[NSNumber numberWithInt:callType] forKey:DEFAULTS_KEY_CALL_TYPE]; + [encoder encodeObject:phoneNumber forKey:DEFAULTS_KEY_PHONE_NUMBER]; + [encoder encodeObject:[NSNumber numberWithInt:(int)contactRecordID] forKey:DEFAULTS_KEY_CONTACT_ID]; + [encoder encodeObject:date forKey:DEFAULTS_KEY_DATE]; + [encoder encodeBool:isArchived forKey:DEFAULTS_KEY_IS_ARCHIVED]; + [encoder encodeBool:userNotified forKey:DEFAULTS_KEY_USER_NOTIFIED]; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if((self = [super init])) { + callType = (RPRecentCallType)[[decoder decodeObjectForKey:DEFAULTS_KEY_CALL_TYPE] intValue]; + contactRecordID = [[decoder decodeObjectForKey:DEFAULTS_KEY_CONTACT_ID] intValue]; + phoneNumber = [decoder decodeObjectForKey:DEFAULTS_KEY_PHONE_NUMBER]; + date = [decoder decodeObjectForKey:DEFAULTS_KEY_DATE]; + isArchived = [decoder decodeBoolForKey:DEFAULTS_KEY_IS_ARCHIVED]; + userNotified = [decoder decodeBoolForKey:DEFAULTS_KEY_USER_NOTIFIED]; + } + return self; +} + +@end diff --git a/Signal/src/call/RecentCallManager.h b/Signal/src/call/RecentCallManager.h new file mode 100644 index 000000000..f73834fe4 --- /dev/null +++ b/Signal/src/call/RecentCallManager.h @@ -0,0 +1,34 @@ +#import +#import "RecentCall.h" +#import "PhoneManager.h" + +/** + * + * RecentCallManager is used for adding and reading RecentCall objects from NSUserDefaults. + * Recent Call objects that are non-archived are to be displayed in the Inbox and persist in RecentCallViewController. + * This class also contains an observable value that can be subscribed to which gives updates. + * + */ + +@interface RecentCallManager : NSObject { +@private ObservableValueController* observableRecentsController; +} + +- (ObservableValue *)getObservableRecentCalls; +- (void)watchForCallsThrough:(PhoneManager*)phoneManager + untilCancelled:(id)untilCancelledToken; +- (void)watchForContactUpdatesFrom:(ContactsManager*) contactManager + untillCancelled:(id) cancelToken; + +- (void)addRecentCall:(RecentCall *)recentCall; +- (void)removeRecentCall:(RecentCall *)recentCall; +- (void)archiveRecentCall:(RecentCall *)recentCall; +- (void)clearRecentCalls; +- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor*)incomingCallDescriptor; +- (NSArray *)recentsForSearchString:(NSString *)optionalSearchString + andExcludeArchived:(BOOL)excludeArchived; +- (void)saveContactsToDefaults; +- (BOOL)isPhoneNumberPresentInRecentCalls:(PhoneNumber*) phoneNumber; +- (NSUInteger)missedCallCount; + +@end diff --git a/Signal/src/call/RecentCallManager.m b/Signal/src/call/RecentCallManager.m new file mode 100644 index 000000000..69ab61882 --- /dev/null +++ b/Signal/src/call/RecentCallManager.m @@ -0,0 +1,186 @@ +#import "RecentCallManager.h" +#import "ContactsManager.h" +#import "FunctionalUtil.h" +#import "ObservableValue.h" +#import "PreferencesUtil.h" + + +#define RECENT_CALLS_DEFAULT_KEY @"RPRecentCallsDefaultKey" + +typedef BOOL (^SearchTermConditionalBlock)(RecentCall*, NSUInteger, BOOL*); + +@interface RecentCallManager () { + NSMutableArray *_allRecents; +} + +@end + +@implementation RecentCallManager + +- (id)init { + if (self = [super init]) { + [self initRecentCallsObservable]; + } + return self; +} + +-(void) initRecentCallsObservable { + _allRecents = [self loadContactsFromDefaults]; + observableRecentsController = [ObservableValueController observableValueControllerWithInitialValue:_allRecents]; +} + +- (ObservableValue *)getObservableRecentCalls { + return observableRecentsController; +} + +-(void) watchForContactUpdatesFrom:(ContactsManager*) contactManager untillCancelled:(id) cancelToken{ + [[contactManager getObservableWhisperUsers] watchLatestValue:^(NSArray* latestUsers) { + for (RecentCall* recentCall in _allRecents) { + if (![contactManager latestContactWithRecordId:recentCall.contactRecordID]) { + Contact* contact = [contactManager latestContactForPhoneNumber:recentCall.phoneNumber]; + if(contact){ + [self updateRecentCall:recentCall withContactId:contact.recordID]; + } + } + } + } onThread:[NSThread mainThread] untilCancelled:cancelToken]; +} + +-(void) watchForCallsThrough:(PhoneManager*)phoneManager + untilCancelled:(id)untilCancelledToken { + require(phoneManager != nil); + + [[phoneManager currentCallObservable] watchLatestValue:^(CallState* latestCall) { + if (latestCall != nil && [[[Environment getCurrent] preferences] getHistoryLogEnabled]) { + [self addCall:latestCall]; + } + } onThread:[NSThread mainThread] untilCancelled:untilCancelledToken]; +} + +-(void) addCall:(CallState*)call { + require(call != nil); + + [call.futureCallLocallyAcceptedOrRejected finallyDo:^(Future* interactionCompletion) { + bool isOutgoingCall = call.initiatedLocally; + bool isMissedCall = [interactionCompletion hasFailed]; + Contact* contact = [self tryGetContactForCall:call]; + + RPRecentCallType callType = isOutgoingCall ? RPRecentCallTypeOutgoing + : isMissedCall ? RPRecentCallTypeMissed + : RPRecentCallTypeIncoming; + + [self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID + andNumber:call.remoteNumber + andCallType:callType]]; + }]; +} + +-(Contact*) tryGetContactForCall:(CallState*)call { + if (call.potentiallySpecifiedContact != nil) return call.potentiallySpecifiedContact; + return [self tryGetContactForNumber:call.remoteNumber]; +} + +-(Contact*) tryGetContactForNumber:(PhoneNumber*)number { + return [[[Environment getCurrent] contactsManager] latestContactForPhoneNumber:number]; +} + +- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor*)incomingCallDescriptor { + require(incomingCallDescriptor != nil); + + Contact* contact = [self tryGetContactForNumber:incomingCallDescriptor.initiatorNumber]; + [self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID + andNumber:incomingCallDescriptor.initiatorNumber + andCallType:RPRecentCallTypeMissed]]; +} + +-(void) updateRecentCall:(RecentCall*) recentCall withContactId:(ABRecordID) contactId { + [recentCall updateRecentCallWithContactId:contactId]; + [observableRecentsController updateValue:[_allRecents copy]]; + [self saveContactsToDefaults]; +} + +- (void)addRecentCall:(RecentCall *)recentCall { + [_allRecents insertObject:recentCall atIndex:0]; + [[[Environment getCurrent] preferences] setFreshInstallTutorialsEnabled:NO]; + [observableRecentsController updateValue:[_allRecents copy]]; + [self saveContactsToDefaults]; +} + +- (void)removeRecentCall:(RecentCall *)recentCall { + [_allRecents removeObject:recentCall]; + [observableRecentsController updateValue:[_allRecents copy]]; + [self saveContactsToDefaults]; +} + +- (void)archiveRecentCall:(RecentCall *)recentCall { + NSUInteger indexOfRecent = [_allRecents indexOfObject:recentCall]; + recentCall.isArchived = YES; + [_allRecents replaceObjectAtIndex:indexOfRecent withObject:recentCall]; + [self saveContactsToDefaults]; + [observableRecentsController updateValue:[_allRecents copy]]; +} + +- (void)clearRecentCalls { + [_allRecents removeAllObjects]; + [observableRecentsController updateValue:[_allRecents copy]]; + [self saveContactsToDefaults]; +} + +- (void)saveContactsToDefaults { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:[_allRecents copy]]; + + [defaults setObject:saveData forKey:RECENT_CALLS_DEFAULT_KEY]; + [defaults synchronize]; +} + +- (NSMutableArray *)loadContactsFromDefaults { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *encodedData = [defaults objectForKey:RECENT_CALLS_DEFAULT_KEY]; + id data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedData]; + + if(![data isKindOfClass:[NSArray class]]) { + return [NSMutableArray array]; + } else { + return [NSMutableArray arrayWithArray:data]; + } +} + +- (NSArray *)recentsForSearchString:(NSString *)optionalSearchString andExcludeArchived:(BOOL)excludeArchived { + ContactsManager *contactsManager = [[Environment getCurrent] contactsManager]; + SearchTermConditionalBlock searchBlock = ^BOOL(RecentCall *obj, NSUInteger idx, BOOL *stop) { + BOOL nameMatchesSearch = YES; + BOOL numberMatchesSearch = YES; + + if (optionalSearchString) { + NSString *contactName = [contactsManager latestContactWithRecordId:obj.contactRecordID].fullName; + nameMatchesSearch = [ContactsManager name:contactName matchesQuery:optionalSearchString]; + numberMatchesSearch = [ContactsManager phoneNumber:obj.phoneNumber matchesQuery:optionalSearchString]; + } + + if (excludeArchived) { + return !obj.isArchived && (nameMatchesSearch || numberMatchesSearch); + } else { + return (nameMatchesSearch || numberMatchesSearch); + } + }; + + NSIndexSet *newsFeedIndexes = [_allRecents indexesOfObjectsPassingTest:searchBlock]; + return [_allRecents objectsAtIndexes:newsFeedIndexes]; +} + +- (NSUInteger)missedCallCount { + SearchTermConditionalBlock missedCallBlock = ^BOOL(RecentCall *recentCall, NSUInteger idx, BOOL *stop) { + return !recentCall.userNotified; + }; + + return [[_allRecents indexesOfObjectsPassingTest:missedCallBlock] count]; +} + +-(BOOL) isPhoneNumberPresentInRecentCalls:(PhoneNumber*) phoneNumber { + return [_allRecents any:^int(RecentCall* call) { + return [call.phoneNumber resolvesInternationallyTo:phoneNumber]; + }]; +} + +@end diff --git a/Signal/src/contact/Contact.h b/Signal/src/contact/Contact.h new file mode 100644 index 000000000..886633d3a --- /dev/null +++ b/Signal/src/contact/Contact.h @@ -0,0 +1,40 @@ +#import +#import +#import "PhoneNumber.h" + +/** + * + * Contact represents relevant information related to a contact from the user's contact list. + * + */ + +@interface Contact : NSObject + +@property (readonly,nonatomic) NSString* firstName; +@property (readonly,nonatomic) NSString* lastName; +@property (readonly,nonatomic) NSArray* parsedPhoneNumbers; +@property (readonly,nonatomic) NSArray* userTextPhoneNumbers; +@property (readonly,nonatomic) NSArray* emails; +@property (readonly,nonatomic) UIImage* image; +@property (readonly,nonatomic) NSString *notes; +@property (readonly,nonatomic) ABRecordID recordID; +@property (nonatomic, assign) BOOL isFavourite; + ++ (Contact*)contactWithFirstName:(NSString*)firstName + andLastName:(NSString *)lastName + andUserTextPhoneNumbers:(NSArray*)phoneNumbers + andEmails:(NSArray*)emails + andContactID:(ABRecordID)record; + ++ (Contact*)contactWithFirstName:(NSString*)firstName + andLastName:(NSString *)lastName + andUserTextPhoneNumbers:(NSArray*)numbers + andEmails:(NSArray*)emails + andImage:(UIImage *)image + andContactID:(ABRecordID)record + andIsFavourite:(BOOL)isFavourite + andNotes:(NSString *)notes; + +- (NSString *)fullName; + +@end diff --git a/Signal/src/contact/Contact.m b/Signal/src/contact/Contact.m new file mode 100644 index 000000000..db1b9b441 --- /dev/null +++ b/Signal/src/contact/Contact.m @@ -0,0 +1,84 @@ +#import "Contact.h" +#import "Util.h" +#import "Environment.h" +#import "PreferencesUtil.h" + +static NSString *const DEFAULTS_KEY_CONTACT = @"DefaultsKeyContact"; +static NSString *const DEFAULTS_KEY_PHONE_NUMBER = @"DefaultsKeyPhoneNumber"; +static NSString *const DEFAULTS_KEY_CALL_TYPE = @"DefaultsKeycallType"; +static NSString *const DEFAULTS_KEY_DATE = @"DefaultsKeyDate"; + +@implementation Contact + +@synthesize firstName, lastName, emails, image, recordID, isFavourite, notes, parsedPhoneNumbers, userTextPhoneNumbers; + ++ (Contact*)contactWithFirstName:(NSString*)firstName + andLastName:(NSString *)lastName + andUserTextPhoneNumbers:(NSArray*)phoneNumbers + andEmails:(NSArray*)emails + andContactID:(ABRecordID)record { + + Contact* contact = [Contact new]; + contact->firstName = firstName; + contact->lastName = lastName; + contact->userTextPhoneNumbers = phoneNumbers; + contact->emails = emails; + contact->recordID = record; + + NSMutableArray *parsedPhoneNumbers = [NSMutableArray array]; + + for (NSString *phoneNumberString in phoneNumbers) { + PhoneNumber *phoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumberString]; + if (phoneNumber) { + [parsedPhoneNumbers addObject:phoneNumber]; + } + } + + contact->parsedPhoneNumbers = [parsedPhoneNumbers copy]; + + return contact; +} + ++ (Contact*)contactWithFirstName:(NSString*)firstName + andLastName:(NSString *)lastName + andUserTextPhoneNumbers:(NSArray*)numbers + andEmails:(NSArray*)emails + andImage:(UIImage *)image + andContactID:(ABRecordID)record + andIsFavourite:(BOOL)isFavourite + andNotes:(NSString *)notes { + + Contact* contact = [Contact contactWithFirstName:firstName + andLastName:lastName + andUserTextPhoneNumbers:numbers + andEmails:emails + andContactID:record]; + + contact->isFavourite = isFavourite; + contact->image = image; + contact->notes = notes; + return contact; +} + +- (NSString *)fullName { + NSMutableString *fullName = [NSMutableString string]; + if (firstName) [fullName appendString:firstName]; + if (lastName) { + [fullName appendString:[NSString stringWithFormat:@" %@",lastName]]; + } + return fullName; +} + +-(NSString *)description { + return [NSString stringWithFormat:@"%@ %@: %@", firstName, lastName, userTextPhoneNumbers]; +} + +- (UIImage *)image { + if ([[[Environment getCurrent] preferences] getContactImagesEnabled]) { + return image; + } else { + return nil; + } +} + +@end diff --git a/Signal/src/contact/ContactsManager.h b/Signal/src/contact/ContactsManager.h new file mode 100644 index 000000000..87550d608 --- /dev/null +++ b/Signal/src/contact/ContactsManager.h @@ -0,0 +1,57 @@ +#import +#import "Contact.h" +#import "Future.h" +#import "ObservableValue.h" +#import "CancelTokenSource.h" + +/** + * + * ContactsManager provides access to an updated list of contacts with optional categorizations - + * such as searching and favourite-attributed contacts (favourites are also managed through this class) + * Others can subscribe for contact and/or favourite updates + * Contacts can be grouped by first letter into an NSDictionary in order to display in individual sections- + * in the ContactBrowseViewController. + * + */ + +typedef void(^ABAccessRequestCompletionBlock)(BOOL hasAccess); +typedef void(^ABReloadRequestCompletionBlock)(NSArray *contacts); + +@interface ContactsManager : NSObject { +@private Future* futureAddressBook; +@private ObservableValueController* observableContactsController; +@private ObservableValueController* observableWhisperUsersController; +@private ObservableValueController* observableFavouritesController; +@private CancelTokenSource* life; +@private NSDictionary *latestContactsById; +@private NSDictionary *latestWhisperUsersById; +} + +-(ObservableValue *) getObservableContacts; +-(ObservableValue *) getObservableWhisperUsers; +-(ObservableValue *) getObservableFavourites; + +-(NSArray*) getContactsFromAddressBook:(ABAddressBookRef)addressBook; +-(Contact*) latestContactWithRecordId:(ABRecordID)recordId; +-(Contact*) latestContactForPhoneNumber:(PhoneNumber *)phoneNumber; +-(NSArray*) latestContactsWithSearchString:(NSString *)searchString; + +-(void) toggleFavourite:(Contact *)contact; +-(NSArray*) contactsForContactIds:(NSArray *)favouriteIds; ++(NSArray *)favouritesForAllContacts:(NSArray *)contacts; + +-(void) addContactsToKnownWhisperUsers:(NSArray*) contacts; + ++(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString; + ++(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString; ++(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString; +-(BOOL)isContactRegisteredWithWhisper:(Contact*) contact; + +-(void) doAfterEnvironmentInitSetup; + +-(void) enableNewUserNotifications; +-(NSUInteger) getNumberOfUnacknowledgedCurrentUsers; + + +@end diff --git a/Signal/src/contact/ContactsManager.m b/Signal/src/contact/ContactsManager.m new file mode 100644 index 000000000..59bb0b115 --- /dev/null +++ b/Signal/src/contact/ContactsManager.m @@ -0,0 +1,521 @@ +#import "ContactsManager.h" +#import "FutureSource.h" +#import "FutureUtil.h" +#import +#import "Constraints.h" +#import "Environment.h" +#import "NotificationManifest.h" +#import "PhoneNumberDirectoryFilter.h" +#import "PhoneNumberDirectoryFilterManager.h" +#import "PreferencesUtil.h" +#import "Util.h" + + +//@TODO: Split Up into Seperate components. + +#define ADDRESSBOOK_QUEUE dispatch_get_main_queue() + +static NSString *const FAVOURITES_DEFAULT_KEY = @"FAVOURITES_DEFAULT_KEY"; +static NSString *const KNOWN_USERS_DEFAULT_KEY = @"KNOWN_USERS_DEFAULT_KEY"; + +typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*); + +@interface ContactsManager () { + NSMutableArray *_favouriteContactIds; + NSMutableArray *_knownWhisperUserIds; + BOOL newUserNotificationsEnabled; + + id addressBookReference; +} + +@end + +@implementation ContactsManager + +- (id)init { + self = [super init]; + if (self) { + newUserNotificationsEnabled = [self knownUserStoreInitialized]; + _favouriteContactIds = [self loadFavouriteIds]; + _knownWhisperUserIds = [self loadKnownWhisperUsers]; + life = [CancelTokenSource cancelTokenSource]; + observableContactsController = [ObservableValueController observableValueControllerWithInitialValue:nil]; + observableWhisperUsersController = [ObservableValueController observableValueControllerWithInitialValue:nil]; + [self registerNotificationHandlers]; + } + return self; +} +-(void) doAfterEnvironmentInitSetup { + [self setupAddressBook]; + [observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) { + @synchronized(self) { + [self setupLatestContacts:latestContacts]; + } + } untilCancelled:[life getToken]]; + + [observableWhisperUsersController watchLatestValueOnArbitraryThread:^(NSArray *latestUsers) { + @synchronized(self) { + [self setupLatestWhisperUsers:latestUsers]; + } + } untilCancelled:[life getToken]]; +} + +-(void)dealloc { + [life cancel]; +} + +#pragma mark - Notification Handlers +-(void) registerNotificationHandlers{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatedDirectoryHandler:) name:NOTIFICATION_DIRECTORY_UPDATE object:nil]; +} + +-(void) updatedDirectoryHandler:(NSNotification*) notification { + [self checkForNewWhisperUsers]; + } + +-(void) enableNewUserNotifications{ + newUserNotificationsEnabled = YES; +} + +#pragma mark - Address Book callbacks + +void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef info, void *context); +void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef info, void *context) { + ContactsManager* contactsManager = (__bridge ContactsManager*)context; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [contactsManager pullLatestAddressBook]; + + }); +} + +#pragma mark - Setup + +-(void) setupAddressBook { + dispatch_async(ADDRESSBOOK_QUEUE, ^{ + [[ContactsManager asyncGetAddressBook] thenDo:^(id addressBook) { + addressBookReference = addressBook; + ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook; + ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void*)self); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{ + [self pullLatestAddressBook]; + }); + }]; + }); +} + +-(void) pullLatestAddressBook{ + CFErrorRef creationError = nil; + ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); + checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError) localizedDescription]) ; + ABAddressBookRequestAccessWithCompletion(addressBookRef, nil); + [observableContactsController updateValue:[self getContactsFromAddressBook:addressBookRef]]; +} + +- (void)setupLatestContacts:(NSArray *)contacts { + if (contacts) { + latestContactsById = [ContactsManager keyContactsById:contacts]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self checkForNewWhisperUsers]; + }); + } +} + +- (void)setupLatestWhisperUsers:(NSArray *)users { + if (users) { + latestWhisperUsersById = [ContactsManager keyContactsById:users]; + + if (!observableFavouritesController) { + NSArray *favourites = [self contactsForContactIds:_favouriteContactIds]; + observableFavouritesController = [ObservableValueController observableValueControllerWithInitialValue:favourites]; + } + + } +} + +#pragma mark - Observables + +-(ObservableValue *) getObservableContacts { + return observableContactsController; +} + +-(ObservableValue *) getObservableWhisperUsers { + return observableWhisperUsersController; +} + +-(ObservableValue *) getObservableFavourites { + return observableFavouritesController; +} + +#pragma mark - Address Book utils + ++(Future*) asyncGetAddressBook { + CFErrorRef creationError = nil; + ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError); + assert((addressBookRef == nil) == (creationError != nil)); + if (creationError != nil) { + return [Future failed:(__bridge_transfer id)creationError]; + } + + FutureSource *futureAddressBookSource = [FutureSource new]; + + id addressBook = (__bridge_transfer id)addressBookRef; + ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef requestAccessError) { + if (granted) { + dispatch_async(ADDRESSBOOK_QUEUE,^{ + [futureAddressBookSource trySetResult:addressBook]; + }); + } else { + [futureAddressBookSource trySetFailure:(__bridge id)requestAccessError]; + } + }); + + return futureAddressBookSource; +} + +-(NSArray*) getContactsFromAddressBook:(ABAddressBookRef)addressBook { + ABRecordRef source = ABAddressBookCopyDefaultSource(addressBook); + + NSArray *allPeople = (__bridge_transfer NSArray *) + (ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBook, + source, + kABPersonSortByFirstName)); + + return [allPeople map:^id(id item) { + return [self contactForRecord:(__bridge ABRecordRef)item]; + }]; +} + +-(NSArray*)latestContactsWithSearchString:(NSString *)searchString { + return [[latestContactsById allValues] filter:^int(Contact *contact) { + return [searchString length] == 0 || [ContactsManager name:[contact fullName] matchesQuery:searchString]; + }]; +} + +#pragma mark - Contact/Phone Number util + +- (Contact *)contactForRecord:(ABRecordRef)record { + ABRecordID recordID = ABRecordGetRecordID(record); + + NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty); + NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty); + NSArray *phoneNumbers = [self phoneNumbersForRecord:record]; + + if (!firstName && !lastName) { + NSString *companyName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonOrganizationProperty); + if (companyName) { + firstName = companyName; + } else if ([phoneNumbers count]) { + firstName = [phoneNumbers firstObject]; + } + } + + NSString *notes = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonNoteProperty); + NSArray *emails = [ContactsManager emailsForRecord:record]; + NSData *image = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail); + UIImage *img = [UIImage imageWithData:image]; + + ContactSearchBlock searchBlock = ^BOOL(NSNumber *obj, NSUInteger idx, BOOL *stop) { + return [obj intValue] == recordID; + }; + + NSUInteger favouriteIndex = [_favouriteContactIds indexOfObjectPassingTest:searchBlock]; + + return [Contact contactWithFirstName:firstName + andLastName:lastName + andUserTextPhoneNumbers:phoneNumbers + andEmails:emails + andImage:img + andContactID:recordID + andIsFavourite:favouriteIndex != NSNotFound + andNotes:notes]; +} + +-(Contact*)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber { + NSArray *allContacts = [latestContactsById allValues]; + + ContactSearchBlock searchBlock = ^BOOL(Contact *contact, NSUInteger idx, BOOL *stop) { + for (PhoneNumber *number in contact.parsedPhoneNumbers) { + + if ([self phoneNumber:number matchesNumber:phoneNumber]) { + *stop = YES; + return YES; + } + } + return NO; + }; + + NSUInteger contactIndex = [allContacts indexOfObjectPassingTest:searchBlock]; + + if (contactIndex != NSNotFound) { + return allContacts[contactIndex]; + } else { + return nil; + } +} + +- (BOOL)phoneNumber:(PhoneNumber *)phoneNumber1 matchesNumber:(PhoneNumber *)phoneNumber2 { + return [[phoneNumber1 toE164] isEqualToString:[phoneNumber2 toE164]]; +} + +- (NSArray *)phoneNumbersForRecord:(ABRecordRef)record { + ABMultiValueRef numberRefs = ABRecordCopyValue(record, kABPersonPhoneProperty); + + @try { + NSArray *phoneNumbers = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(numberRefs); + + if (phoneNumbers == nil) phoneNumbers = @[]; + + NSMutableArray *numbers = [NSMutableArray array]; + + for (CFIndex i = 0; i < (NSInteger)[phoneNumbers count]; i++) { + NSString *phoneNumber = phoneNumbers[(NSUInteger)i]; + [numbers addObject:phoneNumber]; + } + + return numbers; + + } @finally { + if (numberRefs) { + CFRelease(numberRefs); + } + } +} + ++(NSArray *)emailsForRecord:(ABRecordRef)record { + ABMultiValueRef emailRefs = ABRecordCopyValue(record, kABPersonEmailProperty); + + @try { + NSArray *emails = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(emailRefs); + + if (emails == nil) emails = @[]; + + return emails; + + } @finally { + if (emailRefs) { + CFRelease(emailRefs); + } + } +} + ++(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString { + require(contacts != nil); + + NSArray *matchingContacts = [contacts filter:^int(Contact *contact) { + return [optionalSearchString length] == 0 || [self name:[contact fullName] matchesQuery:optionalSearchString]; + }]; + + return [matchingContacts groupBy:^id(Contact *contact) { + NSString *nameToUse = @""; + + BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO; + + if (firstNameOrdering && [contact firstName] != nil && [[contact firstName] length] > 0) { + nameToUse = [contact firstName]; + } else if (!firstNameOrdering && [contact lastName] != nil && [[contact lastName] length] > 0){ + nameToUse = [contact lastName]; + } else if ([contact lastName] == nil) { + if ([[contact fullName] length] > 0) { + nameToUse = [contact fullName]; + } else { + return nameToUse; + } + } else { + nameToUse = [contact lastName]; + } + + return [[[nameToUse substringToIndex:1] uppercaseString] decomposedStringWithCompatibilityMapping]; + }]; +} + ++(NSDictionary *)keyContactsById:(NSArray *)contacts { + return [contacts keyedBy:^id(Contact* contact) { + return [NSNumber numberWithInt:(int)contact.recordID]; + }]; +} + +-(Contact *)latestContactWithRecordId:(ABRecordID)recordId { + @synchronized(self) { + return [latestContactsById objectForKey:[NSNumber numberWithInt:recordId]]; + } +} + +-(NSArray*) recordsForContacts:(NSArray*) contacts{ + return [contacts map:^id(Contact *contact) { + return [NSNumber numberWithInt:[contact recordID]]; + }]; +} + ++(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString { + NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet]; + NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet]; + NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet]; + + return [queryStrings all:^int(NSString* query) { + if ([query length] == 0) return YES; + return [nameStrings any:^int(NSString* nameWord) { + NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch; + return [nameWord rangeOfString:query options:searchOpts].location != NSNotFound; + }]; + }]; +} + ++(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString { + NSString *phoneNumberString = [phoneNumber localizedDescriptionForUser]; + NSString *searchString = [[phoneNumberString componentsSeparatedByCharactersInSet: + [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] + componentsJoinedByString:@""]; + + if ([queryString length] == 0) return YES; + NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch; + return [searchString rangeOfString:queryString options:searchOpts].location != NSNotFound; +} + +-(NSArray*) contactsForContactIds:(NSArray *)contactIds { + NSMutableArray *contacts = [NSMutableArray array]; + for (NSNumber *favouriteId in contactIds) { + Contact *contact = [self latestContactWithRecordId:[favouriteId integerValue]]; + + if (contact) { + [contacts addObject:contact]; + } + } + return [contacts copy]; +} + +#pragma mark - Favourites + +-(NSMutableArray *)loadFavouriteIds { + NSArray *favourites = [[NSUserDefaults standardUserDefaults] objectForKey:FAVOURITES_DEFAULT_KEY]; + return favourites == nil ? [NSMutableArray array] : [favourites mutableCopy]; +} + +-(void)saveFavouriteIds { + [[NSUserDefaults standardUserDefaults] setObject:[_favouriteContactIds copy] + forKey:FAVOURITES_DEFAULT_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [observableFavouritesController updateValue:[self contactsForContactIds:_favouriteContactIds]]; +} + +-(void)toggleFavourite:(Contact *)contact { + require(contact != nil); + + contact.isFavourite = !contact.isFavourite; + if (contact.isFavourite) { + [_favouriteContactIds addObject:[NSNumber numberWithInt:contact.recordID]]; + } else { + + ContactSearchBlock removeBlock = ^BOOL(NSNumber *favouriteNumber, NSUInteger idx, BOOL *stop) { + return [favouriteNumber integerValue] == contact.recordID; + }; + + NSUInteger indexToRemove = [_favouriteContactIds indexOfObjectPassingTest:removeBlock]; + + if (indexToRemove != NSNotFound) { + [_favouriteContactIds removeObjectAtIndex:indexToRemove]; + } + } + [self saveFavouriteIds]; +} + ++(NSArray *)favouritesForAllContacts:(NSArray *)contacts { + return [contacts filter:^int(Contact* contact) { + return [contact isFavourite]; + }]; +} + +#pragma mark - Whisper User Management + +-(unsigned int) checkForNewWhisperUsers { + NSArray *currentUsers = [self getWhisperUsersFromContactsArray:[latestContactsById allValues]]; + NSArray *newUsers = [self getNewItemsFrom:currentUsers comparedTo:[latestWhisperUsersById allValues]]; + + if([newUsers count] > 0){ + [observableWhisperUsersController updateValue:currentUsers]; + } + + NSArray *unacknowledgedUserIds = [self getUnacknowledgedUsersFrom:currentUsers]; + if([unacknowledgedUserIds count] > 0){ + NSArray *unacknowledgedUsers = [self contactsForContactIds: unacknowledgedUserIds]; + if(!newUserNotificationsEnabled){ + [self addContactsToKnownWhisperUsers:unacknowledgedUsers]; + }else{ + NSDictionary *payload = [NSDictionary dictionaryWithObject:unacknowledgedUsers forKey:NOTIFICATION_DATAKEY_NEW_USERS]; + [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_NEW_USERS_AVAILABLE object:self userInfo:payload]; + } + } + return [newUsers count]; +} + +-(NSArray*) getUnacknowledgedUsersFrom:(NSArray*) users { + NSArray *userIds = [self recordsForContacts:users]; + return [self getNewItemsFrom:userIds comparedTo:_knownWhisperUserIds ]; +} + +-(NSUInteger) getNumberOfUnacknowledgedCurrentUsers{ + NSArray *currentUsers = [self getWhisperUsersFromContactsArray:[latestContactsById allValues]]; + return [[self getUnacknowledgedUsersFrom:currentUsers] count]; +} + +-(NSArray*) getWhisperUsersFromContactsArray:(NSArray*) contacts { + return [contacts filter:^int(Contact* contact) { + return [self isContactRegisteredWithWhisper:contact]; + }]; +} + +-(NSArray*) getNewItemsFrom:(NSArray*) newArray comparedTo:(NSArray*) oldArray { + NSMutableSet *newSet = [NSMutableSet setWithArray:newArray]; + NSSet *oldSet = [NSSet setWithArray:oldArray]; + + [newSet minusSet:oldSet]; + return [newSet allObjects]; +} + +- (BOOL)isContactRegisteredWithWhisper:(Contact*) contact { + for(PhoneNumber *phoneNumber in contact.parsedPhoneNumbers){ + if ( [self isPhoneNumberRegisteredWithWhisper:phoneNumber]) { + return YES; + } + } + return NO; +} + +- (BOOL)isPhoneNumberRegisteredWithWhisper:(PhoneNumber *)phoneNumber { + PhoneNumberDirectoryFilter* directory = [[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter]; + return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber]; +} + +-(void) addContactsToKnownWhisperUsers:(NSArray*) contacts { + for( Contact *contact in contacts){ + [_knownWhisperUserIds addObject:@([contact recordID])]; + } + NSMutableSet *users = [NSMutableSet setWithArray:[latestWhisperUsersById allValues]]; + [users addObjectsFromArray:contacts]; + + [observableWhisperUsersController updateValue:[users allObjects]]; + [self saveKnownWhisperUsers]; +} + +-(BOOL) knownUserStoreInitialized{ + NSUserDefaults *d = [[NSUserDefaults standardUserDefaults] objectForKey:KNOWN_USERS_DEFAULT_KEY]; + return (Nil != d); +} + +-(NSMutableArray*) loadKnownWhisperUsers{ + NSArray *knownUsers = [[NSUserDefaults standardUserDefaults] objectForKey:KNOWN_USERS_DEFAULT_KEY]; + return knownUsers == nil ? [NSMutableArray array] : [knownUsers mutableCopy]; +} + +-(void) saveKnownWhisperUsers{ + _knownWhisperUserIds = [NSMutableArray arrayWithArray:[latestWhisperUsersById allKeys]]; + [[NSUserDefaults standardUserDefaults] setObject:[_knownWhisperUserIds copy] forKey:KNOWN_USERS_DEFAULT_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +-(void) clearKnownWhisUsers{ + [[NSUserDefaults standardUserDefaults] setObject:[NSArray array] forKey:KNOWN_USERS_DEFAULT_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +@end diff --git a/Signal/src/crypto/CryptoTools.h b/Signal/src/crypto/CryptoTools.h new file mode 100644 index 000000000..d89e7a1df --- /dev/null +++ b/Signal/src/crypto/CryptoTools.h @@ -0,0 +1,40 @@ + + +/// All dependencies on external libraries used for cryptography should be hidden behind CryptoTools methods. +/// That way, changing to a different library affects only one part of the system. + +@interface CryptoTools : NSObject + +/// Returns a secure random 16-bit unsigned integer. ++(uint16_t)generateSecureRandomUInt16; + +/// Returns data composed of 'length' cryptographically unpredictable bytes sampled uniformly from [0, 256). ++(NSData*)generateSecureRandomData:(NSUInteger)length; + +/// Returns the token included as part of HTTP OTP authentication. ++(NSString*) computeOtpWithPassword:(NSString*)password andCounter:(int64_t)counter; + +@end + +@interface NSData (CryptoUtil) + +-(NSData*)hashWithSha256; + +-(NSData*)hmacWithSha1WithKey:(NSData*)key; +-(NSData*)hmacWithSha256WithKey:(NSData*)key; + +-(NSData*)encryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv; +-(NSData*)decryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv; + +-(NSData*)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv; +-(NSData*)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv; + +-(NSData*)encryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv; +-(NSData*)decryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv; + +/// Determines if two data vectors contain the same information. +/// Avoids short-circuiting or data-dependent branches, so that early returns can't be used to infer where the difference is. +/// Returns early if data is of different length. +-(bool)isEqualToData_TimingSafe:(NSData*)other; + +@end diff --git a/Signal/src/crypto/CryptoTools.m b/Signal/src/crypto/CryptoTools.m new file mode 100644 index 000000000..8eee95552 --- /dev/null +++ b/Signal/src/crypto/CryptoTools.m @@ -0,0 +1,75 @@ +#import "CryptoTools.h" + +#import + +#import "Constraints.h" +#import "Conversions.h" +#import "EvpMessageDigest.h" +#import "EvpSymetricUtil.h" +#import "Util.h" + +@implementation CryptoTools + ++(NSData*)generateSecureRandomData:(NSUInteger)length { + NSMutableData* d = [NSMutableData dataWithLength:length]; + SecRandomCopyBytes(kSecRandomDefault, length, [d mutableBytes]); + return d; +} + ++(uint16_t)generateSecureRandomUInt16 { + return [[self generateSecureRandomData:sizeof(uint16_t)] bigEndianUInt16At:0]; +} + ++(NSString*) computeOtpWithPassword:(NSString*)password andCounter:(int64_t)counter { + require(password != nil); + + NSData* d = [[[NSNumber numberWithLongLong:counter] stringValue] encodedAsUtf8]; + NSData* h = [d hmacWithSha1WithKey:[password encodedAsUtf8]]; + return [h encodedAsBase64]; +} + +@end + +@implementation NSData (CryptoUtil) + +-(NSData*)hmacWithSha1WithKey:(NSData*)key { + return [EvpMessageDigest hmacUsingSha1Data:self withKey:key]; +} + +-(NSData*)hmacWithSha256WithKey:(NSData*)key { + return [EvpMessageDigest hmacUsingSha256Data:self withKey:key]; +} + +-(NSData*)encryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil encryptMessage:self usingAes128WithCfbAndKey:key andIv:iv]; +} +-(NSData*)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil encryptMessage:self usingAes128WithCbcAndPaddingAndKey:key andIv:iv]; +} +-(NSData*)encryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil encryptMessage:self usingAes128InCounterModeAndKey:key andIv:iv]; +} + +-(NSData*)decryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil decryptMessage:self usingAes128WithCfbAndKey:key andIv:iv]; +} +-(NSData*)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil decryptMessage:self usingAes128WithCbcAndPaddingAndKey:key andIv:iv]; +} +-(NSData*)decryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv { + return [EvpSymetricUtil decryptMessage:self usingAes128InCounterModeAndKey:key andIv:iv]; +} + +-(NSData*)hashWithSha256 { + return [EvpMessageDigest hashWithSha256:self]; +} +-(bool)isEqualToData_TimingSafe:(NSData*)other { + if (other == nil) return false; + NSUInteger n = [self length]; + if ([other length] != n) return false; + bool equal = true; + for (NSUInteger i = 0; i < n; i++) + equal &= [self uint8At:i] == [other uint8At:i]; + return equal; +} +@end diff --git a/Signal/src/crypto/EvpMessageDigest.h b/Signal/src/crypto/EvpMessageDigest.h new file mode 100644 index 000000000..0e51da63a --- /dev/null +++ b/Signal/src/crypto/EvpMessageDigest.h @@ -0,0 +1,10 @@ +#import + +// Implements class level functions for Openssl's EVP_Digest Api + +@interface EvpMessageDigest : NSObject + ++(NSData*) hashWithSha256:(NSData*) data; ++(NSData*) hmacUsingSha1Data:(NSData*) data withKey:(NSData*) key; ++(NSData*) hmacUsingSha256Data:(NSData*) data withKey:(NSData*) key; +@end diff --git a/Signal/src/crypto/EvpMessageDigest.m b/Signal/src/crypto/EvpMessageDigest.m new file mode 100644 index 000000000..c24b483b0 --- /dev/null +++ b/Signal/src/crypto/EvpMessageDigest.m @@ -0,0 +1,52 @@ +#import "EvpMessageDigest.h" + +#import +#import + +#import "Constraints.h" +#import "EvpUtil.h" +#import "NumberUtil.h" + +@implementation EvpMessageDigest + ++(NSData*) hash:(NSData*) data withDigest:(const EVP_MD*) digest { + NSUInteger expectedDigestLength = [NumberUtil assertConvertIntToNSUInteger:EVP_MD_size(digest)]; + unsigned int digestLength = 0; + unsigned char digestBuffer[expectedDigestLength]; + + EVP_MD_CTX* ctx = EVP_MD_CTX_create(); + require(NULL != ctx); + @try { + RAISE_EXCEPTION_ON_FAILURE(EVP_DigestInit_ex(ctx, digest, NULL)); + RAISE_EXCEPTION_ON_FAILURE(EVP_DigestUpdate(ctx, data.bytes, data.length)); + RAISE_EXCEPTION_ON_FAILURE(EVP_DigestFinal_ex(ctx, digestBuffer, &digestLength)); + } + @finally { + EVP_MD_CTX_destroy(ctx); + } + + require(digestLength == expectedDigestLength); + return [NSData dataWithBytes:digestBuffer length:digestLength]; +} + ++(NSData*) hmacWithData:(NSData*) data andKey:(NSData*) key andDigest:(const EVP_MD*) md{ + NSUInteger digestLength = [NumberUtil assertConvertIntToNSUInteger:EVP_MD_size(md)]; + + unsigned char* digest = HMAC(md, + [key bytes], [NumberUtil assertConvertNSUIntegerToInt:[key length]], + [data bytes], [data length], + NULL, NULL); + + return [NSData dataWithBytes:digest length:digestLength]; +} + ++(NSData*) hashWithSha256:(NSData *)data { + return [self hash:data withDigest:EVP_sha256()]; +} ++(NSData*) hmacUsingSha1Data:(NSData*) data withKey:(NSData*) key { + return [self hmacWithData:data andKey:key andDigest:EVP_sha1()]; +} ++(NSData*) hmacUsingSha256Data:(NSData*) data withKey:(NSData*) key{ + return [self hmacWithData:data andKey:key andDigest:EVP_sha256()]; +} +@end diff --git a/Signal/src/crypto/EvpSymetricUtil.h b/Signal/src/crypto/EvpSymetricUtil.h new file mode 100644 index 000000000..7aa03e5ba --- /dev/null +++ b/Signal/src/crypto/EvpSymetricUtil.h @@ -0,0 +1,16 @@ +#import +#import + +// Implements Symetric encryption methods using Openssl EVP Api. Raises Exceptions on failure. + +@interface EvpSymetricUtil : NSObject + ++(NSData*) encryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv; ++(NSData*) decryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv; + ++(NSData*) encryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv; ++(NSData*) decryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv; + ++(NSData*) encryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv; ++(NSData*) decryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv; +@end diff --git a/Signal/src/crypto/EvpSymetricUtil.m b/Signal/src/crypto/EvpSymetricUtil.m new file mode 100644 index 000000000..c8b2dd459 --- /dev/null +++ b/Signal/src/crypto/EvpSymetricUtil.m @@ -0,0 +1,99 @@ +#import "EvpSymetricUtil.h" + +#import "Constraints.h" +#import "EvpUtil.h" +#import "NumberUtil.h" + +@implementation EvpSymetricUtil + ++(NSData*) encryptMessage:(NSData *)message usingCipher:(const EVP_CIPHER*) cipher andKey:(NSData *)key andIv:(NSData *)iv { + [self assertKey:key andIv:iv lengthsAgainstCipher:cipher]; + + int messageLength = [NumberUtil assertConvertNSUIntegerToInt:[message length]]; + int cipherBlockSize = EVP_CIPHER_block_size(cipher); + int cipherTextLength = 0; + int paddingLength = 0; + + int bufferLength = ( messageLength + cipherBlockSize - 1); + unsigned char cipherText[bufferLength]; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if(!ctx) { RAISE_EXCEPTION;} + + @try { + RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptInit_ex(ctx, cipher, NULL,[key bytes], [iv bytes])) + RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptUpdate(ctx, cipherText, &cipherTextLength, [message bytes], messageLength)) + RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptFinal_ex(ctx, cipherText + cipherTextLength, &paddingLength)) + + cipherTextLength += paddingLength; + } + @finally { + EVP_CIPHER_CTX_free(ctx); + } + + require(cipherTextLength <= bufferLength); + + return [NSData dataWithBytes:cipherText length: [NumberUtil assertConvertIntToNSUInteger:cipherTextLength]]; +} + ++(void) assertKey:(NSData*) key andIv:(NSData*) iv lengthsAgainstCipher:(const EVP_CIPHER*) cipher { + int cipherKeyLength = EVP_CIPHER_key_length(cipher); + int cipherIvLength = EVP_CIPHER_iv_length(cipher); + + require([key length] == [NumberUtil assertConvertIntToNSUInteger:cipherKeyLength]); + require([iv length] == [NumberUtil assertConvertIntToNSUInteger:cipherIvLength]); +} + ++(NSData*) decryptMessage:(NSData *)cipherText usingCipher:(const EVP_CIPHER*) cipher andKey:(NSData *)key andIv:(NSData *)iv { + + [self assertKey:key andIv:iv lengthsAgainstCipher:cipher]; + + int cipherTextLength = [NumberUtil assertConvertNSUIntegerToInt:[cipherText length]]; + int cipherBlockSize = EVP_CIPHER_block_size(cipher); + int plainTextLength = 0; + int paddingLength = 0; + + int bufferLength = ( cipherTextLength + cipherBlockSize); + unsigned char plainText[bufferLength]; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if(!ctx) { RAISE_EXCEPTION;} + + @try { + RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptInit_ex( ctx, cipher, NULL, [key bytes], [iv bytes])) + RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptUpdate( ctx, plainText, &plainTextLength, [cipherText bytes], cipherTextLength)) + RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptFinal_ex( ctx, plainText + plainTextLength, &paddingLength)) + + plainTextLength += paddingLength; + } + @finally { + EVP_CIPHER_CTX_free(ctx); + } + + require(plainTextLength <= bufferLength); + + return [NSData dataWithBytes:plainText length: [NumberUtil assertConvertIntToNSUInteger:plainTextLength]]; +} + ++(NSData*) encryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv { + return [self encryptMessage:message usingCipher:EVP_aes_128_cbc() andKey:key andIv:iv]; +} ++(NSData*) decryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv { + return [self decryptMessage:message usingCipher:EVP_aes_128_cbc() andKey:key andIv:iv]; +} + ++(NSData*) encryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv { + return [self encryptMessage:message usingCipher:EVP_aes_128_cfb128() andKey:key andIv:iv]; +} ++(NSData*) decryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv { + return [self decryptMessage:message usingCipher:EVP_aes_128_cfb128() andKey:key andIv:iv]; +} + ++(NSData*) encryptMessage:(NSData*) message usingAes128InCounterModeAndKey:(NSData*) key andIv:(NSData*) iv { + return [self encryptMessage:message usingCipher:EVP_aes_128_ctr() andKey:key andIv:iv]; +} ++(NSData*) decryptMessage:(NSData*) message usingAes128InCounterModeAndKey:(NSData*) key andIv:(NSData*) iv { + return [self decryptMessage:message usingCipher:EVP_aes_128_ctr() andKey:key andIv:iv]; +} + +@end; diff --git a/Signal/src/crypto/EvpUtil.h b/Signal/src/crypto/EvpUtil.h new file mode 100644 index 000000000..2b6165f0e --- /dev/null +++ b/Signal/src/crypto/EvpUtil.h @@ -0,0 +1,4 @@ +#import + +#define RAISE_EXCEPTION [NSException raise:@"OPENSSL_Exception" format:@"Line:%d File:%s ", __LINE__ , __FILE__] +#define RAISE_EXCEPTION_ON_FAILURE(X) if( 1 != X){ RAISE_EXCEPTION;} diff --git a/Signal/src/environment/Environment.h b/Signal/src/environment/Environment.h new file mode 100644 index 000000000..b3aea8164 --- /dev/null +++ b/Signal/src/environment/Environment.h @@ -0,0 +1,77 @@ +#import +#import "Logging.h" +#import "PropertyListPreferences.h" +#import "PacketHandler.h" +#import "SecureEndPoint.h" + +/** + * + * Environment is a data and data accessor class. + * It handles application-level component wiring in order to support mocks for testing. + * It also handles network configuration for testing/deployment server configurations. + * + **/ + +#define SAMPLE_RATE 8000 + +#define ENVIRONMENT_TESTING_OPTION_LOSE_CONF_ACK_ON_PURPOSE @"LoseConfAck" +#define ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS @"AllowTcpWithoutTls" +#define ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER @"LegacyAndroidInterop_1" + +@class RecentCallManager; +@class ContactsManager; +@class PhoneManager; +@class PhoneNumberDirectoryFilterManager; + +@interface Environment : NSObject +@property (nonatomic, readonly) PropertyListPreferences* preferences; +@property (nonatomic, readonly) in_port_t serverPort; +@property (nonatomic, readonly) id logging; +@property (nonatomic, readonly) SecureEndPoint* masterServerSecureEndPoint; +@property (nonatomic, readonly) NSString* defaultRelayName; +@property (nonatomic, readonly) Certificate* certificate; +@property (nonatomic, readonly) NSString* relayServerHostNameSuffix; +@property (nonatomic, readonly) NSArray* keyAgreementProtocolsInDescendingPriority; +@property (nonatomic, readonly) ErrorHandlerBlock errorNoter; +@property (nonatomic, readonly) NSString* currentRegionCodeForPhoneNumbers; +@property (nonatomic, readonly) PhoneManager* phoneManager; +@property (nonatomic, readonly) RecentCallManager *recentCallManager; +@property (nonatomic, readonly) NSArray* testingAndLegacyOptions; +@property (nonatomic, readonly) NSData* zrtpClientId; +@property (nonatomic, readonly) NSData* zrtpVersionId; +@property (nonatomic, readonly) ContactsManager *contactsManager; +@property (nonatomic, readonly) PhoneNumberDirectoryFilterManager* phoneDirectoryManager; + ++(SecureEndPoint*) getMasterServerSecureEndPoint; ++(SecureEndPoint*) getSecureEndPointToDefaultRelayServer; ++(SecureEndPoint*) getSecureEndPointToSignalingServerNamed:(NSString*)name; + ++(Environment*) environmentWithPreferences:(PropertyListPreferences*)preferences + andLogging:(id)logging + andErrorNoter:(ErrorHandlerBlock)errorNoter + andServerPort:(in_port_t)serverPort + andMasterServerHostName:(NSString*)masterServerHostName + andDefaultRelayName:(NSString*)defaultRelayName + andRelayServerHostNameSuffix:(NSString*)relayServerHostNameSuffix + andCertificate:(Certificate*)certificate + andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers + andSupportedKeyAgreementProtocols:(NSArray*)keyAgreementProtocolsInDescendingPriority + andPhoneManager:(PhoneManager*)phoneManager + andRecentCallManager:(RecentCallManager *)recentCallManager + andTestingAndLegacyOptions:(NSArray*)testingAndLegacyOptions + andZrtpClientId:(NSData*)zrtpClientId + andZrtpVersionId:(NSData*)zrtpVersionId + andContactsManager:(ContactsManager *)contactsManager + andPhoneDirectoryManager:(PhoneNumberDirectoryFilterManager*)phoneDirectoryManager; + ++(Environment*) getCurrent; ++(void) setCurrent:(Environment*)curEnvironment; ++(PropertyListPreferences*) preferences; ++(id) logging; ++(NSString*) relayServerNameToHostName:(NSString*)name; ++(ErrorHandlerBlock) errorNoter; ++(NSString*) currentRegionCodeForPhoneNumbers; ++(bool) hasEnabledTestingOrLegacyOption:(NSString*)flag; ++(PhoneManager*) phoneManager; + +@end diff --git a/Signal/src/environment/Environment.m b/Signal/src/environment/Environment.m new file mode 100644 index 000000000..1a766d0a5 --- /dev/null +++ b/Signal/src/environment/Environment.m @@ -0,0 +1,146 @@ +#import "Environment.h" +#import "Constraints.h" +#import "FunctionalUtil.h" +#import "KeyAgreementProtocol.h" +#import "DH3KKeyAgreementProtocol.h" +#import "HostNameEndPoint.h" +#import "RecentCallManager.h" +#import "ContactsManager.h" +#import "PhoneNumberDirectoryFilterManager.h" + +static Environment* environment = nil; + +@implementation Environment + +@synthesize testingAndLegacyOptions, + currentRegionCodeForPhoneNumbers, + errorNoter, + keyAgreementProtocolsInDescendingPriority, + logging, + masterServerSecureEndPoint, + preferences, + defaultRelayName, + relayServerHostNameSuffix, + certificate, + serverPort, + zrtpClientId, + zrtpVersionId, + phoneManager, + recentCallManager, + contactsManager, + phoneDirectoryManager; + ++(NSString*) currentRegionCodeForPhoneNumbers { + return [[self getCurrent] currentRegionCodeForPhoneNumbers]; +} + ++(Environment*) getCurrent { + require(environment != nil); + return environment; +} ++(void) setCurrent:(Environment*)curEnvironment { + environment = curEnvironment; +} ++(ErrorHandlerBlock) errorNoter { + return [[self getCurrent] errorNoter]; +} ++(bool) hasEnabledTestingOrLegacyOption:(NSString*)flag { + return [[self getCurrent].testingAndLegacyOptions containsObject:flag]; +} + ++(NSString*) relayServerNameToHostName:(NSString*)name { + return [NSString stringWithFormat:@"%@.%@", + name, + [[Environment getCurrent] relayServerHostNameSuffix]]; +} ++(SecureEndPoint*) getMasterServerSecureEndPoint { + return [[Environment getCurrent] masterServerSecureEndPoint]; +} ++(SecureEndPoint*) getSecureEndPointToDefaultRelayServer { + return [Environment getSecureEndPointToSignalingServerNamed:[Environment getCurrent].defaultRelayName]; +} ++(SecureEndPoint*) getSecureEndPointToSignalingServerNamed:(NSString*)name { + require(name != nil); + Environment* env = [Environment getCurrent]; + + NSString* hostName = [self relayServerNameToHostName:name]; + HostNameEndPoint* location = [HostNameEndPoint hostNameEndPointWithHostName:hostName andPort:env.serverPort]; + return [SecureEndPoint secureEndPointForHost:location identifiedByCertificate:env.certificate]; +} + + +(Environment*) environmentWithPreferences:(PropertyListPreferences*)preferences + andLogging:(id)logging + andErrorNoter:(ErrorHandlerBlock)errorNoter + andServerPort:(in_port_t)serverPort + andMasterServerHostName:(NSString*)masterServerHostName + andDefaultRelayName:(NSString*)defaultRelayName + andRelayServerHostNameSuffix:(NSString*)relayServerHostNameSuffix + andCertificate:(Certificate*)certificate + andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers + andSupportedKeyAgreementProtocols:(NSArray*)keyAgreementProtocolsInDescendingPriority + andPhoneManager:(PhoneManager*)phoneManager + andRecentCallManager:(RecentCallManager *)recentCallManager + andTestingAndLegacyOptions:(NSArray*)testingAndLegacyOptions + andZrtpClientId:(NSData*)zrtpClientId + andZrtpVersionId:(NSData*)zrtpVersionId + andContactsManager:(ContactsManager *)contactsManager + andPhoneDirectoryManager:(PhoneNumberDirectoryFilterManager*)phoneDirectoryManager { + require(errorNoter != nil); + require(preferences != nil); + require(zrtpClientId != nil); + require(zrtpVersionId != nil); + require(testingAndLegacyOptions != nil); + require(currentRegionCodeForPhoneNumbers != nil); + require(keyAgreementProtocolsInDescendingPriority != nil); + require([keyAgreementProtocolsInDescendingPriority all:^int(id p) { + return [p conformsToProtocol:@protocol(KeyAgreementProtocol)]; + }]); + + // must support DH3k + require([keyAgreementProtocolsInDescendingPriority any:^int(id p) { + return [p isKindOfClass:[DH3KKeyAgreementProtocol class]]; + }]); + + Environment* e = [Environment new]; + e->preferences = preferences; + e->errorNoter = errorNoter; + e->logging = logging; + e->testingAndLegacyOptions = testingAndLegacyOptions; + e->serverPort = serverPort; + e->masterServerSecureEndPoint = [SecureEndPoint secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName + andPort:serverPort] + identifiedByCertificate:certificate]; + e->phoneDirectoryManager = phoneDirectoryManager; + e->defaultRelayName = defaultRelayName; + e->certificate = certificate; + e->relayServerHostNameSuffix = relayServerHostNameSuffix; + e->keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority; + e->currentRegionCodeForPhoneNumbers = currentRegionCodeForPhoneNumbers; + e->phoneManager = phoneManager; + e->recentCallManager = recentCallManager; + e->zrtpClientId = zrtpClientId; + e->zrtpVersionId = zrtpVersionId; + e->contactsManager = contactsManager; + + // @todo: better place for this? + if (recentCallManager != nil) { + [recentCallManager watchForCallsThrough:phoneManager + untilCancelled:nil]; + [recentCallManager watchForContactUpdatesFrom:contactsManager + untillCancelled:nil]; + } + + return e; + } + ++(PropertyListPreferences*) preferences { + return [[Environment getCurrent] preferences]; +} ++(PhoneManager*) phoneManager { + return [[Environment getCurrent] phoneManager]; +} ++(id) logging { + return [[Environment getCurrent] logging]; +} + +@end diff --git a/Signal/src/environment/KeyChainStorage.h b/Signal/src/environment/KeyChainStorage.h new file mode 100644 index 000000000..e6c89853a --- /dev/null +++ b/Signal/src/environment/KeyChainStorage.h @@ -0,0 +1,24 @@ +// +// KeyChainStorage.h +// Signal +// +// Created by Frederic Jacobs on 06/05/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import +@class PhoneNumber, Zid; +@interface KeyChainStorage : NSObject + ++(PhoneNumber*) forceGetLocalNumber; ++(PhoneNumber*)tryGetLocalNumber; ++(void) setLocalNumberTo:(PhoneNumber*)localNumber; ++(Zid*) getOrGenerateZid; ++(NSString*) getOrGenerateSavedPassword; ++(NSData*) getOrGenerateSignalingMacKey; ++(NSData*) getOrGenerateSignalingCipherKey; ++(NSData*) getOrGenerateSignalingExtraKey; + ++ (void)clear; + +@end diff --git a/Signal/src/environment/KeyChainStorage.m b/Signal/src/environment/KeyChainStorage.m new file mode 100644 index 000000000..8c737f12a --- /dev/null +++ b/Signal/src/environment/KeyChainStorage.m @@ -0,0 +1,105 @@ +// +// KeyChainStorage.m +// Signal +// +// Created by Frederic Jacobs on 06/05/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +#import "Constraints.h" +#import "CryptoTools.h" +#import "DataUtil.h" +#import "KeyChainStorage.h" +#import "KeychainWrapper.h" +#import "StringUtil.h" +#import "PhoneNumber.h" +#import "Zid.h" + + +#define LOCAL_NUMBER_KEY @"Number" +#define SAVED_PASSWORD_KEY @"Password" +#define SIGNALING_MAC_KEY @"Signaling Mac Key" +#define SIGNALING_CIPHER_KEY @"Signaling Cipher Key" +#define ZID_KEY @"ZID" +#define SIGNALING_EXTRA_KEY @"Signaling Extra Key" + +#define SIGNALING_MAC_KEY_LENGTH 20 +#define SIGNALING_CIPHER_KEY_LENGTH 16 +#define SAVED_PASSWORD_LENGTH 18 +#define SIGNALING_EXTRA_KEY_LENGTH 4 + +@implementation KeyChainStorage + ++(PhoneNumber*) forceGetLocalNumber { + NSString* localNumber = [self tryGetValueForKey:LOCAL_NUMBER_KEY]; + checkOperation(localNumber != nil); + return [PhoneNumber tryParsePhoneNumberFromE164:localNumber]; +} + ++(void) setLocalNumberTo:(PhoneNumber*)localNumber { + require(localNumber != nil); + [self setValueForKey:LOCAL_NUMBER_KEY toValue:[localNumber toE164]]; +} + ++(PhoneNumber*)tryGetLocalNumber { + NSString* localNumber = [self tryGetValueForKey:LOCAL_NUMBER_KEY]; + return (localNumber != nil ? [PhoneNumber tryParsePhoneNumberFromE164:localNumber] : nil); +} + ++(Zid*) getOrGenerateZid { + return [Zid zidWithData:[self getOrGenerateRandomDataWithKey:ZID_KEY andLength:12]]; +} + ++(NSString*) getOrGenerateSavedPassword { + NSString *password = [KeychainWrapper keychainStringFromMatchingIdentifier:SAVED_PASSWORD_KEY]; + + if (!password) { + password = [[CryptoTools generateSecureRandomData:SAVED_PASSWORD_LENGTH] encodedAsBase64]; + [KeychainWrapper createKeychainValue:password forIdentifier:SAVED_PASSWORD_KEY]; + } + + return password; +} + ++(NSData*) getOrGenerateSignalingMacKey { + return [self getOrGenerateRandomDataWithKey:SIGNALING_MAC_KEY andLength:SIGNALING_MAC_KEY_LENGTH]; +} + ++(NSData*) getOrGenerateSignalingCipherKey { + return [self getOrGenerateRandomDataWithKey:SIGNALING_CIPHER_KEY andLength:SIGNALING_CIPHER_KEY_LENGTH]; +} + ++(NSData*) getOrGenerateSignalingExtraKey { + return [self getOrGenerateRandomDataWithKey:SIGNALING_EXTRA_KEY andLength:SIGNALING_EXTRA_KEY_LENGTH]; +} + ++(NSData*) getOrGenerateRandomDataWithKey:(NSString*)key andLength:(NSUInteger)length { + require(key != nil); + + NSData *password = [[KeychainWrapper keychainStringFromMatchingIdentifier:key] decodedAsBase64Data]; + + if (!password) { + password = [CryptoTools generateSecureRandomData:length]; + [KeychainWrapper createKeychainValue:[password encodedAsBase64] forIdentifier:key]; + } + + return password; +} + ++ (NSString*)tryGetValueForKey:(NSString*)key{ + return [KeychainWrapper keychainStringFromMatchingIdentifier:key]; +} + ++ (void)setValueForKey:(NSString*)key toValue:(NSString*)string{ + [KeychainWrapper createKeychainValue:string forIdentifier:key]; +} + ++ (void)clear{ + [KeychainWrapper deleteItemFromKeychainWithIdentifier:SIGNALING_MAC_KEY]; + [KeychainWrapper deleteItemFromKeychainWithIdentifier:SIGNALING_EXTRA_KEY]; + [KeychainWrapper deleteItemFromKeychainWithIdentifier:SIGNALING_CIPHER_KEY]; + [KeychainWrapper deleteItemFromKeychainWithIdentifier:SAVED_PASSWORD_KEY]; + [KeychainWrapper deleteItemFromKeychainWithIdentifier:ZID_KEY]; + [KeychainWrapper deleteItemFromKeychainWithIdentifier:LOCAL_NUMBER_KEY]; +} + +@end diff --git a/Signal/src/environment/LocalizableText.h b/Signal/src/environment/LocalizableText.h new file mode 100644 index 000000000..e6972889a --- /dev/null +++ b/Signal/src/environment/LocalizableText.h @@ -0,0 +1,139 @@ +#import +#import "CallTermination.h" +#import "CallProgress.h" + +#define TXT_IN_CALL_CONNECTING NSLocalizedString(@"IN_CALL_CONNECTING", @"") +#define TXT_IN_CALL_RINGING NSLocalizedString(@"IN_CALL_RINGING", @"") +#define TXT_IN_CALL_SECURING NSLocalizedString(@"IN_CALL_SECURING", @"") +#define TXT_IN_CALL_TALKING NSLocalizedString(@"IN_CALL_TALKING", @"") +#define TXT_IN_CALL_TERMINATED NSLocalizedString(@"IN_CALL_TERMINATED", @"") + +#define TXT_END_CALL_LOGIN_FAILED NSLocalizedString(@"END_CALL_LOGIN_FAILED", @"") +#define TXT_END_CALL_STALE_SESSION NSLocalizedString(@"END_CALL_STALE_SESSION", @"") +#define TXT_END_CALL_NO_SUCH_USER NSLocalizedString(@"END_CALL_NO_SUCH_USER", @"") +#define TXT_END_CALL_RESPONDER_IS_BUSY NSLocalizedString(@"END_CALL_RESPONDER_IS_BUSY", @"") +#define TXT_END_CALL_REJECTED_LOCAL NSLocalizedString(@"END_CALL_REJECTED_LOCAL", @"") +#define TXT_END_CALL_REJECTED_REMOTE NSLocalizedString(@"END_CALL_REJECTED_REMOTE", @"") +#define TXT_END_CALL_RECIPIENT_UNAVAILABLE NSLocalizedString(@"END_CALL_RECIPIENT_UNAVAILABLE", @"") +#define TXT_END_CALL_UNCATEGORIZED_FAILURE NSLocalizedString(@"END_CALL_UNCATEGORIZED_FAILURE", @"") +#define TXT_END_CALL_BAD_INTERACTION_WITH_SERVER NSLocalizedString(@"END_CALL_BAD_INTERACTION_WITH_SERVER", @"") +#define TXT_END_CALL_HANDSHAKE_FAILED NSLocalizedString(@"END_CALL_HANDSHAKE_FAILED", @"") +#define TXT_END_CALL_HANGUP_REMOTE NSLocalizedString(@"END_CALL_HANGUP_REMOTE", @"") +#define TXT_END_CALL_HANGUP_LOCAL NSLocalizedString(@"END_CALL_HANGUP_LOCAL", @"") +#define TXT_END_CALL_REPLACED_BY_NEXT NSLocalizedString(@"END_CALL_REPLACED_BY_NEXT", @"") +// @todo: some languages probably don't prefix this sort of thing +#define TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX NSLocalizedString(@"END_CALL_MESSAGE_FROM_SERVER_PREFIX", @"") + +#pragma mark - Menu Table Cell Titles + +#define MAIN_MENU_OPTION_RECENT_CALLS NSLocalizedString(@"MAIN_MENU_OPTION_RECENT_CALLS",@"") +#define MAIN_MENU_OPTION_FAVOURITES NSLocalizedString(@"MAIN_MENU_OPTION_FAVOURITES",@"") +#define MAIN_MENU_OPTION_CONTACTS NSLocalizedString(@"MAIN_MENU_OPTION_CONTACTS",@"") +#define MAIN_MENU_OPTION_DIALER NSLocalizedString(@"MAIN_MENU_OPTION_DIALER",@"") +#define MAIN_MENU_INVITE_CONTACTS NSLocalizedString(@"MAIN_MENU_INVITE_CONTACTS",@"") + +#define MAIN_MENU_OPTION_SETTINGS NSLocalizedString(@"MAIN_MENU_OPTION_SETTINGS",@"") +#define MAIN_MENU_OPTION_ABOUT NSLocalizedString(@"MAIN_MENU_OPTION_ABOUT",@"") +#define MAIN_MENU_OPTION_REPORT_BUG NSLocalizedString(@"MAIN_MENU_OPTION_REPORT_BUG",@"") +#define MAIN_MENU_OPTION_BLOG NSLocalizedString(@"MAIN_MENU_OPTION_BLOG",@"") + +#pragma mark - View Controller Titles + +#define WHISPER_NAV_BAR_TITLE NSLocalizedString(@"WHISPER_NAV_BAR_TITLE", @"Title for home feed view controller") +#define CONTACT_BROWSE_NAV_BAR_TITLE NSLocalizedString(@"CONTACT_BROWSE_NAV_BAR_TITLE", @"Title for contact browse view controller") +#define KEYPAD_NAV_BAR_TITLE NSLocalizedString(@"KEYPAD_NAV_BAR_TITLE", @"Title for keypad view controller") +#define RECENT_NAV_BAR_TITLE NSLocalizedString(@"RECENT_NAV_BAR_TITLE", @"Title for recent calls view controller") +#define SETTINGS_NAV_BAR_TITLE NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for recent calls view controller") +#define FAVOURITES_NAV_BAR_TITLE NSLocalizedString(@"FAVOURITES_NAV_BAR_TITLE", @"Title for favourites view controller") + +#pragma mark - Contact Detail Communication Types + +#define CONTACT_DETAIL_COMM_TYPE_EMAIL NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_EMAIL", @"") +#define CONTACT_DETAIL_COMM_TYPE_SECURE NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_SECURE", @"") +#define CONTACT_DETAIL_COMM_TYPE_INSECURE NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_INSECURE", @"") +#define CONTACT_DETAIL_COMM_TYPE_NOTES NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_NOTES", @"") + +#pragma mark - Dialer + +#define DIALER_NUMBER_1 NSLocalizedString(@"DIALER_NUMBER_1", @"") +#define DIALER_NUMBER_2 NSLocalizedString(@"DIALER_NUMBER_2", @"") +#define DIALER_NUMBER_3 NSLocalizedString(@"DIALER_NUMBER_3", @"") +#define DIALER_NUMBER_4 NSLocalizedString(@"DIALER_NUMBER_4", @"") +#define DIALER_NUMBER_5 NSLocalizedString(@"DIALER_NUMBER_5", @"") +#define DIALER_NUMBER_6 NSLocalizedString(@"DIALER_NUMBER_6", @"") +#define DIALER_NUMBER_7 NSLocalizedString(@"DIALER_NUMBER_7", @"") +#define DIALER_NUMBER_8 NSLocalizedString(@"DIALER_NUMBER_8", @"") +#define DIALER_NUMBER_9 NSLocalizedString(@"DIALER_NUMBER_9", @"") +#define DIALER_NUMBER_0 NSLocalizedString(@"DIALER_NUMBER_0", @"") +#define DIALER_NUMBER_PLUS NSLocalizedString(@"DIALER_NUMBER_PLUS", @"") +#define DIALER_NUMBER_POUND NSLocalizedString(@"DIALER_NUMBER_POUND", @"") +#define TXT_ADD_CONTACT NSLocalizedString(@"TXT_ADD_CONTACT", @"") +#define CALL_BUTTON_TITLE NSLocalizedString(@"CALL_BUTTON_TITLE", @"") + +#define DIALER_CALL_BUTTON_TITLE NSLocalizedString(@"DIALER_CALL_BUTTON_TITLE", @"") + +#pragma mark - General Purpose + +#define TXT_CANCEL_TITLE NSLocalizedString(@"TXT_CANCEL_TITLE", @"") +#define TXT_SEARCH_PLACEHOLDER_TEXT NSLocalizedString(@"TXT_SEARCH_PLACEHOLDER_TEXT", @"") +#define UNKNOWN_CONTACT_NAME NSLocalizedString(@"UNKNOWN_CONTACT_NAME", @"") +#define TXT_DATESTRING_TODAY NSLocalizedString(@"DATESTRING_TODAY", @"") + +#pragma mark - Inbox View + +#define INBOX_VIEW_TUTORIAL_LABEL_TOP NSLocalizedString(@"INBOX_VIEW_TUTORIAL_LABEL_TOP", @"") +#define INBOX_VIEW_TUTORIAL_LABEL_MIDDLE NSLocalizedString(@"INBOX_VIEW_TUTORIAL_LABEL_MIDDLE", @"") + +#define TABLE_SECTION_TITLE_REGISTERED NSLocalizedString(@"TABLE_SECTION_TITLE_REGISTERED", @"") +#define TABLE_SECTION_TITLE_UNREGISTERED NSLocalizedString(@"TABLE_SECTION_TITLE_UNREGISTERED", @"") + +#pragma mark - Home View footer cell + +#define HOME_FOOTER_FIRST_MESSAGE_CALLS_UNSORTED NSLocalizedString(@"HOME_FOOTER_FIRST_MESSAGE_CALLS_UNSORTED", @"") +#define HOME_FOOTER_SECOND_MESSAGE_CALLS_UNSORTED NSLocalizedString(@"HOME_FOOTER_SECOND_MESSAGE_CALLS_UNSORTED", @"") +#define HOME_FOOTER_SECOND_MESSAGE_CALL_UNSORTED NSLocalizedString(@"HOME_FOOTER_SECOND_MESSAGE_CALL_UNSORTED", @"") +#define HOME_FOOTER_FIRST_MESSAGE_CALLS_NIL NSLocalizedString(@"HOME_FOOTER_FIRST_MESSAGE_CALLS_NIL", @"") +#define HOME_FOOTER_SECOND_MESSAGE_CALLS_NIL NSLocalizedString(@"HOME_FOOTER_SECOND_MESSAGE_CALLS_NIL", @"") + +#pragma mark - Settings View + +#define SETTINGS_NUMBER_PREFIX NSLocalizedString(@"SETTINGS_NUMBER_PREFIX", @"") +#define SETTINGS_LOG_CLEAR_TITLE NSLocalizedString(@"SETTINGS_LOG_CLEAR_TITLE", @"") +#define SETTINGS_LOG_CLEAR_MESSAGE NSLocalizedString(@"SETTINGS_LOG_CLEAR_MESSAGE", @"") +#define SETTINGS_LOG_CLEAR_CONFIRM NSLocalizedString(@"OK", @"") + +#pragma mark - Registration + +#define REGISTER_CC_ERR_ALERT_VIEW_TITLE NSLocalizedString(@"REGISTER_CC_ERR_ALERT_VIEW_TITLE", @"") +#define REGISTER_CC_ERR_ALERT_VIEW_MESSAGE NSLocalizedString(@"REGISTER_CC_ERR_ALERT_VIEW_MESSAGE", @"") +#define REGISTER_CC_ERR_ALERT_VIEW_DISMISS NSLocalizedString(@"OK", @"") +#define CONTINUE_TO_WHISPER_TITLE NSLocalizedString(@"CONTINUE_TO_WHISPER_TITLE", @"") + +#define REGISTER_BUTTON_TITLE NSLocalizedString(@"REGISTER_BUTTON_TITLE", @"") +#define CHALLENGE_CODE_BUTTON_TITLE NSLocalizedString(@"CHALLENGE_CODE_BUTTON_TITLE", @"") + +#define END_CALL_BUTTON_TITLE NSLocalizedString(@"END_CALL_BUTTON_TITLE", @"") +#define ANSWER_CALL_BUTTON_TITLE NSLocalizedString(@"ANSWER_CALL_BUTTON_TITLE", @"") +#define REJECT_CALL_BUTTON_TITLE NSLocalizedString(@"REJECT_CALL_BUTTON_TITLE", @"") + +#pragma mark - Invite Users + +#define INVITE_USERS_ACTION_SHEET_TITLE NSLocalizedString(@"INVITE_USERS_ACTION_SHEET_TITLE", @""); +#define INVITE_USERS_MESSAGE NSLocalizedString(@"INVITE_USERS_MESSAGE", @""); + +#pragma mark - Invite User Modal + +#define INVITE_USER_MODAL_TITLE NSLocalizedString(@"INVITE_USER_MODAL_TITLE",@"") +#define INVITE_USER_MODAL_BUTTON_CANCEL NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_CANCEL",@"") +#define INVITE_USER_MODAL_BUTTON_INVITE NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_INVITE",@"") +#define INVITE_USER_MODAL_TEXT NSLocalizedString(@"INVITE_USER_MODAL_TEXT",@"") + +#pragma mark - Contact Intersection + +#define TIMEOUT NSLocalizedString(@"TIMEOUT",@"") +#define TIMEOUT_CONTACTS_DETAIL NSLocalizedString(@"TIMEOUT_CONTACTS_DETAIL", @"") + + +NSDictionary* makeCallProgressLocalizedTextDictionary(void); +NSDictionary* makeCallTerminationLocalizedTextDictionary(void); + diff --git a/Signal/src/environment/LocalizableText.m b/Signal/src/environment/LocalizableText.m new file mode 100644 index 000000000..8f9e3a731 --- /dev/null +++ b/Signal/src/environment/LocalizableText.m @@ -0,0 +1,39 @@ +#import "LocalizableText.h" + +CallTermination* ct(enum CallTerminationType t); +CallTermination* ct(enum CallTerminationType t) { + return [CallTermination callTerminationOfType:t withFailure:nil andMessageInfo:nil]; +} +CallProgress* cp(enum CallProgressType t); +CallProgress* cp(enum CallProgressType t) { + return [CallProgress callProgressWithType:t]; +} + +NSDictionary* makeCallProgressLocalizedTextDictionary(void) { + return @{ + cp(CallProgressType_Connecting): TXT_IN_CALL_CONNECTING, + cp(CallProgressType_Ringing): TXT_IN_CALL_RINGING, + cp(CallProgressType_Securing): TXT_IN_CALL_SECURING, + cp(CallProgressType_Talking): TXT_IN_CALL_TALKING, + cp(CallProgressType_Terminated): TXT_IN_CALL_TERMINATED + }; + +} +NSDictionary* makeCallTerminationLocalizedTextDictionary(void) { + return @{ + ct(CallTerminationType_NoSuchUser): TXT_END_CALL_NO_SUCH_USER, + ct(CallTerminationType_LoginFailed): TXT_END_CALL_LOGIN_FAILED, + ct(CallTerminationType_ResponderIsBusy): TXT_END_CALL_RESPONDER_IS_BUSY, + ct(CallTerminationType_StaleSession): TXT_END_CALL_STALE_SESSION, + ct(CallTerminationType_UncategorizedFailure): TXT_END_CALL_UNCATEGORIZED_FAILURE, + ct(CallTerminationType_ReplacedByNext): TXT_END_CALL_REPLACED_BY_NEXT, + ct(CallTerminationType_RecipientUnavailable): TXT_END_CALL_RECIPIENT_UNAVAILABLE, + ct(CallTerminationType_BadInteractionWithServer): TXT_END_CALL_BAD_INTERACTION_WITH_SERVER, + ct(CallTerminationType_HandshakeFailed): TXT_END_CALL_HANDSHAKE_FAILED, + ct(CallTerminationType_HangupRemote): TXT_END_CALL_HANGUP_REMOTE, + ct(CallTerminationType_HangupLocal): TXT_END_CALL_HANGUP_LOCAL, + ct(CallTerminationType_ServerMessage): TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX, + ct(CallTerminationType_RejectedLocal): TXT_END_CALL_REJECTED_LOCAL, + ct(CallTerminationType_RejectedRemote): TXT_END_CALL_REJECTED_REMOTE + }; +} diff --git a/Signal/src/environment/PreferencesUtil.h b/Signal/src/environment/PreferencesUtil.h new file mode 100644 index 000000000..78cfe7ff0 --- /dev/null +++ b/Signal/src/environment/PreferencesUtil.h @@ -0,0 +1,38 @@ +#import +#import "PhoneNumberDirectoryFilter.h" +#import "PropertyListPreferences.h" +#import "CallLogViewController.h" +#import "Zid.h" + +@class PhoneNumber; + +@interface PropertyListPreferences (PropertyUtil) + +-(PhoneNumberDirectoryFilter*) tryGetSavedPhoneNumberDirectory; +-(void) setSavedPhoneNumberDirectory:(PhoneNumberDirectoryFilter*)phoneNumberDirectoryFilter; +-(NSTimeInterval) getCachedOrDefaultDesiredBufferDepth; +-(void) setCachedDesiredBufferDepth:(double)value; +-(int64_t) getAndIncrementOneTimeCounter; +-(void) setSettingsRowExpandedPrefs:(NSArray *)prefs; +-(NSArray *) getOrGenerateSettingsRowExpandedPrefs; +-(NSArray *) getAvailableDateFormats; + +-(BOOL) getFreshInstallTutorialsEnabled; +-(BOOL) getContactImagesEnabled; +-(BOOL) getAutocorrectEnabled; +-(BOOL) getHistoryLogEnabled; +-(BOOL) getAnonymousFeedbackEnabled; +-(BOOL) getIsRegistered; +-(NSString *) getDateFormat; + +-(void) setDateFormat:(NSString *)format; +-(void) setFreshInstallTutorialsEnabled:(BOOL)enabled; +-(void) setContactImagesEnabled:(BOOL)enabled; +-(void) setAutocorrectEnabled:(BOOL)enabled; +-(void) setHistoryLogEnabled:(BOOL)enabled; +-(void) setAnonymousFeedbackEnabled:(BOOL)enabled; +-(void) setIsRegistered:(BOOL)registered; + +-(NSString *)getDateFormatKey; + +@end diff --git a/Signal/src/environment/PreferencesUtil.m b/Signal/src/environment/PreferencesUtil.m new file mode 100644 index 000000000..10a5a7827 --- /dev/null +++ b/Signal/src/environment/PreferencesUtil.m @@ -0,0 +1,193 @@ +#import "PreferencesUtil.h" +#import "CryptoTools.h" +#import "Constraints.h" +#import "KeychainWrapper.h" +#import "PhoneNumber.h" +#import "Util.h" + + +#import "NotificationManifest.h" + + +#define PHONE_DIRECTORY_BLOOM_FILTER_HASH_COUNT_KEY @"Directory Bloom Hash Count" +#define PHONE_DIRECTORY_BLOOM_FILTER_DATA_KEY @"Directory Bloom Data" +#define PHONE_DIRECTORY_EXPIRATION @"Directory Expiration" + +#define SETTINGS_EXPANDED_ROW_PREF_DICT_KEY @"Settings Expanded Row Pref Dict Key" + +#define FRESH_INSTALL_TUTORIALS_ENABLED_KEY @"Fresh Install Tutorials Enabled Key" +#define CONTACT_IMAGES_ENABLED_KEY @"Contact Images Enabled Key" +#define AUTOCORRECT_ENABLED_KEY @"Autocorrect Enabled Key" +#define HISTORY_LOG_ENABLED_KEY @"History Log Enabled Key" +#define ANONYMOUS_FEEDBACK_ENABLED_KEY @"Anonymous Feedback Enabled Key" + +#define CALL_STREAM_DES_BUFFER_LEVEL_KEY @"CallStreamDesiredBufferLevel" +#define DEFAULT_CALL_STREAM_DES_BUFFER_LEVEL 0.5 + +#define PASSWORD_COUNTER_KEY @"PasswordCounter" + +#define DATE_FORMAT_KEY @"Date Format Key" +#define DATE_FORMAT_1 @"dd-MM-yyyy" +#define DATE_FORMAT_2 @"yyyy-MM-dd" +#define DATE_FORMAT_3 @"MM-dd-yyyy" +#define DATE_FORMAT_4 @"dd/MM/yyyy" +#define DATE_FORMAT_5 @"yyyy/MM/dd" +#define DATE_FORMAT_6 @"MM/dd/yyyy" + +#define IS_REGISTERED_KEY @"Is Registered" + +@implementation PropertyListPreferences (PropertyUtil) + +-(PhoneNumberDirectoryFilter*) tryGetSavedPhoneNumberDirectory { + NSUInteger hashCount = [[self tryGetValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_HASH_COUNT_KEY] unsignedIntegerValue]; + NSData* data = [self tryGetValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_DATA_KEY]; + NSDate* expiration = [self tryGetValueForKey:PHONE_DIRECTORY_EXPIRATION]; + if (hashCount == 0 || [data length] == 0 || expiration == nil) return nil; + BloomFilter* bloomFilter = [BloomFilter bloomFilterWithHashCount:hashCount andData:data]; + return [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterWithBloomFilter:bloomFilter + andExpirationDate:expiration]; +} +-(void) setSavedPhoneNumberDirectory:(PhoneNumberDirectoryFilter*)phoneNumberDirectoryFilter { + // note: clearing before setting so that torn reads can be detected + [self setValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_DATA_KEY toValue:nil]; + [self setValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_HASH_COUNT_KEY toValue:nil]; + [self setValueForKey:PHONE_DIRECTORY_EXPIRATION toValue:nil]; + if (phoneNumberDirectoryFilter == nil) return; + + NSData* data = [[phoneNumberDirectoryFilter bloomFilter] data]; + NSNumber* hashCount = [NSNumber numberWithUnsignedInteger:[[phoneNumberDirectoryFilter bloomFilter] hashCount]]; + NSDate* expiry = [phoneNumberDirectoryFilter getExpirationDate]; + [self setValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_DATA_KEY toValue:data]; + [self setValueForKey:PHONE_DIRECTORY_BLOOM_FILTER_HASH_COUNT_KEY toValue:hashCount]; + [self setValueForKey:PHONE_DIRECTORY_EXPIRATION toValue:expiry]; + [self sendDirectoryUpdateNotification]; +} + +-(void) sendDirectoryUpdateNotification{ + [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_DIRECTORY_UPDATE object:nil]; +} + +-(NSTimeInterval) getCachedOrDefaultDesiredBufferDepth { + id v = [self tryGetValueForKey:CALL_STREAM_DES_BUFFER_LEVEL_KEY]; + if (v == nil) return DEFAULT_CALL_STREAM_DES_BUFFER_LEVEL; + return [v doubleValue]; +} +-(void) setCachedDesiredBufferDepth:(double)value { + require(value >= 0); + [self setValueForKey:CALL_STREAM_DES_BUFFER_LEVEL_KEY toValue:[NSNumber numberWithDouble:value]]; +} + +-(int64_t) getAndIncrementOneTimeCounter { + __block int64_t oldCounter; + [self adjustAndTryGetNewValueForKey:PASSWORD_COUNTER_KEY afterAdjuster:^(id oldValue) { + oldCounter = [oldValue longLongValue]; + int64_t newCounter = (oldCounter == INT64_MAX) + ? INT64_MIN + : (oldCounter + 1); + return [NSNumber numberWithLongLong:newCounter]; + }]; + return oldCounter; +} + +-(void) setSettingsRowExpandedPrefs:(NSArray *)prefs { + [self setValueForKey:SETTINGS_EXPANDED_ROW_PREF_DICT_KEY toValue:prefs]; +} + +-(NSArray *) getOrGenerateSettingsRowExpandedPrefs { + NSArray *prefs = [self tryGetValueForKey:SETTINGS_EXPANDED_ROW_PREF_DICT_KEY]; + if (!prefs) { + prefs = @[[NSNumber numberWithBool:true], [NSNumber numberWithBool:true], [NSNumber numberWithBool:true], [NSNumber numberWithBool:true]]; + } + return prefs; +} + +-(NSArray *) getAvailableDateFormats { + return @[DATE_FORMAT_1, DATE_FORMAT_2, DATE_FORMAT_3, DATE_FORMAT_4, DATE_FORMAT_5, DATE_FORMAT_6]; +} + +- (NSString *)getDateFormat { + NSString *format = [self tryGetValueForKey:DATE_FORMAT_KEY]; + if (format) { + return format; + } else { + return DATE_FORMAT_1; + } +} + +- (NSString *)getDateFormatKey { + return DATE_FORMAT_KEY; +} +-(BOOL) getFreshInstallTutorialsEnabled { + NSNumber *preference = [self tryGetValueForKey:FRESH_INSTALL_TUTORIALS_ENABLED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return YES; + } +} +-(BOOL) getContactImagesEnabled { + NSNumber *preference = [self tryGetValueForKey:CONTACT_IMAGES_ENABLED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return YES; + } +} +-(BOOL) getAutocorrectEnabled { + NSNumber *preference = [self tryGetValueForKey:AUTOCORRECT_ENABLED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return YES; + } +} +-(BOOL) getHistoryLogEnabled { + NSNumber *preference = [self tryGetValueForKey:HISTORY_LOG_ENABLED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return YES; + } +} +-(BOOL) getAnonymousFeedbackEnabled { + NSNumber *preference = [self tryGetValueForKey:ANONYMOUS_FEEDBACK_ENABLED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return NO; + } +} + +-(BOOL) getIsRegistered { + NSNumber *preference = [self tryGetValueForKey:IS_REGISTERED_KEY]; + if (preference) { + return [preference boolValue]; + } else { + return NO; + } +} + +-(void) setDateFormat:(NSString *)format { + [self setValueForKey:DATE_FORMAT_KEY toValue:format]; +} + +-(void) setFreshInstallTutorialsEnabled:(BOOL)enabled { + [self setValueForKey:FRESH_INSTALL_TUTORIALS_ENABLED_KEY toValue:[NSNumber numberWithBool:enabled]]; +} +-(void) setContactImagesEnabled:(BOOL)enabled { + [self setValueForKey:CONTACT_IMAGES_ENABLED_KEY toValue:[NSNumber numberWithBool:enabled]]; +} +-(void) setAutocorrectEnabled:(BOOL)enabled { + [self setValueForKey:AUTOCORRECT_ENABLED_KEY toValue:[NSNumber numberWithBool:enabled]]; +} +-(void) setHistoryLogEnabled:(BOOL)enabled { + [self setValueForKey:HISTORY_LOG_ENABLED_KEY toValue:[NSNumber numberWithBool:enabled]]; +} +-(void) setAnonymousFeedbackEnabled:(BOOL)enabled { + [self setValueForKey:ANONYMOUS_FEEDBACK_ENABLED_KEY toValue:[NSNumber numberWithBool:enabled]]; +} +-(void) setIsRegistered:(BOOL)registered { + [self setValueForKey:IS_REGISTERED_KEY toValue:[NSNumber numberWithBool:registered]]; +} + +@end diff --git a/Signal/src/environment/PropertyListPreferences.h b/Signal/src/environment/PropertyListPreferences.h new file mode 100644 index 000000000..e1e15cb03 --- /dev/null +++ b/Signal/src/environment/PropertyListPreferences.h @@ -0,0 +1,15 @@ +#import + +@interface PropertyListPreferences : NSObject { +@private NSMutableDictionary* dictionary; +@private NSString* plistName; +} + ++(PropertyListPreferences*) propertyListPreferencesWithName:(NSString*)name; + +-(id) tryGetValueForKey:(NSString*)key; +-(void) setValueForKey:(NSString*)key toValue:(id)value; +-(id) adjustAndTryGetNewValueForKey:(NSString*)key afterAdjuster:(id (^)(id oldValue))adjuster; +-(void) clear; + +@end diff --git a/Signal/src/environment/PropertyListPreferences.m b/Signal/src/environment/PropertyListPreferences.m new file mode 100644 index 000000000..6b51b8958 --- /dev/null +++ b/Signal/src/environment/PropertyListPreferences.m @@ -0,0 +1,75 @@ +#import "PropertyListPreferences.h" +#import "Constraints.h" + +@implementation PropertyListPreferences + ++(PropertyListPreferences*) propertyListPreferencesWithName:(NSString*)name { + PropertyListPreferences* p = [PropertyListPreferences new]; + p->plistName = name; + p->dictionary = [[PropertyListPreferences readPlist:name] mutableCopy]; + return p; +} + +-(void) clear { + @synchronized(self) { + dictionary = [NSMutableDictionary dictionary]; + [PropertyListPreferences writePlist:dictionary withName:plistName]; + } +} ++(NSDictionary*) readPlist:(NSString*)name {require(name != nil); + NSString* documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"/Documents/"]; + NSString *path = [NSString stringWithFormat:@"%@/%@.plist", documentsDirectory, name]; + + NSData *plistData = [NSData dataWithContentsOfFile:path]; + // assume empty dictionary, if no data + if (plistData == nil) return @{}; + + NSString *error; + NSPropertyListFormat format; + id plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error]; + checkOperationDescribe(plist != nil, ([NSString stringWithFormat:@"Error parsing plist data: %@", error])); + checkOperationDescribe([plist isKindOfClass:[NSDictionary class]], @"Plist file didn't contain a dictionary"); + + return plist; +} ++(void) writePlist:(NSDictionary*)plist withName:(NSString*)name { + NSString *errorDesc; + NSData* xmlData = [NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc]; + checkOperationDescribe(xmlData != nil, ([NSString stringWithFormat:@"Error serializing plist: %@", errorDesc])); + + NSError* error; + NSString* documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"/Documents/"]; + NSString *path = [NSString stringWithFormat:@"%@/%@.plist",documentsDirectory,name]; + bool written = [xmlData writeToFile:path options:NSDataWritingAtomic error:&error]; + checkOperationDescribe(written, ([NSString stringWithFormat:@"Error atomically writing plist to file: %@", error])); +} + +-(id) tryGetValueForKey:(NSString *)key { + require(key != nil); + @synchronized(self) { + return [dictionary objectForKey:key]; + } +} +-(void) setValueForKey:(NSString *)key toValue:(id)value { + require(key != nil); + @synchronized(self) { + if (value == nil) { + [dictionary removeObjectForKey:key]; + } else { + [dictionary setObject:value forKey:key]; + } + [PropertyListPreferences writePlist:dictionary withName:plistName]; + } +} +-(id) adjustAndTryGetNewValueForKey:(NSString *)key afterAdjuster:(id (^)(id))adjuster { + require(key != nil); + require(adjuster != nil); + @synchronized(self) { + id oldValue = [self tryGetValueForKey:key]; + id newValue = adjuster(oldValue); + [self setValueForKey:key toValue:newValue]; + return newValue; + } +} + +@end diff --git a/Signal/src/environment/Release.h b/Signal/src/environment/Release.h new file mode 100644 index 000000000..625ffd249 --- /dev/null +++ b/Signal/src/environment/Release.h @@ -0,0 +1,22 @@ +#import +#import "Environment.h" +#import "DH3KKeyAgreementProtocol.h" +#import "EC25KeyAgreementProtocol.h" + +@interface Release : NSObject + +/// Connects to actual production infrastructure ++(Environment*) releaseEnvironmentWithLogging:(id)logging; + +/// Connects to external testing infrastructure ++(Environment*) pseudoEnvironmentWithLogging:(id)logging; + +// Connects to Whisper test infrastructure. ++(Environment*) releaseInteropEnvironmentWithLogging:(id)logging; + +/// Fake environment with no logging ++(Environment*) unitTestEnvironment:(NSArray*)testingAndLegacyOptions; + ++(DH3KKeyAgreementProtocol*) supportedDH3KKeyAgreementProtocol; + +@end diff --git a/Signal/src/environment/Release.m b/Signal/src/environment/Release.m new file mode 100644 index 000000000..80bb3a72e --- /dev/null +++ b/Signal/src/environment/Release.m @@ -0,0 +1,149 @@ +#import "Release.h" +#import "PropertyListPreferences.h" +#import "DiscardingLog.h" +#import "HostNameEndPoint.h" +#import "PhoneManager.h" +#import "RecentCallManager.h" +#import "PhoneNumberDirectoryFilterManager.h" + +#define RELEASE_ZRTP_CLIENT_ID [@"Whisper 000 " encodedAsAscii] +#define RELEASE_ZRTP_VERSION_ID [@"1.10" encodedAsAscii] + +#define TESTING_ZRTP_CLIENT_ID [@"RedPhone 019 " encodedAsAscii] +#define TESTING_ZRTP_VERSION_ID [@"1.10" encodedAsAscii] + +static unsigned char DH3K_PRIME[]={ + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2, + 0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1,0x29,0x02,0x4E,0x08,0x8A,0x67, + 0xCC,0x74,0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E, + 0x34,0x04,0xDD,0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D, + 0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45,0xE4,0x85,0xB5, + 0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B,0x0B,0xFF, + 0x5C,0xB6,0xF4,0x06,0xB7,0xED,0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE, + 0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, + 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3, + 0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,0x83,0x65,0x5D,0x23,0xDC,0xA3, + 0xAD,0x96,0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,0x9E,0xD5,0x29,0x07,0x70, + 0x96,0x96,0x6D,0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,0xF1,0x74,0x6C,0x08, + 0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,0xE3,0x9E,0x77, + 0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,0xB5,0xC5, + 0x5D,0xF0,0x6F,0x4C,0x52,0xC9,0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,0x39, + 0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10, + 0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D,0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A, + 0x33,0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64,0xEC,0xFB,0x85,0x04,0x58,0xDB, + 0xEF,0x0A,0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D,0xB3,0x97,0x0F,0x85,0xA6, + 0xE1,0xE4,0xC7,0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7,0x1E,0x8C,0x94,0xE0, + 0x4A,0x25,0x61,0x9D,0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B,0xF1,0x2F,0xFA, + 0x06,0xD9,0x8A,0x08,0x64,0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64,0x52,0x1F, + 0x2B,0x18,0x17,0x7B,0x20,0x0C,0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C,0x77, + 0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, + 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,0x4B,0x82,0xD1,0x20,0xA9,0x3A,0xD2, + 0xCA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +}; +#define DH3K_GENERATOR 2 + +@implementation Release + ++(Environment*) releaseEnvironmentWithLogging:(id)logging { + // @todo: turn off error notification + //ErrorHandlerBlock errorDiscarder = ^(id error, id relatedInfo, bool causedTermination) {}; + ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) { NSLog(@"%@: %@, %d", error, relatedInfo, causedTermination); }; + + return [Environment environmentWithPreferences:[PropertyListPreferences propertyListPreferencesWithName:@"RedPhone-Data"] + andLogging:logging + andErrorNoter:errorNoter + andServerPort:31337 + andMasterServerHostName:@"master.whispersystems.org" + andDefaultRelayName:@"relay" + andRelayServerHostNameSuffix:@"whispersystems.org" + andCertificate:[Certificate certificateFromResourcePath:@"whisperReal" ofType:@"der"] + andCurrentRegionCodeForPhoneNumbers:[(NSLocale*)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] + andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] + andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter] + andRecentCallManager:[RecentCallManager new] + andTestingAndLegacyOptions:@[ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER] + andZrtpClientId:RELEASE_ZRTP_CLIENT_ID + andZrtpVersionId:RELEASE_ZRTP_VERSION_ID + andContactsManager:[[ContactsManager alloc] init] + andPhoneDirectoryManager:[PhoneNumberDirectoryFilterManager new]]; +} + ++(Environment*) releaseInteropEnvironmentWithLogging:(id)logging { + // @todo: turn off error notification + //ErrorHandlerBlock errorDiscarder = ^(id error, id relatedInfo, bool causedTermination) {}; + ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) { NSLog(@"%@: %@, %d", error, relatedInfo, causedTermination); }; + + return [Environment environmentWithPreferences:[PropertyListPreferences propertyListPreferencesWithName:@"RedPhone-Data"] + andLogging:logging + andErrorNoter:errorNoter + andServerPort:31337 + andMasterServerHostName:@"testing.whispersystems.org" + andDefaultRelayName:@"testing" + andRelayServerHostNameSuffix:@"whispersystems.org" + andCertificate:[Certificate certificateFromResourcePath:@"whisperReal" ofType:@"der"] + andCurrentRegionCodeForPhoneNumbers:[(NSLocale*)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] + andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] + andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter] + andRecentCallManager:[RecentCallManager new] + andTestingAndLegacyOptions:@[ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER] + andZrtpClientId:RELEASE_ZRTP_CLIENT_ID + andZrtpVersionId:RELEASE_ZRTP_VERSION_ID + andContactsManager:[[ContactsManager alloc] init] + andPhoneDirectoryManager:[PhoneNumberDirectoryFilterManager new]]; +} + ++(Environment*) pseudoEnvironmentWithLogging:(id)logging { + ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) { NSLog(@"%@: %@, %d", error, relatedInfo, causedTermination); }; + + return [Environment environmentWithPreferences:[PropertyListPreferences propertyListPreferencesWithName:@"Pseudo-RedPhone-Data"] + andLogging:logging + andErrorNoter:errorNoter + andServerPort:31337 + andMasterServerHostName:@"testing.whispersystems.org" + andDefaultRelayName:@"testing" + andRelayServerHostNameSuffix:@"whispersystems.org" + andCertificate:[Certificate certificateFromResourcePath:@"whisperReal" ofType:@"der"] + andCurrentRegionCodeForPhoneNumbers:[(NSLocale*)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] + andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] + andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter] + andRecentCallManager:[RecentCallManager new] + andTestingAndLegacyOptions:@[] + andZrtpClientId:RELEASE_ZRTP_CLIENT_ID + andZrtpVersionId:RELEASE_ZRTP_VERSION_ID + andContactsManager:[[ContactsManager alloc] init] + andPhoneDirectoryManager:[PhoneNumberDirectoryFilterManager new]]; +} ++(Environment*) unitTestEnvironment:(NSArray*)testingAndLegacyOptions { + return [Environment environmentWithPreferences:[PropertyListPreferences propertyListPreferencesWithName:@"RedPhone-Test-Data"] + andLogging:[DiscardingLog discardingLog] + andErrorNoter:^(id error, id relatedInfo, bool causedTermination) {} + andServerPort:31337 + andMasterServerHostName:@"testing.whispersystems.org" + andDefaultRelayName:@"testing" + andRelayServerHostNameSuffix:@"whispersystems.org" + andCertificate:[Certificate certificateFromResourcePath:@"whisperReal" ofType:@"der"] + andCurrentRegionCodeForPhoneNumbers:@"US" + andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] + andPhoneManager:nil + andRecentCallManager:nil + andTestingAndLegacyOptions:testingAndLegacyOptions + andZrtpClientId:TESTING_ZRTP_CLIENT_ID + andZrtpVersionId:TESTING_ZRTP_VERSION_ID + andContactsManager:nil + andPhoneDirectoryManager:nil]; +} + ++(NSArray*) supportedKeyAgreementProtocols { + return @[ + [EC25KeyAgreementProtocol new], + [Release supportedDH3KKeyAgreementProtocol] + ]; +} + ++(DH3KKeyAgreementProtocol*) supportedDH3KKeyAgreementProtocol { + NSData* prime = [NSData dataWithBytes:DH3K_PRIME length:sizeof(DH3K_PRIME)]; + NSData* generator = [NSData dataWithSingleByte:DH3K_GENERATOR]; + return [DH3KKeyAgreementProtocol protocolWithModulus:prime andGenerator:generator]; +} + +@end diff --git a/Signal/src/network/IpAddress.h b/Signal/src/network/IpAddress.h new file mode 100644 index 000000000..d6c66438f --- /dev/null +++ b/Signal/src/network/IpAddress.h @@ -0,0 +1,34 @@ +#import +#import + +@class IpEndPoint; + +/** + * + * Stores an ip address. + * Supports both ipv4 and ipv6 addresses. + * +**/ + +@interface IpAddress : NSObject { +@private bool isIpv4; +@private bool isIpv6; +@private struct sockaddr_in ipv4Data; +@private struct sockaddr_in6 ipv6Data; +} + ++(IpAddress*) localhost; + ++(IpAddress*) tryGetIpAddressFromString:(NSString*)text; ++(IpAddress*) ipAddressFromString:(NSString*)text; ++(IpAddress*) ipv4AddressFromString:(NSString*)text; ++(IpAddress*) ipv6AddressFromString:(NSString*)text; + ++(IpAddress*) ipv4AddressFromSockaddr:(struct sockaddr_in)sockaddr; ++(IpAddress*) ipv6AddressFromSockaddr:(struct sockaddr_in6)sockaddr; + +-(IpEndPoint*) withPort:(in_port_t)port; +-(NSData*) sockaddrData; +-(NSData*) sockaddrDataWithPort:(in_port_t)port; + +@end diff --git a/Signal/src/network/IpAddress.m b/Signal/src/network/IpAddress.m new file mode 100644 index 000000000..d461ad8fa --- /dev/null +++ b/Signal/src/network/IpAddress.m @@ -0,0 +1,138 @@ +#import "IpAddress.h" +#import "Util.h" +#import "Constraints.h" +#import "IpEndPoint.h" + +#define LOCAL_HOST_IP @"127.0.0.1" + +@implementation IpAddress + ++(IpAddress*) localhost { + return [IpAddress ipv4AddressFromString:LOCAL_HOST_IP]; +} ++(IpAddress*) ipAddressFromString:(NSString*)text { + require(text != nil); + if ([IpAddress isIpv4Text:text]) return [IpAddress ipv4AddressFromString:text]; + if ([IpAddress isIpv6Text:text]) return [IpAddress ipv6AddressFromString:text]; + [BadArgument raise:[NSString stringWithFormat:@"Invalid IP address: %@", text]]; + return nil; +} ++(IpAddress*) tryGetIpAddressFromString:(NSString*)text { + require(text != nil); + if ([IpAddress isIpv4Text:text]) return [IpAddress ipv4AddressFromString:text]; + if ([IpAddress isIpv6Text:text]) return [IpAddress ipv6AddressFromString:text]; + return nil; +} ++(IpAddress*) ipv4AddressFromString:(NSString*)text { + require(text != nil); + + IpAddress* a = [IpAddress new]; + + struct sockaddr_in s; + memset(&s, 0, sizeof(struct sockaddr_in)); + s.sin_len = sizeof(s); + s.sin_family = AF_INET; + int inet_pton_result = inet_pton(AF_INET, [text UTF8String], &(s.sin_addr)); + + if (inet_pton_result == -1) { + [BadArgument raise:[NSString stringWithFormat:@"Error parsing IPv4 address: %@, %s", text, strerror(errno)]]; + } + if (inet_pton_result != +1) { + [BadArgument raise:[NSString stringWithFormat:@"Invalid IPv4 address: %@", text]]; + } + + a->isIpv4 = true; + a->ipv4Data = s; + return a; +} ++(IpAddress*) ipv6AddressFromString:(NSString*)text { + require(text != nil); + + IpAddress* a = [IpAddress new]; + + struct sockaddr_in6 s; + memset(&s, 0, sizeof(struct sockaddr_in6)); + s.sin6_len = sizeof(s); + s.sin6_family = AF_INET6; + int inet_pton_result = inet_pton(AF_INET6, [text UTF8String], &(s.sin6_addr)); + + if (inet_pton_result == -1) { + [BadArgument raise:[NSString stringWithFormat:@"Error parsing IPv6 address: %@, %s", text, strerror(errno)]]; + } + if (inet_pton_result != +1) { + [BadArgument raise:[NSString stringWithFormat:@"Invalid IPv6 address: %@", text]]; + } + + a->ipv6Data = s; + a->isIpv6 = true; + return a; +} + ++(IpAddress*) ipv4AddressFromSockaddr:(struct sockaddr_in)sockaddr { + IpAddress* a = [IpAddress new]; + a->ipv4Data = sockaddr; + a->isIpv4 = true; + return a; +} ++(IpAddress*) ipv6AddressFromSockaddr:(struct sockaddr_in6)sockaddr { + IpAddress* a = [IpAddress new]; + a->ipv6Data = sockaddr; + a->isIpv6 = true; + return a; +} + +-(IpEndPoint*) withPort:(in_port_t)port { + return [IpEndPoint ipEndPointAtAddress:self onPort:port]; +} + +-(NSData*) sockaddrData { + return [self sockaddrDataWithPort:0]; +} +-(NSData*) sockaddrDataWithPort:(in_port_t)port { + requireState(isIpv4 || isIpv6); + if (isIpv4) { + struct sockaddr_in s = ipv4Data; + s.sin_port = htons(port); + NSMutableData* d = [NSMutableData dataWithLength:sizeof(struct sockaddr_in)]; + memcpy([d mutableBytes], &s, sizeof(struct sockaddr_in)); + return d; + } else { + struct sockaddr_in6 s = ipv6Data; + s.sin6_port = htons(port); + NSMutableData* d = [NSMutableData dataWithLength:sizeof(struct sockaddr_in6)]; + memcpy([d mutableBytes], &s, sizeof(struct sockaddr_in6)); + return d; + } +} + +-(NSString*) description { + requireState(isIpv4 || isIpv6); + return isIpv4 + ? [IpAddress ipv4AddressToString:&ipv4Data] + : [IpAddress ipv6AddressToString:&ipv6Data]; +} + ++(bool) isIpv4Text:(NSString*)text { + require(text != nil); + struct sockaddr_in s; + return inet_pton(AF_INET, [text UTF8String], &(s.sin_addr)) == 1; +} ++(bool) isIpv6Text:(NSString*)text { + require(text != nil); + struct sockaddr_in6 s; + return inet_pton(AF_INET6, [text UTF8String], &(s.sin6_addr)) == 1; +} ++(NSString*) ipv4AddressToString:(const struct sockaddr_in*)addr { + char buffer[INET_ADDRSTRLEN]; + const char* result = inet_ntop(AF_INET, &addr->sin_addr, buffer, INET_ADDRSTRLEN); + checkOperationDescribe(result != NULL, @"Invalid ipv4 address data"); + return [NSString stringWithUTF8String:result]; +} ++(NSString*) ipv6AddressToString:(const struct sockaddr_in6*)addr { + char buffer[INET6_ADDRSTRLEN]; + const char* result = inet_ntop(AF_INET6, &addr->sin6_addr, buffer, INET6_ADDRSTRLEN); + checkOperationDescribe(result != NULL, @"Invalid ipv6 address data"); + return [NSString stringWithUTF8String:result]; +} + +@end diff --git a/Signal/src/network/IpEndPoint.h b/Signal/src/network/IpEndPoint.h new file mode 100644 index 000000000..319baf150 --- /dev/null +++ b/Signal/src/network/IpEndPoint.h @@ -0,0 +1,34 @@ +#import +#import +#import "NetworkEndPoint.h" + +@class IpAddress; + +/** + * + * An ip address and port, identifying a network endpoint to/from which connections/data can be-sent/arrive-from. + * Supports both ipv4 and ipv6 addresses. + * + * Used for interop with sockaddr structures. + * +**/ + +@interface IpEndPoint : NSObject { +@private IpAddress* address; +@private in_port_t port; +} + ++(IpEndPoint*) ipEndPointAtAddress:(IpAddress*)address + onPort:(in_port_t)port; + ++(IpEndPoint*) ipEndPointAtUnspecifiedAddressOnPort:(in_port_t)port; + ++(IpEndPoint*) ipEndPointFromSockaddrData:(NSData*)sockaddrData; ++(IpEndPoint*) ipv4EndPointFromSockaddrData:(NSData*)sockaddrData; ++(IpEndPoint*) ipv6EndPointFromSockaddrData:(NSData*)sockaddrData; + +-(in_port_t) port; +-(IpAddress*) address; +-(NSData*) sockaddrData; + +@end diff --git a/Signal/src/network/IpEndPoint.m b/Signal/src/network/IpEndPoint.m new file mode 100644 index 000000000..571ab8863 --- /dev/null +++ b/Signal/src/network/IpEndPoint.m @@ -0,0 +1,97 @@ +#import "IpEndPoint.h" +#import "Util.h" +#import "Constraints.h" +#import "IpAddress.h" +#import "FutureSource.h" +#import "DnsManager.h" + +@implementation IpEndPoint + ++(IpEndPoint*) ipEndPointAtAddress:(IpAddress*)address + onPort:(in_port_t)port { + require(address != nil); + IpEndPoint* p = [IpEndPoint new]; + p->address = address; + p->port = port; + return p; +} + ++(IpEndPoint*) ipEndPointAtUnspecifiedAddressOnPort:(in_port_t)port { + struct sockaddr_in s; + memset(&s, 0, sizeof(struct sockaddr_in)); + s.sin_len = sizeof(struct sockaddr_in); + s.sin_family = AF_INET; + s.sin_port = htons(port); + + IpEndPoint* a = [IpEndPoint new]; + a->address = [IpAddress ipv4AddressFromString:@"0.0.0.0"]; + a->port = port; + return a; +} + ++(IpEndPoint*) ipEndPointFromSockaddrData:(NSData*)sockaddrData { + require(sockaddrData != nil); + require([sockaddrData length] >= sizeof(struct sockaddr)); + + struct sockaddr s; + memcpy(&s, [sockaddrData bytes], sizeof(struct sockaddr)); + + if (s.sa_family == AF_INET) return [IpEndPoint ipv4EndPointFromSockaddrData:sockaddrData]; + if (s.sa_family == AF_INET6) return [IpEndPoint ipv6EndPointFromSockaddrData:sockaddrData]; + + [BadArgument raise:[NSString stringWithFormat:@"Unrecognized sockaddr family: %d", s.sa_family]]; + return nil; +} ++(IpEndPoint*) ipv4EndPointFromSockaddrData:(NSData*)sockaddrData { + require(sockaddrData != nil); + require([sockaddrData length] >= sizeof(struct sockaddr_in)); + + struct sockaddr_in s; + memcpy(&s, [sockaddrData bytes], sizeof(struct sockaddr_in)); + + return [[IpAddress ipv4AddressFromSockaddr:s] withPort:ntohs(s.sin_port)]; +} ++(IpEndPoint*) ipv6EndPointFromSockaddrData:(NSData*)sockaddrData { + require(sockaddrData != nil); + require([sockaddrData length] >= sizeof(struct sockaddr_in6)); + + struct sockaddr_in6 s; + memcpy(&s, [sockaddrData bytes], sizeof(struct sockaddr_in6)); + + return [[IpAddress ipv6AddressFromSockaddr:s] withPort:ntohs(s.sin6_port)]; +} + +-(IpAddress*) address { + return address; +} +-(in_port_t) port { + return port; +} +-(NSData*) sockaddrData { + return [address sockaddrDataWithPort:port]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"Address: %@, port: %d", address, port]; +} + +-(void) handleStreamsOpened:(StreamPair *)streamPair { + // no work needed +} +-(Future*) asyncHandleStreamsConnected:(StreamPair *)streamPair { + return [Future finished:@YES]; +} + +-(StreamPair*)createStreamPair { + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)[address description], port, &readStream, &writeStream); + return [StreamPair streamPairWithInput:(__bridge_transfer NSInputStream*)readStream + andOutput:(__bridge_transfer NSOutputStream*)writeStream]; +} + +-(Future*) asyncResolveToSpecificEndPointsUnlessCancelled:(id)unlessCancelledToken { + return [Future finished:@[self]]; +} + +@end diff --git a/Signal/src/network/NetworkEndPoint.h b/Signal/src/network/NetworkEndPoint.h new file mode 100644 index 000000000..06e6337a8 --- /dev/null +++ b/Signal/src/network/NetworkEndPoint.h @@ -0,0 +1,25 @@ +#import +#import "CancelToken.h" +#import "StreamPair.h" +#import "Future.h" + +/// Describes a location to which you can connect and communicate. +@protocol NetworkEndPoint + +/// Creates a pair of read/write streams to this end point. +-(StreamPair*) createStreamPair; + +/// Invoked when a stream pair has opened (tcp handshake completed), but before it is necessary writable. +/// (The time to set any options on the stream.) +-(void) handleStreamsOpened:(StreamPair*)streamPair; + +/// Invoked when a stream pair is ready for read/write. +/// (The time to authenticate certificates of a completed SSL connection.) +-(Future*) asyncHandleStreamsConnected:(StreamPair*)streamPair; + +/// Resolves this general end point into underlying associated specific end points. +/// For example, a hostname+port end point resolves into one or more ip+port end points. +/// The asynchronous result has type Future(Array(NetworkEndPoint)). +-(Future*) asyncResolveToSpecificEndPointsUnlessCancelled:(id)unlessCancelledToken; + +@end diff --git a/Signal/src/network/PacketHandler.h b/Signal/src/network/PacketHandler.h new file mode 100644 index 000000000..fe458f2ba --- /dev/null +++ b/Signal/src/network/PacketHandler.h @@ -0,0 +1,28 @@ +#import + +typedef void (^PacketHandlerBlock)(id packet); +typedef void (^ErrorHandlerBlock)(id error, id relatedInfo, bool causedTermination); + +/** + * + * A PacketHandler is a block to call for received values, and a block to call when minor or major error occur. + * + * Most of the socket types we use are started by giving them a packet handler. + * + **/ + +@interface PacketHandler : NSObject + +@property (readonly,nonatomic) PacketHandlerBlock dataHandler; +@property (readonly,nonatomic) ErrorHandlerBlock errorHandler; + ++(PacketHandler*) packetHandler:(PacketHandlerBlock)dataHandler + withErrorHandler:(ErrorHandlerBlock)errorHandler; + +-(void) handlePacket:(id)packet; + +-(void) handleError:(id)error + relatedInfo:(id)packet + causedTermination:(bool)causedTermination; + +@end diff --git a/Signal/src/network/PacketHandler.m b/Signal/src/network/PacketHandler.m new file mode 100644 index 000000000..e1f007cfa --- /dev/null +++ b/Signal/src/network/PacketHandler.m @@ -0,0 +1,31 @@ +#import "PacketHandler.h" +#import "Constraints.h" + +@implementation PacketHandler + +@synthesize dataHandler, errorHandler; + ++(PacketHandler*) packetHandler:(PacketHandlerBlock)dataHandler + withErrorHandler:(ErrorHandlerBlock)errorHandler { + + require(dataHandler != nil); + require(errorHandler != nil); + + PacketHandler* p = [PacketHandler new]; + p->dataHandler = [dataHandler copy]; + p->errorHandler = [errorHandler copy]; + return p; +} + +-(void) handlePacket:(id)packet { + dataHandler(packet); +} + +-(void) handleError:(id)error + relatedInfo:(id)relatedInfo + causedTermination:(bool)causedTermination { + + errorHandler(error, relatedInfo, causedTermination); +} + +@end diff --git a/Signal/src/network/dns/DnsManager.h b/Signal/src/network/dns/DnsManager.h new file mode 100644 index 000000000..986151361 --- /dev/null +++ b/Signal/src/network/dns/DnsManager.h @@ -0,0 +1,21 @@ +#import +#import "FutureSource.h" +#import "CancelToken.h" +#import "IpAddress.h" + +/** + * + * DnsManager implements utility methods for querying the addresses/CName associated with a domain name. + * + **/ + +@interface DnsManager : NSObject { +@private CFStreamError error; +@public FutureSource* futureResultSource; +} + +/// Result has type Future(Array(IpAddress)) ++(Future*) asyncQueryAddressesForDomainName:(NSString*)domainName + unlessCancelled:(id)unlessCancelledToken; + +@end diff --git a/Signal/src/network/dns/DnsManager.m b/Signal/src/network/dns/DnsManager.m new file mode 100644 index 000000000..254e97750 --- /dev/null +++ b/Signal/src/network/dns/DnsManager.m @@ -0,0 +1,78 @@ +#import "CancelTokenSource.h" +#import "Constraints.h" +#import "DnsManager.h" +#import "HttpResponse.h" +#import "IpEndPoint.h" +#import "UdpSocket.h" +#import "Util.h" +#import +#import "ThreadManager.h" + +#define STRING_POINTER_FLAG 0xc0 + +#define gethostbyname_ErrorDescriptions @{ \ + @HOST_NOT_FOUND: @"HOST_NOT_FOUND. The specified host is unknown.", \ + @NO_ADDRESS: @"NO_ADDRESS. The requested name is valid but does not have an IP address.", \ + @NO_DATA: @"NO_DATA. The requested name is valid but does not have an IP address.", \ + @NO_RECOVERY: @"NO_RECOVERY. A nonrecoverable name server error occurred.", \ + @TRY_AGAIN: @"TRY_AGAIN. A temporary error occurred on an authoritative name server." \ +} + +@implementation DnsManager + +void handleDnsCompleted(CFHostRef, CFHostInfoType, const CFStreamError*, void*); +void handleDnsCompleted(CFHostRef hostRef, CFHostInfoType typeInfo, const CFStreamError* error, void* info) { + DnsManager* instance = (__bridge_transfer DnsManager*)info; + + @try { + Boolean gotHostAddressesData = false; + NSArray* addressDatas = (__bridge NSArray*)CFHostGetAddressing(hostRef, &gotHostAddressesData); + checkOperation(gotHostAddressesData); + checkOperationDescribe(addressDatas != nil, @"No addresses for host"); + + NSArray* ips = [addressDatas map:^(id addressData) { + checkOperation([addressData isKindOfClass:[NSData class]]); + + return [[IpEndPoint ipEndPointFromSockaddrData:addressData] address]; + }]; + + [instance->futureResultSource trySetResult:ips]; + } @catch (OperationFailed* ex) { + [instance->futureResultSource trySetFailure:ex]; + } +} + ++(Future*) asyncQueryAddressesForDomainName:(NSString*)domainName + unlessCancelled:(id)unlessCancelledToken { + require(domainName != nil); + + CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)domainName); + checkOperation(hostRef != nil); + + DnsManager* d = [DnsManager new]; + d->futureResultSource = [FutureSource new]; + + CFHostClientContext c; + c.version = 0; + c.info = (__bridge_retained void*)d; + c.release = CFRelease; + c.retain = CFRetain; + c.copyDescription = CFCopyDescription; + + CFHostSetClient(hostRef, handleDnsCompleted, &c); + CFHostScheduleWithRunLoop(hostRef, + [[ThreadManager normalLatencyThreadRunLoop] getCFRunLoop], + kCFRunLoopDefaultMode); + + Boolean startedSuccess = CFHostStartInfoResolution(hostRef, kCFHostAddresses, &d->error); + CFRelease(hostRef); + if (!startedSuccess) { + [d->futureResultSource trySetFailure:[OperationFailed new:[NSString stringWithFormat:@"DNS query failed to start. Error code: %ld", + d->error.error]]]; + } + [unlessCancelledToken whenCancelledTryCancel:d->futureResultSource]; + + return d->futureResultSource; +} + +@end diff --git a/Signal/src/network/dns/HostNameEndPoint.h b/Signal/src/network/dns/HostNameEndPoint.h new file mode 100644 index 000000000..6500f9861 --- /dev/null +++ b/Signal/src/network/dns/HostNameEndPoint.h @@ -0,0 +1,17 @@ +#import +#import "NetworkEndPoint.h" + +/** + * + * Stores the port and hostname for a resolvable network end point + * +**/ + +@interface HostNameEndPoint : NSObject +@property (nonatomic, readonly) in_port_t port; +@property (nonatomic, readonly) NSString* hostname; + ++(HostNameEndPoint*) hostNameEndPointWithHostName:(NSString*)hostname + andPort:(in_port_t)port; + +@end diff --git a/Signal/src/network/dns/HostNameEndPoint.m b/Signal/src/network/dns/HostNameEndPoint.m new file mode 100644 index 000000000..06cbe6001 --- /dev/null +++ b/Signal/src/network/dns/HostNameEndPoint.m @@ -0,0 +1,50 @@ +#import "HostNameEndPoint.h" +#import "DnsManager.h" +#import "IpEndPoint.h" +#import "ThreadManager.h" +#import "Util.h" + +@implementation HostNameEndPoint +@synthesize hostname, port; + ++(HostNameEndPoint*) hostNameEndPointWithHostName:(NSString*)hostname + andPort:(in_port_t)port { + require(hostname != nil); + require(port > 0); + + HostNameEndPoint* h = [HostNameEndPoint new]; + h->hostname = [hostname copy]; // avoid mutability + h->port = port; + return h; +} + +-(void) handleStreamsOpened:(StreamPair *)streamPair { + // no work needed +} +-(Future *)asyncHandleStreamsConnected:(StreamPair *)streamPair { + return [Future finished:@YES]; +} + +-(StreamPair*)createStreamPair { + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostname, port, &readStream, &writeStream); + return [StreamPair streamPairWithInput:(__bridge_transfer NSInputStream*)readStream + andOutput:(__bridge_transfer NSOutputStream*)writeStream]; +} + +-(Future*) asyncResolveToSpecificEndPointsUnlessCancelled:(id)unlessCancelledToken { + Future* futureDnsResult = [DnsManager asyncQueryAddressesForDomainName:hostname + unlessCancelled:unlessCancelledToken]; + return [futureDnsResult then:^(NSArray* ipAddresses) { + return [ipAddresses map:^(IpAddress* address) { + return [IpEndPoint ipEndPointAtAddress:address onPort:port]; + }]; + }]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"%@:%d", hostname, port]; +} + +@end diff --git a/Signal/src/network/failures/IgnoredPacketFailure.h b/Signal/src/network/failures/IgnoredPacketFailure.h new file mode 100644 index 000000000..265149b62 --- /dev/null +++ b/Signal/src/network/failures/IgnoredPacketFailure.h @@ -0,0 +1,14 @@ +#import + +/** + * + * Instances of IgnoredPacketFailure are used to indicate that a packet was ignored. + * + */ +@interface IgnoredPacketFailure : NSObject { +@private NSString* reason; +} + ++(IgnoredPacketFailure*) new:(NSString*)reason; + +@end diff --git a/Signal/src/network/failures/IgnoredPacketFailure.m b/Signal/src/network/failures/IgnoredPacketFailure.m new file mode 100644 index 000000000..926a86a5a --- /dev/null +++ b/Signal/src/network/failures/IgnoredPacketFailure.m @@ -0,0 +1,14 @@ +#import "IgnoredPacketFailure.h" + +@implementation IgnoredPacketFailure + ++(IgnoredPacketFailure*) new:(NSString*)reason { + IgnoredPacketFailure* instance = [IgnoredPacketFailure new]; + instance->reason = reason; + return instance; +} + +-(NSString *)description { + return [NSString stringWithFormat:@"Ignored: %@", reason]; +} +@end diff --git a/Signal/src/network/failures/UnrecognizedRequestFailure.h b/Signal/src/network/failures/UnrecognizedRequestFailure.h new file mode 100644 index 000000000..f01d2c0f3 --- /dev/null +++ b/Signal/src/network/failures/UnrecognizedRequestFailure.h @@ -0,0 +1,14 @@ +#import + +/** + * + * Instances of UnrecognizedRequestFailure are used to indicate that a request could not be handled due to being strange. + * + */ +@interface UnrecognizedRequestFailure : NSObject { +@private NSString* reason; +} + ++(UnrecognizedRequestFailure*) new:(NSString*)reason; + +@end diff --git a/Signal/src/network/failures/UnrecognizedRequestFailure.m b/Signal/src/network/failures/UnrecognizedRequestFailure.m new file mode 100644 index 000000000..16dbecf36 --- /dev/null +++ b/Signal/src/network/failures/UnrecognizedRequestFailure.m @@ -0,0 +1,14 @@ +#import "UnrecognizedRequestFailure.h" + +@implementation UnrecognizedRequestFailure + ++(UnrecognizedRequestFailure*) new:(NSString*)reason { + UnrecognizedRequestFailure* instance = [UnrecognizedRequestFailure new]; + instance->reason = reason; + return instance; +} + +-(NSString *)description { + return [NSString stringWithFormat:@"Unrecognized request: %@", reason]; +} +@end diff --git a/Signal/src/network/http/HttpManager.h b/Signal/src/network/http/HttpManager.h new file mode 100644 index 000000000..7958fd0fa --- /dev/null +++ b/Signal/src/network/http/HttpManager.h @@ -0,0 +1,50 @@ +#import +#import "CancelTokenSource.h" +#import "NetworkEndPoint.h" +#import "Logging.h" +#import "Future.h" +#import "HttpRequestOrResponse.h" +#import "Terminable.h" +#import "Queue.h" +#import "CancelToken.h" +#import "PacketHandler.h" +#import "HttpSocket.h" + +/** + * + * HttpManager handles asynchronously performing and responding to http requests/responses. + * + */ +@interface HttpManager : NSObject { +@private HttpSocket* httpChannel; +@private Queue* eventualResponseQueue; +@private bool isStarted; +@private CancelTokenSource* lifetime; +} + ++(HttpManager*) httpManagerFor:(HttpSocket*)httpSocket + untilCancelled:(id)untilCancelledToken; + ++(HttpManager*) startWithEndPoint:(id)endPoint + untilCancelled:(id)untilCancelledToken; + +-(Future*) asyncResponseForRequest:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken; + +-(Future*) asyncOkResponseForRequest:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken; + +-(void) startWithRejectingRequestHandlerAndErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken; + +-(void) startWithRequestHandler:(HttpResponse*(^)(HttpRequest* remoteRequest))requestHandler + andErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken; + +-(void) terminateWhenDoneCurrentWork; + ++(Future*) asyncOkResponseFromMasterServer:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken + andErrorHandler:(ErrorHandlerBlock)errorHandler; + +@end diff --git a/Signal/src/network/http/HttpManager.m b/Signal/src/network/http/HttpManager.m new file mode 100644 index 000000000..ab94122f9 --- /dev/null +++ b/Signal/src/network/http/HttpManager.m @@ -0,0 +1,164 @@ +#import "HttpManager.h" +#import "FutureSource.h" +#import "NetworkStream.h" +#import "HttpSocket.h" +#import "Util.h" + +@implementation HttpManager + ++(HttpManager*) httpManagerFor:(HttpSocket*)httpSocket + untilCancelled:(id)untilCancelledToken { + require(httpSocket != nil); + + HttpManager* m = [HttpManager new]; + m->httpChannel = httpSocket; + m->eventualResponseQueue = [Queue new]; + m->lifetime = [CancelTokenSource cancelTokenSource]; + [untilCancelledToken whenCancelledTerminate:m]; + return m; +} ++(HttpManager*) startWithEndPoint:(id)endPoint + untilCancelled:(id)untilCancelledToken { + require(endPoint != nil); + + NetworkStream* dataChannel = [NetworkStream networkStreamToEndPoint:endPoint]; + + HttpSocket* httpChannel = [HttpSocket httpSocketOver:dataChannel]; + + return [HttpManager httpManagerFor:httpChannel + untilCancelled:untilCancelledToken]; +} +-(Future*) asyncResponseForRequest:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken { + + require(request != nil); + requireState(isStarted); + + @try { + FutureSource* ev = [FutureSource new]; + [unlessCancelledToken whenCancelledTryCancel:ev]; + @synchronized (self) { + if ([[lifetime getToken] isAlreadyCancelled]) { + return [Future failed:@"terminated"]; + } + [eventualResponseQueue enqueue:ev]; + } + [httpChannel send:[HttpRequestOrResponse httpRequestOrResponse:request]]; + return ev; + } @catch (OperationFailed* ex) { + return [Future failed:ex]; + } +} ++(Future*) asyncOkResponseFromMasterServer:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken + andErrorHandler:(ErrorHandlerBlock)errorHandler { + require(request != nil); + require(errorHandler != nil); + + HttpManager* manager = [HttpManager startWithEndPoint:[Environment getMasterServerSecureEndPoint] + untilCancelled:unlessCancelledToken]; + + [manager startWithRejectingRequestHandlerAndErrorHandler:errorHandler + untilCancelled:nil]; + + Future* result = [manager asyncOkResponseForRequest:request + unlessCancelled:unlessCancelledToken]; + + [manager terminateWhenDoneCurrentWork]; + + return result; +} +-(Future*) asyncOkResponseForRequest:(HttpRequest*)request + unlessCancelled:(id)unlessCancelledToken { + + require(request != nil); + + Future* futureResponse = [self asyncResponseForRequest:request + unlessCancelled:unlessCancelledToken]; + + return [futureResponse then:^(HttpResponse* response) { + if (![response isOkResponse]) return [Future failed:response]; + return [Future finished:response]; + }]; +} +-(void) startWithRejectingRequestHandlerAndErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken { + require(errorHandler != nil); + + HttpResponse*(^requestHandler)(HttpRequest* remoteRequest) = ^(HttpRequest* remoteRequest) { + errorHandler(@"Rejecting Requests", remoteRequest, false); + return [HttpResponse httpResponse501NotImplemented]; + }; + + [self startWithRequestHandler:requestHandler + andErrorHandler:errorHandler + untilCancelled:untilCancelledToken]; +} + +-(void) startWithRequestHandler:(HttpResponse*(^)(HttpRequest* remoteRequest))requestHandler + andErrorHandler:(ErrorHandlerBlock)errorHandler + untilCancelled:(id)untilCancelledToken { + + require(requestHandler != nil); + require(errorHandler != nil); + + @synchronized(self) { + requireState(!isStarted); + isStarted = true; + } + + ErrorHandlerBlock clearOnSeriousError = ^(id error, id relatedInfo, bool causedTermination) { + if (causedTermination) [self clearQueue:error]; + errorHandler(error, relatedInfo, causedTermination); + }; + + PacketHandlerBlock httpHandler = ^(HttpRequestOrResponse* requestOrResponse) { + require(requestOrResponse != nil); + require([requestOrResponse isKindOfClass:[HttpRequestOrResponse class]]); + @synchronized (self) { + if ([requestOrResponse isRequest]) { + HttpResponse* response = requestHandler([requestOrResponse request]); + requireState(response != nil); + [httpChannel send:[HttpRequestOrResponse httpRequestOrResponse:response]]; + } else if ([eventualResponseQueue count] == 0) { + errorHandler(@"Response when no requests queued", [requestOrResponse response], false); + } else { + FutureSource* ev = [eventualResponseQueue dequeue]; + [ev trySetResult:[requestOrResponse response]]; + } + } + }; + + [httpChannel startWithHandler:[PacketHandler packetHandler:httpHandler + withErrorHandler:clearOnSeriousError] + untilCancelled:[lifetime getToken]]; + + [untilCancelledToken whenCancelledTerminate:self]; +} +-(void) clearQueue:(id)error { + @synchronized (self) { + while ([eventualResponseQueue count] > 0) { + [[eventualResponseQueue dequeue] trySetFailure:error]; + } + } +} +-(void) terminateWhenDoneCurrentWork { + @synchronized (self) { + if ([eventualResponseQueue count] == 0) { + [self terminate]; + } else { + FutureSource* v = [eventualResponseQueue peekAt:[eventualResponseQueue count]-1]; + [v finallyDo:^(id _) { + [self terminate]; + }]; + } + } +} +-(void) terminate { + @synchronized (self) { + [lifetime cancel]; + [self clearQueue:@"Terminated before response"]; + } +} + +@end diff --git a/Signal/src/network/http/HttpRequest.h b/Signal/src/network/http/HttpRequest.h new file mode 100644 index 000000000..775106cbc --- /dev/null +++ b/Signal/src/network/http/HttpRequest.h @@ -0,0 +1,45 @@ +#import +#import "PhoneNumber.h" + +@interface HttpRequest : NSObject + ++(HttpRequest*)httpRequestUnauthenticatedWithMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody; + ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody + andLocalNumber:(PhoneNumber*)localNumber + andPassword:(NSString*)password; + ++(HttpRequest*)httpRequestWithOtpAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody + andLocalNumber:(PhoneNumber*)localNumber + andPassword:(NSString*)password + andCounter:(int64_t)counter; + ++(HttpRequest*) httpRequestFromData:(NSData*)data; ++(HttpRequest*)httpRequestWithMethod:(NSString*)method + andLocation:(NSString*)location + andHeaders:(NSDictionary*)headers + andOptionalBody:(NSString*)optionalBody; + +-(NSString*) toHttp; +-(NSData*) serialize; +-(NSString*) method; +-(NSString*) location; +-(NSString*) optionalBody; +-(NSDictionary*) headers; + +-(bool) isEqualToHttpRequest:(HttpRequest*)other; + ++(NSString*) computeOtpAuthorizationTokenForLocalNumber:(PhoneNumber*)localNumber + andCounterValue:(int64_t)counterValue + andPassword:(NSString*)password; + ++(NSString*) computeBasicAuthorizationTokenForLocalNumber:(PhoneNumber*)localNumber + andPassword:(NSString*)password; + +@end diff --git a/Signal/src/network/http/HttpRequest.m b/Signal/src/network/http/HttpRequest.m new file mode 100644 index 000000000..d84692315 --- /dev/null +++ b/Signal/src/network/http/HttpRequest.m @@ -0,0 +1,163 @@ +#import "HttpRequest.h" +#import "Util.h" +#import "CryptoTools.h" +#import "Constraints.h" +#import "HttpRequestOrResponse.h" + +@interface HttpRequest () +@property NSString* method; +@property NSString* location; +@property NSString* optionalBody; +@property NSDictionary* headers; + +@end + +@implementation HttpRequest + ++(HttpRequest*)httpRequestWithMethod:(NSString*)method andLocation:(NSString*)location andHeaders:(NSDictionary*)headers andOptionalBody:(NSString*)optionalBody { + require(method != nil); + require(location != nil); + require(headers != nil); + require((optionalBody == nil) == ([headers objectForKey:@"Content-Length"] == nil)); + require(optionalBody == nil || [[[NSNumber numberWithUnsignedInteger:[optionalBody length]] description] isEqualToString:[headers objectForKey:@"Content-Length"]]); + + HttpRequest* s = [HttpRequest new]; + s->_method = method; + s->_location = location; + s->_optionalBody = optionalBody; + s->_headers = headers; + return s; +} ++(HttpRequest*)httpRequestUnauthenticatedWithMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody { + require(method != nil); + require(location != nil); + + NSMutableDictionary* headers = [NSMutableDictionary dictionary]; + if (optionalBody != nil) { + [headers setObject:[[NSNumber numberWithLongLong:[optionalBody length]] stringValue] forKey:@"Content-Length"]; + } + + HttpRequest* s = [HttpRequest new]; + s->_method = method; + s->_location = location; + s->_optionalBody = optionalBody; + s->_headers = headers; + return s; +} ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody + andLocalNumber:(PhoneNumber*)localNumber + andPassword:(NSString*)password { + require(method != nil); + require(location != nil); + require(password != nil); + require(localNumber != nil); + + NSMutableDictionary* headers = [NSMutableDictionary dictionary]; + if (optionalBody != nil) { + [headers setObject:[[NSNumber numberWithLongLong:[optionalBody length]] stringValue] forKey:@"Content-Length"]; + } + [headers setObject:[HttpRequest computeBasicAuthorizationTokenForLocalNumber:localNumber andPassword:password] forKey:@"Authorization"]; + + HttpRequest* s = [HttpRequest new]; + s->_method = method; + s->_location = location; + s->_optionalBody = optionalBody; + s->_headers = headers; + return s; +} ++(HttpRequest*)httpRequestWithOtpAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody + andLocalNumber:(PhoneNumber*)localNumber + andPassword:(NSString*)password + andCounter:(int64_t)counter { + require(method != nil); + require(location != nil); + require(password != nil); + + NSMutableDictionary* headers = [NSMutableDictionary dictionary]; + if (optionalBody != nil) { + [headers setObject:[[NSNumber numberWithLongLong:[optionalBody length]] stringValue] forKey:@"Content-Length"]; + } + [headers setObject:[HttpRequest computeOtpAuthorizationTokenForLocalNumber:localNumber andCounterValue:counter andPassword:password] forKey:@"Authorization"]; + + HttpRequest* s = [HttpRequest new]; + s->_method = method; + s->_location = location; + s->_optionalBody = optionalBody; + s->_headers = headers; + return s; +} + ++(HttpRequest*) httpRequestFromData:(NSData*)data { + require(data != nil); + NSUInteger requestSize; + HttpRequestOrResponse* http = [HttpRequestOrResponse tryExtractFromPartialData:data usedLengthOut:&requestSize]; + checkOperation([http isRequest] && requestSize == [data length]); + return [http request]; +} + ++(NSString*) computeOtpAuthorizationTokenForLocalNumber:(PhoneNumber*)localNumber + andCounterValue:(int64_t)counterValue + andPassword:(NSString*)password { + require(localNumber != nil); + require(password != nil); + + NSString* rawToken = [NSString stringWithFormat:@"%@:%@:%lld", + [localNumber toE164], + [CryptoTools computeOtpWithPassword:password andCounter:counterValue], + counterValue]; + return [@"OTP " stringByAppendingString:[[rawToken encodedAsUtf8] encodedAsBase64]]; +} ++(NSString*) computeBasicAuthorizationTokenForLocalNumber:(PhoneNumber*)localNumber andPassword:(NSString*)password { + NSString* rawToken = [NSString stringWithFormat:@"%@:%@", + [localNumber toE164], + password]; + return [@"Basic " stringByAppendingString:[[rawToken encodedAsUtf8] encodedAsBase64]]; +} + +-(NSString*) toHttp { + NSMutableArray* r = [NSMutableArray array]; + + [r addObject:self.method]; + [r addObject:@" "]; + [r addObject:self.location]; + [r addObject:@" HTTP/1.0\r\n"]; + + for (NSString* key in self.headers) { + [r addObject:key]; + [r addObject:@": "]; + [r addObject:[self.headers objectForKey:key]]; + [r addObject:@"\r\n"]; + } + + [r addObject:@"\r\n"]; + if (self.optionalBody != nil) [r addObject:self.optionalBody]; + + return [r componentsJoinedByString:@""]; +} +-(NSData*) serialize { + return [[self toHttp] encodedAsUtf8]; +} +-(bool) isEqualToHttpRequest:(HttpRequest *)other { + return [[self toHttp] isEqualToString:[other toHttp]] + && [self.method isEqualToString:other.method] + && [self.location isEqualToString:other.location] + && (self.optionalBody == other.optionalBody || [self.optionalBody isEqualToString:[other optionalBody]]) + && [self.headers isEqualToDictionary:other.headers]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"%@ %@%@", + self.method, + self.location, + self.optionalBody == nil ? @"" + : [self.optionalBody length] == 0 ? @" [empty body]" + : @" [...body...]"]; +} + +@end diff --git a/Signal/src/network/http/HttpRequestOrResponse.h b/Signal/src/network/http/HttpRequestOrResponse.h new file mode 100644 index 000000000..35906c7fa --- /dev/null +++ b/Signal/src/network/http/HttpRequestOrResponse.h @@ -0,0 +1,18 @@ +#import +#import "HttpRequest.h" +#import "HttpResponse.h" + +@interface HttpRequestOrResponse : NSObject { +@private id requestOrResponse; +@private bool isRequest; +} + ++(HttpRequestOrResponse*) httpRequestOrResponse:(id)requestOrResponse; +-(bool) isRequest; +-(bool) isResponse; +-(HttpRequest*) request; +-(HttpResponse*) response; +-(NSData*) serialize; ++(HttpRequestOrResponse*) tryExtractFromPartialData:(NSData*)data usedLengthOut:(NSUInteger*)usedLengthPtr; + +@end diff --git a/Signal/src/network/http/HttpRequestOrResponse.m b/Signal/src/network/http/HttpRequestOrResponse.m new file mode 100644 index 000000000..c4d1f1e53 --- /dev/null +++ b/Signal/src/network/http/HttpRequestOrResponse.m @@ -0,0 +1,107 @@ +#import "HttpRequestOrResponse.h" +#import "Constraints.h" +#import "Util.h" + +@implementation HttpRequestOrResponse + ++(HttpRequestOrResponse*) httpRequestOrResponse:(id)requestOrResponse { + require(requestOrResponse != nil); + + HttpRequestOrResponse* h = [HttpRequestOrResponse new]; + h->requestOrResponse = requestOrResponse; + require([h isResponse] || [h isRequest]); + return h; +} +-(bool) isRequest { + return [requestOrResponse isKindOfClass:[HttpRequest class]]; +} +-(bool) isResponse { + return [requestOrResponse isKindOfClass:[HttpResponse class]]; +} +-(HttpRequest*) request { + requireState([self isRequest]); + return requestOrResponse; +} +-(HttpResponse*) response { + requireState([self isResponse]); + return requestOrResponse; +} +-(NSData*) serialize { + return [requestOrResponse serialize]; +} + ++(HttpRequestOrResponse*) tryExtractFromPartialData:(NSData*)data usedLengthOut:(NSUInteger*)usedLengthPtr { + require(data != nil); + + // first line should contain HTTP + checkOperation([data tryFindIndexOf:[@"\r\n" encodedAsAscii]] == nil || [data tryFindIndexOf:[@"HTTP" encodedAsAscii]] != nil); + // expecting \r\n line endings + checkOperation(([data tryFindIndexOf:[@"\n" encodedAsAscii]] == nil) == ([data tryFindIndexOf:[@"\r\n" encodedAsAscii]] == nil)); + + NSNumber* tryHeaderLength = [data tryFindIndexOf:[@"\r\n\r\n" encodedAsUtf8]]; + if (tryHeaderLength == nil) return nil; + NSUInteger headerLength = [tryHeaderLength unsignedIntegerValue]; + NSString* fullHeader = [[data take:headerLength] decodedAsUtf8]; + headerLength += 4; // account for \r\n\r\n + + NSArray* headerLines = [fullHeader componentsSeparatedByString:@"\r\n"]; + checkOperation([headerLines count] >= 1); + + // GET /index.html HTTP/1.1 + // HTTP/1.1 200 OK + NSString* requestOrResponseLine = [headerLines objectAtIndex:0]; + NSArray* requestOrResponseLineParts = [requestOrResponseLine componentsSeparatedByString:@" "]; + checkOperation([requestOrResponseLineParts count] >= 3); + bool isResponse = [[requestOrResponseLineParts objectAtIndex:0] hasPrefix:@"HTTP/"]; + + // Host: www.example.com + // Content-Length: 5 + NSMutableDictionary* headers = [NSMutableDictionary dictionary]; + for (NSUInteger i = 1; i < [headerLines count]; i++) { + NSString* headerLine = [headerLines objectAtIndex:i]; + + NSArray* headerLineParts = [headerLine componentsSeparatedByString:@":"]; + checkOperation([headerLineParts count] >= 2); + NSString* headerKey = [[headerLineParts objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString* headerValue = [[headerLine substringFromIndex:[(NSString *)[headerLineParts objectAtIndex:0] length]+1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [headers setObject:headerValue forKey:headerKey]; + } + + NSString* contextLengthText = [headers objectForKey:@"Content-Length"]; + NSNumber* contentLengthParsed = [contextLengthText tryParseAsUnsignedInteger]; + checkOperation((contextLengthText == nil) == (contentLengthParsed == nil)); + + bool hasContent = contentLengthParsed != nil; + NSUInteger contentLength = [contentLengthParsed unsignedIntegerValue]; + if (headerLength + contentLength > [data length]) return nil; // need more data + NSData* optionalBodyData = hasContent ? [data subdataWithRange:NSMakeRange(headerLength, contentLength)] : nil; + + *usedLengthPtr = headerLength + contentLength; + if (isResponse) { + NSNumber* statusCodeParsed = [[requestOrResponseLineParts objectAtIndex:1] tryParseAsUnsignedInteger]; + checkOperation(statusCodeParsed != nil); + + NSUInteger statusCode = [statusCodeParsed unsignedIntegerValue]; + NSString* statusText = [[requestOrResponseLineParts subarrayWithRange:NSMakeRange(2, [requestOrResponseLineParts count] - 2)] componentsJoinedByString:@" "]; + HttpResponse* response = [HttpResponse httpResponseFromStatusCode:statusCode + andStatusText:statusText + andHeaders:headers + andOptionalBodyData:optionalBodyData]; + return [HttpRequestOrResponse httpRequestOrResponse:response]; + } else { + checkOperation([requestOrResponseLineParts count] == 3); + NSString* method = [requestOrResponseLineParts objectAtIndex:0]; + NSString* location = [requestOrResponseLineParts objectAtIndex:1]; + HttpRequest* request = [HttpRequest httpRequestWithMethod:method + andLocation:location + andHeaders:headers + andOptionalBody:[optionalBodyData decodedAsUtf8]]; + return [HttpRequestOrResponse httpRequestOrResponse:request]; + } +} + +-(NSString*) description { + return [requestOrResponse description]; +} + +@end diff --git a/Signal/src/network/http/HttpRequestUtil.h b/Signal/src/network/http/HttpRequestUtil.h new file mode 100644 index 000000000..c982d1e46 --- /dev/null +++ b/Signal/src/network/http/HttpRequestUtil.h @@ -0,0 +1,18 @@ +#import +#import "HttpRequest.h" +#import "HttpResponse.h" +#import "Environment.h" + +@interface HttpRequest (HttpRequestUtil) + ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location; ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody; ++(HttpRequest*)httpRequestWithOtpAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location; ++(HttpRequest*)httpRequestUnauthenticatedWithMethod:(NSString*)method + andLocation:(NSString*)location; + +@end diff --git a/Signal/src/network/http/HttpRequestUtil.m b/Signal/src/network/http/HttpRequestUtil.m new file mode 100644 index 000000000..63b447d98 --- /dev/null +++ b/Signal/src/network/http/HttpRequestUtil.m @@ -0,0 +1,47 @@ +#import "HttpRequestUtil.h" +#import "Constraints.h" +#import "KeyChainStorage.h" +#import "PreferencesUtil.h" +#import "Util.h" + +@implementation HttpRequest (HttpRequestUtil) + ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location { + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:method + andLocation:location + andOptionalBody:nil]; +} ++(HttpRequest*)httpRequestWithBasicAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody { + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:method + andLocation:location + andOptionalBody:optionalBody + andLocalNumber:[KeyChainStorage forceGetLocalNumber] + andPassword:[KeyChainStorage getOrGenerateSavedPassword]]; +} ++(HttpRequest*)httpRequestWithOtpAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location { + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:method + andLocation:location + andOptionalBody:nil]; +} ++(HttpRequest*)httpRequestWithOtpAuthenticationAndMethod:(NSString*)method + andLocation:(NSString*)location + andOptionalBody:(NSString*)optionalBody { + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:method + andLocation:location + andOptionalBody:optionalBody + andLocalNumber:[KeyChainStorage forceGetLocalNumber] + andPassword:[KeyChainStorage getOrGenerateSavedPassword] + andCounter:[[[Environment getCurrent] preferences] getAndIncrementOneTimeCounter]]; +} ++(HttpRequest*)httpRequestUnauthenticatedWithMethod:(NSString*)method + andLocation:(NSString*)location { + return [HttpRequest httpRequestUnauthenticatedWithMethod:method + andLocation:location + andOptionalBody:nil]; +} + +@end diff --git a/Signal/src/network/http/HttpResponse.h b/Signal/src/network/http/HttpResponse.h new file mode 100644 index 000000000..fbd41c7c1 --- /dev/null +++ b/Signal/src/network/http/HttpResponse.h @@ -0,0 +1,34 @@ +#import + +@interface HttpResponse : NSObject { +@private NSUInteger statusCode; +@private NSString* statusText; +@private NSDictionary* headers; +@private NSString* optionalBodyText; +@private NSData* optionalBodyData; +} + ++(HttpResponse*) httpResponseFromStatusCode:(NSUInteger)statusCode + andStatusText:(NSString*)statusText + andHeaders:(NSDictionary*)headers + andOptionalBodyText:(NSString*)optionalBody; ++(HttpResponse*) httpResponseFromStatusCode:(NSUInteger)statusCode + andStatusText:(NSString*)statusText + andHeaders:(NSDictionary*)headers + andOptionalBodyData:(NSData*)optionalBody; ++(HttpResponse*) httpResponseFromData:(NSData*)data; ++(HttpResponse*) httpResponse200Ok; ++(HttpResponse*) httpResponse200OkWithOptionalBody:(NSString*)optionalBody; ++(HttpResponse*) httpResponse501NotImplemented; ++(HttpResponse*) httpResponse500InternalServerError; + +-(NSUInteger) getStatusCode; +-(NSDictionary*) getHeaders; +-(NSString*) getOptionalBodyText; +-(NSData*) getOptionalBodyData; +-(NSData*) serialize; +-(NSString*) getStatusText; +-(bool) isOkResponse; +-(bool) hasBody; + +@end diff --git a/Signal/src/network/http/HttpResponse.m b/Signal/src/network/http/HttpResponse.m new file mode 100644 index 000000000..81101392f --- /dev/null +++ b/Signal/src/network/http/HttpResponse.m @@ -0,0 +1,134 @@ +#import "HttpResponse.h" +#import "Util.h" +#import "Constraints.h" +#import "HttpRequestOrResponse.h" + +@implementation HttpResponse + ++(HttpResponse*) httpResponseFromStatusCode:(NSUInteger)statusCode + andStatusText:(NSString*)statusText + andHeaders:(NSDictionary*)headers + andOptionalBodyText:(NSString*)optionalBody { + + require(headers != nil); + require(statusText != nil); + require(headers != nil); + + HttpResponse* s = [HttpResponse new]; + s->statusCode = statusCode; + s->statusText = statusText; + s->headers = headers; + s->optionalBodyText = optionalBody; + return s; +} ++(HttpResponse*) httpResponseFromStatusCode:(NSUInteger)statusCode + andStatusText:(NSString*)statusText + andHeaders:(NSDictionary*)headers + andOptionalBodyData:(NSData*)optionalBody { + + require(headers != nil); + require(statusText != nil); + require(headers != nil); + + HttpResponse* s = [HttpResponse new]; + s->statusCode = statusCode; + s->statusText = statusText; + s->headers = headers; + s->optionalBodyData = optionalBody; + return s; +} ++(HttpResponse*) httpResponseFromData:(NSData*)data { + require(data != nil); + NSUInteger responseSize; + HttpRequestOrResponse* http = [HttpRequestOrResponse tryExtractFromPartialData:data usedLengthOut:&responseSize]; + checkOperation([http isResponse] && responseSize == [data length]); + return [http response]; +} ++(HttpResponse*) httpResponse200Ok { + return [HttpResponse httpResponse200OkWithOptionalBody:nil]; +} ++(HttpResponse*) httpResponse501NotImplemented { + return [HttpResponse httpResponseFromStatusCode:501 + andStatusText:@"Not Implemented" + andHeaders:@{} + andOptionalBodyData:nil]; +} ++(HttpResponse*) httpResponse500InternalServerError { + return [HttpResponse httpResponseFromStatusCode:500 + andStatusText:@"Internal Server Error" + andHeaders:@{} + andOptionalBodyData:nil]; +} ++(HttpResponse*) httpResponse200OkWithOptionalBody:(NSString*)optionalBody { + return [HttpResponse httpResponseFromStatusCode:200 + andStatusText:@"OK" + andHeaders:[NSDictionary dictionary] + andOptionalBodyText:optionalBody]; +} +-(bool) isOkResponse { + return statusCode == 200; +} + +-(NSUInteger) getStatusCode { + return statusCode; +} +-(NSString*) getStatusText { + return statusText; +} + +-(NSDictionary*) getHeaders { + return headers; +} + +-(bool) hasEmptyBody { + return [optionalBodyData length] == 0 && [optionalBodyText length] == 0; +} +-(NSString*) getOptionalBodyText { + if (optionalBodyText != nil) return optionalBodyText; + if (optionalBodyData != nil) return [optionalBodyData decodedAsUtf8]; + return nil; +} +-(NSData*) getOptionalBodyData { + if (optionalBodyData != nil) return optionalBodyData; + if (optionalBodyText != nil) return [optionalBodyText encodedAsUtf8]; + return nil; +} + +-(NSString*) toHttp { + NSMutableArray* r = [NSMutableArray array]; + + [r addObject:@"HTTP/1.0 "]; + [r addObject:[[NSNumber numberWithUnsignedInteger:statusCode] description]]; + [r addObject:@" "]; + [r addObject:statusText]; + [r addObject:@"\r\n"]; + + NSString* body = [self getOptionalBodyText]; + if (body != nil) { + [r addObject:@"Content-Length: "]; + [r addObject:[[NSNumber numberWithLongLong:[body length]] stringValue]]; + [r addObject:@"\r\n"]; + [r addObject:body]; + } else { + [r addObject:@"\r\n"]; + } + + return [r componentsJoinedByString:@""]; +} +-(NSData*) serialize { + return [[self toHttp] encodedAsUtf8]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"%d %@%@", + statusCode, + statusText, + ![self hasBody] ? @"" + : [self hasEmptyBody] ? @" [empty body]" + : @" [...body...]"]; +} +-(bool) hasBody { + return optionalBodyData != nil || optionalBodyText != nil; +} + +@end diff --git a/Signal/src/network/http/HttpSocket.h b/Signal/src/network/http/HttpSocket.h new file mode 100644 index 000000000..b80cf515b --- /dev/null +++ b/Signal/src/network/http/HttpSocket.h @@ -0,0 +1,32 @@ +#import +#import "PacketHandler.h" +#import "HttpRequestOrResponse.h" +#import "HttpResponse.h" +#import "UdpSocket.h" +#import "NetworkStream.h" +#import "Environment.h" + +/** + * + * HttpSocket is responsible for communicating Http requests and responses over some data channel (tcp, ssl, udp, whatever). + * + */ +@interface HttpSocket : NSObject { +@private NetworkStream* rawDataChannelTcp; +@private UdpSocket* rawDataChannelUdp; + +@private PacketHandler* httpSignalResponseHandler; +@private NSMutableData* partialDataBuffer; +@private id sentPacketsLogger; +@private id receivedPacketsLogger; +} + ++(HttpSocket*) httpSocketOver:(NetworkStream*)rawDataChannel; ++(HttpSocket*) httpSocketOverUdp:(UdpSocket*)rawDataChannel; +-(void) sendHttpRequest:(HttpRequest*)request; +-(void) sendHttpResponse:(HttpResponse*)response; +-(void) send:(HttpRequestOrResponse*)packet; +-(void) startWithHandler:(PacketHandler*)handler + untilCancelled:(id)untilCancelledToken; + +@end diff --git a/Signal/src/network/http/HttpSocket.m b/Signal/src/network/http/HttpSocket.m new file mode 100644 index 000000000..556f6272c --- /dev/null +++ b/Signal/src/network/http/HttpSocket.m @@ -0,0 +1,101 @@ +#import "HttpSocket.h" +#import "Constraints.h" +#import "HttpRequest.h" +#import "Util.h" +#import "HttpResponse.h" +#import "CancelTokenSource.h" + +@implementation HttpSocket + ++(HttpSocket*) httpSocketOver:(NetworkStream*)rawDataChannel { + require(rawDataChannel != nil); + + HttpSocket* h = [HttpSocket new]; + h->rawDataChannelTcp = rawDataChannel; + h->partialDataBuffer = [NSMutableData data]; + h->sentPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"sent"]; + h->receivedPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"received"]; + return h; +} ++(HttpSocket*) httpSocketOverUdp:(UdpSocket*)rawDataChannel { + require(rawDataChannel != nil); + + HttpSocket* h = [HttpSocket new]; + h->rawDataChannelUdp = rawDataChannel; + h->partialDataBuffer = [NSMutableData data]; + h->sentPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"sent"]; + h->receivedPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"received"]; + return h; +} + +-(void) sendHttpRequestOrResponse:(HttpRequestOrResponse*)requestOrResponse { + require(requestOrResponse != nil); + requireState(httpSignalResponseHandler != nil); + [sentPacketsLogger markOccurrence:requestOrResponse]; + NSData* data = [requestOrResponse serialize]; + if (rawDataChannelUdp != nil) { + [rawDataChannelUdp send:data]; + } else { + [rawDataChannelTcp send:data]; + } +} +-(void) sendHttpRequest:(HttpRequest*)request { + require(request != nil); + [self sendHttpRequestOrResponse:[HttpRequestOrResponse httpRequestOrResponse:request]]; +} +-(void) sendHttpResponse:(HttpResponse*)response { + require(response != nil); + [self sendHttpRequestOrResponse:[HttpRequestOrResponse httpRequestOrResponse:response]]; +} +-(void) send:(HttpRequestOrResponse*)packet { + require(packet != nil); + [self sendHttpRequestOrResponse:packet]; +} +-(void) startWithHandler:(PacketHandler*)handler + untilCancelled:(id)untilCancelledToken { + + require(handler != nil); + requireState(httpSignalResponseHandler == nil); + httpSignalResponseHandler = handler; + + CancelTokenSource* lifetime = [CancelTokenSource cancelTokenSource]; + [untilCancelledToken whenCancelled:^{ + [lifetime cancel]; + }]; + + PacketHandler* packetHandler = [PacketHandler packetHandler:^(id packet) { + require(packet != nil); + require([packet isKindOfClass:[NSData class]]); + NSData* data = packet; + + [partialDataBuffer replaceBytesInRange:NSMakeRange([partialDataBuffer length], [data length]) withBytes:[data bytes]]; + + while (true) { + NSUInteger usedDataLength; + HttpRequestOrResponse* s = nil; + @try { + s = [HttpRequestOrResponse tryExtractFromPartialData:partialDataBuffer usedLengthOut:&usedDataLength]; + } @catch (OperationFailed* error) { + [handler handleError:error + relatedInfo:packet + causedTermination:true]; + [lifetime cancel]; + return; + } + if (s == nil) break; + [partialDataBuffer replaceBytesInRange:NSMakeRange(0, usedDataLength) withBytes:NULL length:0]; + [receivedPacketsLogger markOccurrence:s]; + [handler handlePacket:s]; + } + } withErrorHandler:[handler errorHandler]]; + + if (rawDataChannelTcp != nil) { + [rawDataChannelTcp startWithHandler:packetHandler]; + [[lifetime getToken] whenCancelledTerminate:rawDataChannelTcp]; + } else { + [rawDataChannelUdp startWithHandler:packetHandler + untilCancelled:[lifetime getToken]]; + } +} + +@end diff --git a/Signal/src/network/rtp/RtpPacket.h b/Signal/src/network/rtp/RtpPacket.h new file mode 100644 index 000000000..2353d2092 --- /dev/null +++ b/Signal/src/network/rtp/RtpPacket.h @@ -0,0 +1,64 @@ +#import +#import "Conversions.h" + +/** + * + * A Real Time Protocol packet (see RFC 3550, RFC 1889) + * +**/ + +@interface RtpPacket : NSObject { +@private uint16_t extensionHeaderIdentifier; +@private NSData* extensionHeaderData; +@private NSData* rawPacketData; +} + +@property (nonatomic,readonly) bool wasAdjustedDueToInteropIssues; + +@property (nonatomic,readonly) uint8_t version; +@property (nonatomic,readonly) uint8_t padding; +@property (nonatomic,readonly) bool hasExtensionHeader; +@property (nonatomic,readonly) NSArray* contributingSourceIdentifiers; +@property (nonatomic,readonly) bool isMarkerBitSet; +@property (nonatomic,readonly) uint8_t payloadType; +@property (nonatomic,readonly) uint16_t sequenceNumber; +@property (nonatomic,readonly) uint32_t timeStamp; +@property (nonatomic,readonly) uint32_t synchronizationSourceIdentifier; +@property (nonatomic,readonly) NSData* payload; + ++(RtpPacket*) rtpPacketWithDefaultsAndSequenceNumber:(uint16_t)sequenceNumber andPayload:(NSData*)payload; + ++(RtpPacket*) rtpPacketWithVersion:(uint8_t)version + andPadding:(uint8_t)padding + andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers +andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier + andExtensionIdentifier:(uint16_t)extensionHeaderIdentifier + andExtensionData:(NSData*)extensionData + andMarkerBit:(bool)isMarkerBitSet + andPayloadtype:(uint8_t)payloadType + andSequenceNumber:(uint16_t)sequenceNumber + andTimeStamp:(uint32_t)timeStamp + andPayload:(NSData*)payload; + ++(RtpPacket*) rtpPacketWithVersion:(uint8_t)version + andPadding:(uint8_t)padding + andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers +andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier + andMarkerBit:(bool)isMarkerBitSet + andPayloadtype:(uint8_t)payloadType + andSequenceNumber:(uint16_t)sequenceNumber + andTimeStamp:(uint32_t)timeStamp + andPayload:(NSData*)payload; + ++(RtpPacket*) rtpPacketParsedFromPacketData:(NSData*)packetData; + +-(RtpPacket*) withPayload:(NSData*)newPayload; +-(RtpPacket*) withSequenceNumber:(uint16_t)newSequenceNumber; + +-(uint16_t) extensionHeaderIdentifier; +-(NSData*) extensionHeaderData; +-(NSData*) rawPacketDataUsingInteropOptions:(NSArray*)interopOptions; + +-(bool) isEqualToRtpPacket:(RtpPacket*)other; + +@end diff --git a/Signal/src/network/rtp/RtpPacket.m b/Signal/src/network/rtp/RtpPacket.m new file mode 100644 index 000000000..aa54225d6 --- /dev/null +++ b/Signal/src/network/rtp/RtpPacket.m @@ -0,0 +1,369 @@ +#import "RtpPacket.h" +#import "Util.h" +#import "Constraints.h" +#import "FunctionalUtil.h" +#import "Environment.h" + +const uint8_t PACKET_VERSION = 2; +#define HAS_EXTENSION_HEADER_BIT_INDEX 4 +#define HAS_PADDING_BIT_INDEX 5 +#define VERSION_INDEX 6 +#define MARKER_BIT_INDEX 7 +#define EXTENSION_HEADER_IDENTIFIER_LENGTH 2 +#define EXTENSION_HEADER_LENGTH_LENGTH 2 +#define MINIMUM_RTP_HEADER_LENGTH 12 + +#define SEQUENCE_NUMBER_OFFSET 2 +#define TIME_STAMP_OFFSET 4 +#define SYNCHRONIZATION_SOURCE_IDENTIFIER_OFFSET 8 +#define CONTRIBUTING_SOURCE_ID_LENGTH 4 + +#define VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET 0 +#define PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET 1 + +#define SUPPORTED_RTP_VERSION 2 +#define RTP_VERSION_FOR_ZRTP 0 + +#define PAYLOAD_TYPE_MASK 0x7F +#define MAX_EXTENSION_HEADER_LENGTH (0x10000 - 0x1) + + +@implementation RtpPacket + +@synthesize payload; +@synthesize contributingSourceIdentifiers; +@synthesize hasExtensionHeader; +@synthesize isMarkerBitSet; +@synthesize padding; +@synthesize payloadType; +@synthesize sequenceNumber; +@synthesize synchronizationSourceIdentifier; +@synthesize timeStamp; +@synthesize version; +@synthesize wasAdjustedDueToInteropIssues; + ++(RtpPacket*) rtpPacketWithDefaultsAndSequenceNumber:(uint16_t)sequenceNumber andPayload:(NSData *)payload { + require(payload != nil); + return [RtpPacket rtpPacketWithVersion:PACKET_VERSION + andPadding:0 + andContributingSourceIdentifiers:[NSArray array] + andSynchronizationSourceIdentifier:0 + andMarkerBit:false + andPayloadtype:0 + andSequenceNumber:sequenceNumber + andTimeStamp:0 + andPayload:payload]; +} ++(RtpPacket*) rtpPacketWithVersion:(uint8_t)version + andPadding:(uint8_t)padding + andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers +andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier + andExtensionIdentifier:(uint16_t)extensionHeaderIdentifier + andExtensionData:(NSData*)extensionData + andMarkerBit:(bool)isMarkerBitSet + andPayloadtype:(uint8_t)payloadType + andSequenceNumber:(uint16_t)sequenceNumber + andTimeStamp:(uint32_t)timeStamp + andPayload:(NSData*)payload { + + require((version & ~0x3) == 0); + require((payloadType & ~0x7F) == 0); + require(extensionData != nil); + require([extensionData length] < 0x10000); + require(contributingSourceIdentifiers != nil); + require([contributingSourceIdentifiers count] < 0x10); + require(payload != nil); + + RtpPacket* p = [RtpPacket new]; + p->version = version; + p->isMarkerBitSet = isMarkerBitSet; + p->hasExtensionHeader = true; + p->extensionHeaderIdentifier = extensionHeaderIdentifier; + p->extensionHeaderData = extensionData; + p->padding = padding; + p->contributingSourceIdentifiers = contributingSourceIdentifiers; + p->payloadType = payloadType; + p->synchronizationSourceIdentifier = synchronizedSourceIdentifier; + p->timeStamp = timeStamp; + p->sequenceNumber = sequenceNumber; + p->payload = payload; + return p; +} ++(RtpPacket*) rtpPacketWithVersion:(uint8_t)version + andPadding:(uint8_t)padding + andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers +andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier + andMarkerBit:(bool)isMarkerBitSet + andPayloadtype:(uint8_t)payloadType + andSequenceNumber:(uint16_t)sequenceNumber + andTimeStamp:(uint32_t)timeStamp + andPayload:(NSData*)payload { + + require((version & ~0x3) == 0); + require((payloadType & ~0x7F) == 0); + require(contributingSourceIdentifiers != nil); + require([contributingSourceIdentifiers count] < 0x10); + require(payload != nil); + + RtpPacket* p = [RtpPacket new]; + p->version = version; + p->isMarkerBitSet = isMarkerBitSet; + p->padding = padding; + p->contributingSourceIdentifiers = contributingSourceIdentifiers; + p->payloadType = payloadType; + p->synchronizationSourceIdentifier = synchronizedSourceIdentifier; + p->timeStamp = timeStamp; + p->sequenceNumber = sequenceNumber; + p->payload = payload; + return p; +} +-(RtpPacket*) withPayload:(NSData*)newPayload { + if (hasExtensionHeader) { + return [RtpPacket rtpPacketWithVersion:version + andPadding:padding + andContributingSourceIdentifiers:contributingSourceIdentifiers + andSynchronizationSourceIdentifier:synchronizationSourceIdentifier + andExtensionIdentifier:extensionHeaderIdentifier + andExtensionData:extensionHeaderData + andMarkerBit:isMarkerBitSet + andPayloadtype:payloadType + andSequenceNumber:sequenceNumber + andTimeStamp:timeStamp + andPayload:newPayload]; + } + + return [RtpPacket rtpPacketWithVersion:version + andPadding:padding + andContributingSourceIdentifiers:contributingSourceIdentifiers + andSynchronizationSourceIdentifier:synchronizationSourceIdentifier + andMarkerBit:isMarkerBitSet + andPayloadtype:payloadType + andSequenceNumber:sequenceNumber + andTimeStamp:timeStamp + andPayload:newPayload]; +} + ++(uint16_t) getSequenceNumberFromPacketData:(NSData*)packetData { + return [packetData bigEndianUInt16At:SEQUENCE_NUMBER_OFFSET]; +} ++(uint32_t) getTimeStampFromPacketData:(NSData*)packetData { + return [packetData bigEndianUInt32At:TIME_STAMP_OFFSET]; +} ++(uint32_t) getSynchronizationSourceIdentifierFromPacketData:(NSData*)packetData { + return [packetData bigEndianUInt32At:SYNCHRONIZATION_SOURCE_IDENTIFIER_OFFSET]; +} +-(void) readContributingSourcesFromPacketData:(NSData*)packetData trackingOffset:(NSUInteger*)offset andMinSize:(NSUInteger*)minSize { + uint8_t contributingSourceCount = [NumberUtil lowUInt4OfUint8:[packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET]]; + + *minSize += contributingSourceCount * CONTRIBUTING_SOURCE_ID_LENGTH; + checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before header finished."); + + NSMutableArray* contributingSources = [NSMutableArray array]; + for (NSUInteger i = 0; i < contributingSourceCount; i++) { + uint32_t ccsrc = [packetData bigEndianUInt32At:*offset]; + [contributingSources addObject:[NSNumber numberWithUnsignedLong:ccsrc]]; + *offset += CONTRIBUTING_SOURCE_ID_LENGTH; + } + + contributingSourceIdentifiers = contributingSources; +} +-(void) readExtensionHeaderFromPacketData:(NSData*)packetData trackingOffset:(NSUInteger*)offset andMinSize:(NSUInteger*)minSize { + hasExtensionHeader = (([packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET] >> HAS_EXTENSION_HEADER_BIT_INDEX) & 1) != 0; + + // legacy compatibility with android redphone + if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + bool hasPadding = (([packetData uint8At:0] >> HAS_PADDING_BIT_INDEX) & 1) != 0; + if (hasPadding) { + wasAdjustedDueToInteropIssues = true; + *offset += 12; + hasExtensionHeader = true; + } + } + + if (!hasExtensionHeader) return; + + *minSize += EXTENSION_HEADER_IDENTIFIER_LENGTH + EXTENSION_HEADER_LENGTH_LENGTH; + checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before header of extension header finished."); + + extensionHeaderIdentifier = [packetData bigEndianUInt16At:*offset]; + *offset += EXTENSION_HEADER_IDENTIFIER_LENGTH; + + uint16_t extensionLength = [packetData bigEndianUInt16At:*offset]; + *offset += EXTENSION_HEADER_LENGTH_LENGTH; + + *minSize += extensionLength; + checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before payload of extension header finished."); + + extensionHeaderData = [packetData subdataWithRange:NSMakeRange(*offset, extensionLength)]; + *offset += extensionLength; +} +-(void) readPaddingFromPacketData:(NSData*)packetData andMinSize:(NSUInteger*)minSize { + bool hasPadding = (([packetData uint8At:0] >> HAS_PADDING_BIT_INDEX) & 1) != 0; + + // legacy compatibility with android redphone + if (hasPadding) { + if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + wasAdjustedDueToInteropIssues = true; + hasPadding = false; + } + } + + if (!hasPadding) return; + + padding = [packetData uint8At:[packetData length] - 1]; + checkOperationDescribe(padding > 0, @"Padding length must be at least 1 because it includes the suffix byte specifying the length."); + + *minSize += padding; + checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet overlaps header and padding."); +} ++(RtpPacket*) rtpPacketParsedFromPacketData:(NSData*)packetData { + require(packetData != nil); + + NSUInteger minSize = MINIMUM_RTP_HEADER_LENGTH; + checkOperationDescribe([packetData length] >= minSize, @"Rtp packet ends before header finished."); + + RtpPacket* p = [RtpPacket new]; + + p->rawPacketData = packetData; + p->version = [packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET] >> VERSION_INDEX; + checkOperation(p->version == SUPPORTED_RTP_VERSION || p->version == RTP_VERSION_FOR_ZRTP); + + p->isMarkerBitSet = (([packetData uint8At:PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET] >> MARKER_BIT_INDEX) & 1) != 0;; + p->payloadType = [packetData uint8At:PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET] & PAYLOAD_TYPE_MASK; + + NSUInteger offset = MINIMUM_RTP_HEADER_LENGTH; + [p readContributingSourcesFromPacketData:packetData trackingOffset:&offset andMinSize:&minSize]; + [p readExtensionHeaderFromPacketData:packetData trackingOffset:&offset andMinSize:&minSize]; + [p readPaddingFromPacketData:packetData andMinSize:&minSize]; + p->payload = [packetData subdataWithRange:NSMakeRange(offset, [packetData length] - p->padding - offset)]; + + p->sequenceNumber = [self getSequenceNumberFromPacketData:packetData]; + p->timeStamp = [self getTimeStampFromPacketData:packetData]; + p->synchronizationSourceIdentifier = [self getSynchronizationSourceIdentifierFromPacketData:packetData]; + + return p; +} + +-(NSData*) generateFlags { + requireState((version & ~0x3) == 0); + requireState((payloadType & ~0x7F) == 0); + requireState([contributingSourceIdentifiers count] < 0x10); + + NSMutableData* flags = [NSMutableData dataWithLength:2]; + + uint8_t versionMask = (uint8_t)(version << VERSION_INDEX); + uint8_t paddingBit = padding > 0 ? (uint8_t)(1< 0) { + [paddingData setUint8At:[paddingData length] - 1 to:padding]; + } + return paddingData; +} +-(NSData*) generateSerializedPacketDataUsingInteropOptions:(NSArray*)interopOptions { + requireState(hasExtensionHeader == (extensionHeaderData != nil)); + requireState(extensionHeaderData == nil || [extensionHeaderData length] <= MAX_EXTENSION_HEADER_LENGTH); + + NSData* shouldBeEmpty = [NSData data]; + + // legacy compatibility with android redphone + if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + if ([interopOptions containsObject:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + if (hasExtensionHeader) { + shouldBeEmpty = [NSData dataWithLength:12]; + } + } + } + + return [@[ + [self generateFlags], + [NSData dataWithBigEndianBytesOfUInt16:sequenceNumber], + [NSData dataWithBigEndianBytesOfUInt32:timeStamp], + [NSData dataWithBigEndianBytesOfUInt32:synchronizationSourceIdentifier], + [self generateCcrcData], + shouldBeEmpty, + [self generateExtensionHeaderData], + payload, + [self generatePaddingData] + ] concatDatas]; +} + +-(RtpPacket*) withSequenceNumber:(uint16_t)newSequenceNumber { + RtpPacket* p = [RtpPacket new]; + p->version = version; + p->padding = padding; + p->hasExtensionHeader = hasExtensionHeader; + p->contributingSourceIdentifiers = contributingSourceIdentifiers; + p->isMarkerBitSet = isMarkerBitSet; + p->payloadType = payloadType; + p->timeStamp = timeStamp; + p->synchronizationSourceIdentifier = synchronizationSourceIdentifier; + p->extensionHeaderIdentifier = extensionHeaderIdentifier; + p->extensionHeaderData = extensionHeaderData; + p->payload = payload; + + p->sequenceNumber = newSequenceNumber; + return p; +} + +-(uint16_t) extensionHeaderIdentifier { + requireState(hasExtensionHeader); + return extensionHeaderIdentifier; +} +-(NSData*) extensionHeaderData { + requireState(hasExtensionHeader); + return extensionHeaderData; +} +-(NSData*) rawPacketDataUsingInteropOptions:(NSArray*)interopOptions { + if (rawPacketData == nil) rawPacketData = [self generateSerializedPacketDataUsingInteropOptions:interopOptions]; + return rawPacketData; +} +-(bool) isEqualToRtpPacket:(RtpPacket*)other { + if (other == nil) return false; + if (version != [other version]) return false; + if (padding != [other padding]) return false; + if (hasExtensionHeader != [other hasExtensionHeader]) return false; + if (isMarkerBitSet != [other isMarkerBitSet]) return false; + if (payloadType != [other payloadType]) return false; + if (timeStamp != [other timeStamp]) return false; + if (synchronizationSourceIdentifier != [other synchronizationSourceIdentifier]) return false; + if (sequenceNumber != [other sequenceNumber]) return false; + + if (![payload isEqualToData:[other payload]]) return false; + if (![contributingSourceIdentifiers isEqualToArray:[other contributingSourceIdentifiers]]) return false; + if (hasExtensionHeader) { + if (extensionHeaderIdentifier != [other extensionHeaderIdentifier]) return false; + if (![extensionHeaderData isEqualToData:[other extensionHeaderData]]) return false; + } + + if (![[self rawPacketDataUsingInteropOptions:@[]] isEqualToData:[other rawPacketDataUsingInteropOptions:@[]]]) return false; + + return true; +} + +@end diff --git a/Signal/src/network/rtp/RtpSocket.h b/Signal/src/network/rtp/RtpSocket.h new file mode 100644 index 000000000..1c34240c4 --- /dev/null +++ b/Signal/src/network/rtp/RtpSocket.h @@ -0,0 +1,28 @@ +#import +#import +#include +#include +#import "UdpSocket.h" + +#include "RtpPacket.h" +#import "PacketHandler.h" +#import "Terminable.h" + +/** + * + * Rtp Socket is used to send RTP packets by serializing them over a UdpSocket. + * +**/ + +@interface RtpSocket : NSObject { +@private UdpSocket* udpSocket; +@private PacketHandler* currentHandler; +@private NSThread* currentHandlerThread; +@public NSMutableArray* interopOptions; +} + ++(RtpSocket*) rtpSocketOverUdp:(UdpSocket*)udpSocket interopOptions:(NSArray*)interopOptions; +-(void) send:(RtpPacket*)packet; +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken; + +@end diff --git a/Signal/src/network/rtp/RtpSocket.m b/Signal/src/network/rtp/RtpSocket.m new file mode 100644 index 000000000..f226a76b3 --- /dev/null +++ b/Signal/src/network/rtp/RtpSocket.m @@ -0,0 +1,72 @@ +#import "RtpSocket.h" +#import "ThreadManager.h" +#import "Environment.h" + +@implementation RtpSocket + ++(RtpSocket*) rtpSocketOverUdp:(UdpSocket*)udpSocket interopOptions:(NSArray*)interopOptions { + require(udpSocket != nil); + require(interopOptions != nil); + + RtpSocket* s = [RtpSocket new]; + s->udpSocket = udpSocket; + s->interopOptions = [interopOptions mutableCopy]; + return s; +} + +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken { + require(handler != nil); + @synchronized(self) { + bool isFirstTime = currentHandler == nil; + currentHandler = handler; + if (!isFirstTime) return; + } + + PacketHandlerBlock valueHandler = ^(id packet) { + require(packet != nil); + require([packet isKindOfClass:[NSData class]]); + NSData* data = packet; + RtpPacket* rtpPacket = [RtpPacket rtpPacketParsedFromPacketData:data]; + + // enable interop when legacy client is detected + if (rtpPacket.wasAdjustedDueToInteropIssues) { + if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + if (![interopOptions containsObject:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) { + [interopOptions addObject:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]; + } + } + } + + [self handleRtpPacket:rtpPacket]; + }; + ErrorHandlerBlock errorHandler = ^(id error, id relatedInfo, bool causedTermination) { + @synchronized(self) { + currentHandler.errorHandler(error, relatedInfo, causedTermination); + } + }; + + [udpSocket startWithHandler:[PacketHandler packetHandler:valueHandler + withErrorHandler:errorHandler] + untilCancelled:untilCancelledToken]; +} +-(void) handleRtpPacket:(RtpPacket*)rtpPacket { + @synchronized(self) { + if ([ThreadManager lowLatencyThread] == [NSThread currentThread]) { + [currentHandler handlePacket:rtpPacket]; + return; + } + + [self performSelector:@selector(handleRtpPacket:) + onThread:[ThreadManager lowLatencyThread] + withObject:rtpPacket + waitUntilDone:false]; + } +} + +-(void) send:(RtpPacket*)packet { + require(packet != nil); + + [udpSocket send:[packet rawPacketDataUsingInteropOptions:interopOptions]]; +} + +@end diff --git a/Signal/src/network/rtp/srtp/SequenceCounter.h b/Signal/src/network/rtp/srtp/SequenceCounter.h new file mode 100644 index 000000000..2129c958e --- /dev/null +++ b/Signal/src/network/rtp/srtp/SequenceCounter.h @@ -0,0 +1,19 @@ +#import + +/** + * + * SequenceCounter is used to expand a 16-bit sequence number into a 64-bit sequence number. + * + * Works by tracking when the almost monotonically increasing id 'looops around'. + * +**/ + +@interface SequenceCounter : NSObject { +@private uint16_t prevShortId; +@private int64_t prevLongId; +} + ++(SequenceCounter*) sequenceCounter; +-(int64_t)convertNext:(uint16_t)nextShortId; + +@end diff --git a/Signal/src/network/rtp/srtp/SequenceCounter.m b/Signal/src/network/rtp/srtp/SequenceCounter.m new file mode 100644 index 000000000..8e3c54608 --- /dev/null +++ b/Signal/src/network/rtp/srtp/SequenceCounter.m @@ -0,0 +1,19 @@ +#import "SequenceCounter.h" + +const int64_t ShortRange = ((int64_t)1) << 16; + +@implementation SequenceCounter ++(SequenceCounter*) sequenceCounter { + return [SequenceCounter new]; +} +-(int64_t)convertNext:(uint16_t)nextShortId { + int64_t delta = (int64_t)nextShortId - (int64_t)prevShortId; + if (delta > INT16_MAX) delta -= ShortRange; + if (delta < INT16_MIN) delta += ShortRange; + int64_t nextLongId = prevLongId + delta; + + prevShortId = nextShortId; + prevLongId = nextLongId; + return nextLongId; +} +@end diff --git a/Signal/src/network/rtp/srtp/SrtpSocket.h b/Signal/src/network/rtp/srtp/SrtpSocket.h new file mode 100644 index 000000000..83c26bf07 --- /dev/null +++ b/Signal/src/network/rtp/srtp/SrtpSocket.h @@ -0,0 +1,31 @@ +#import +#import "RtpSocket.h" +#import "SrtpStream.h" +#import "HandshakePacket.h" +#import "PacketHandler.h" +#import "Logging.h" + +/** + * + * SrtpSocket is responsible for sending and receiving secured RTP packets. + * Works by authenticating and encrypting/decrypting rtp packets sent/received over an RtpSocket. + * +**/ + +@interface SrtpSocket : NSObject { +@private SrtpStream* incomingContext; +@private SrtpStream* outgoingContext; +@private RtpSocket* rtpSocket; +@private bool hasBeenStarted; +@private id badPacketLogger; +} ++(SrtpSocket*) srtpSocketOverRtp:(RtpSocket*)rtpSocket + andIncomingCipherKey:(NSData*)incomingCipherKey + andIncomingMacKey:(NSData*)incomingMacKey + andIncomingSalt:(NSData*)incomingSalt + andOutgoingCipherKey:(NSData*)outgoingCipherKey + andOutgoingMacKey:(NSData*)outgoingMacKey + andOutgoingSalt:(NSData*)outgoingSalt; +-(void) secureAndSendRtpPacket:(RtpPacket *)packet; +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken; +@end diff --git a/Signal/src/network/rtp/srtp/SrtpSocket.m b/Signal/src/network/rtp/srtp/SrtpSocket.m new file mode 100644 index 000000000..ed668f891 --- /dev/null +++ b/Signal/src/network/rtp/srtp/SrtpSocket.m @@ -0,0 +1,67 @@ +#import "SrtpSocket.h" +#import "ZrtpManager.h" +#import "ZrtpHandshakeSocket.h" + +@implementation SrtpSocket + ++(SrtpSocket*) srtpSocketOverRtp:(RtpSocket*)rtpSocket + andIncomingCipherKey:(NSData*)incomingCipherKey + andIncomingMacKey:(NSData*)incomingMacKey + andIncomingSalt:(NSData*)incomingSalt + andOutgoingCipherKey:(NSData*)outgoingCipherKey + andOutgoingMacKey:(NSData*)outgoingMacKey + andOutgoingSalt:(NSData*)outgoingSalt { + require(rtpSocket != nil); + require(incomingCipherKey != nil); + require(incomingMacKey != nil); + require(incomingSalt != nil); + require(outgoingCipherKey != nil); + require(outgoingMacKey != nil); + require(outgoingSalt != nil); + + SrtpSocket* s = [SrtpSocket new]; + s->incomingContext = [SrtpStream srtpStreamWithCipherKey:incomingCipherKey andMacKey:incomingMacKey andCipherIvSalt:incomingSalt]; + s->outgoingContext = [SrtpStream srtpStreamWithCipherKey:outgoingCipherKey andMacKey:outgoingMacKey andCipherIvSalt:outgoingSalt]; + s->rtpSocket = rtpSocket; + s->badPacketLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"Bad Packet"]; + return s; +} + +-(RtpPacket*) decryptAndAuthenticateReceived:(RtpPacket*)securedRtpPacket { + require(securedRtpPacket != nil); + return [incomingContext verifyAuthenticationAndDecryptSecuredRtpPacket:securedRtpPacket]; +} +-(RtpPacket*) encryptAndAuthenticateToSend:(RtpPacket*)normalRtpPacket { + require(normalRtpPacket != nil); + return [outgoingContext encryptAndAuthenticateNormalRtpPacket:normalRtpPacket]; +} + +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken { + require(handler != nil); + requireState(!hasBeenStarted); + hasBeenStarted = true; + + PacketHandlerBlock packetHandler = ^(id packet) { + require(packet != nil); + require([packet isKindOfClass:[RtpPacket class]]); + + RtpPacket* decryptedPacket; + @try { + decryptedPacket = [self decryptAndAuthenticateReceived:packet] ; + } @catch (OperationFailed* ex) { + [badPacketLogger markOccurrence:ex]; + [handler handleError:ex relatedInfo:packet causedTermination:false]; + return; + } + + [handler handlePacket:decryptedPacket]; + }; + [rtpSocket startWithHandler:[PacketHandler packetHandler:packetHandler withErrorHandler:handler.errorHandler] + untilCancelled:untilCancelledToken]; +} + +-(void) secureAndSendRtpPacket:(RtpPacket *)packet { + require(packet != nil); + [rtpSocket send:[self encryptAndAuthenticateToSend:packet]]; +} +@end diff --git a/Signal/src/network/rtp/srtp/SrtpStream.h b/Signal/src/network/rtp/srtp/SrtpStream.h new file mode 100644 index 000000000..34706f533 --- /dev/null +++ b/Signal/src/network/rtp/srtp/SrtpStream.h @@ -0,0 +1,22 @@ +#import +#import "SequenceCounter.h" +#import "RtpPacket.h" + +/** + * + * SrtpStream is used by SrtpSocket to authenticate and cipher packets. + * One SrtpStream is used for sending, and one is used for receiving. + * (Because it's bad to use the same cryptographic keys in both directions.) + * +**/ + +@interface SrtpStream : NSObject { +@private NSData* cipherKey; +@private NSData* cipherIvSalt; +@private NSData* macKey; +@private SequenceCounter* sequenceCounter; +} ++(SrtpStream*) srtpStreamWithCipherKey:(NSData*)cipherKey andMacKey:(NSData*)macKey andCipherIvSalt:(NSData*)cipherIvSalt; +-(RtpPacket*) encryptAndAuthenticateNormalRtpPacket:(RtpPacket*)normalRtpPacket; +-(RtpPacket*) verifyAuthenticationAndDecryptSecuredRtpPacket:(RtpPacket*)securedRtpPacket; +@end diff --git a/Signal/src/network/rtp/srtp/SrtpStream.m b/Signal/src/network/rtp/srtp/SrtpStream.m new file mode 100644 index 000000000..58972c0ad --- /dev/null +++ b/Signal/src/network/rtp/srtp/SrtpStream.m @@ -0,0 +1,73 @@ +#import "SrtpStream.h" +#import "Util.h" + +#define HMAC_LENGTH 20 +#define IV_SALT_LENGTH 14 +#define IV_LENGTH 16 + +@implementation SrtpStream ++(SrtpStream*) srtpStreamWithCipherKey:(NSData*)cipherKey andMacKey:(NSData*)macKey andCipherIvSalt:(NSData*)cipherIvSalt { + require(cipherKey != nil); + require(macKey != nil); + require(cipherIvSalt != nil); + require([cipherIvSalt length] == IV_SALT_LENGTH); + + SrtpStream* s = [SrtpStream new]; + s->cipherIvSalt = cipherIvSalt; + s->macKey = macKey; + s->cipherKey = cipherKey; + s->sequenceCounter = [SequenceCounter sequenceCounter]; + return s; +} + +-(RtpPacket*) encryptAndAuthenticateNormalRtpPacket:(RtpPacket*)normalRtpPacket { + require(normalRtpPacket != nil); + NSData* payload = [normalRtpPacket payload]; + + NSData* iv = [self getIvForSequenceNumber:[normalRtpPacket sequenceNumber] andSynchronizationSourceIdentifier:[normalRtpPacket synchronizationSourceIdentifier]]; + NSData* encryptedPayload = [payload encryptWithAesInCounterModeWithKey:cipherKey andIv:iv]; + + RtpPacket* encryptedRtpPacket = [normalRtpPacket withPayload:encryptedPayload]; + NSData* hmac = [[encryptedRtpPacket rawPacketDataUsingInteropOptions:@[]] hmacWithSha1WithKey:macKey]; + NSData* authenticatedEncryptedPayload = [@[encryptedPayload, hmac] concatDatas]; + + return [encryptedRtpPacket withPayload:authenticatedEncryptedPayload]; +} + +-(RtpPacket*) verifyAuthenticationAndDecryptSecuredRtpPacket:(RtpPacket*)securedRtpPacket { + require(securedRtpPacket != nil); + checkOperationDescribe([[securedRtpPacket payload] length] >= HMAC_LENGTH, @"Payload not long enough to include hmac"); + + NSData* authenticatedData = [securedRtpPacket rawPacketDataUsingInteropOptions:nil]; + NSData* includedHmac = [authenticatedData takeLastVolatile:HMAC_LENGTH]; + NSData* expectedHmac = [[authenticatedData skipLastVolatile:HMAC_LENGTH] hmacWithSha1WithKey:macKey]; + checkOperationDescribe([expectedHmac length] == HMAC_LENGTH, @"Hmac length constant is wrong"); + checkOperationDescribe([includedHmac isEqualToData_TimingSafe:expectedHmac], @"Authentication failed."); + + NSData* iv = [self getIvForSequenceNumber:[securedRtpPacket sequenceNumber] andSynchronizationSourceIdentifier:[securedRtpPacket synchronizationSourceIdentifier]]; + NSData* encryptedPayload = [[securedRtpPacket payload] skipLastVolatile:HMAC_LENGTH]; + NSData* decryptedPayload = [encryptedPayload decryptWithAesInCounterModeWithKey:cipherKey andIv:iv]; + + return [securedRtpPacket withPayload:decryptedPayload]; +} + +-(NSData*)getIvForSequenceNumber:(uint16_t)sequenceNumber andSynchronizationSourceIdentifier:(uint64_t)synchronizationSourceIdentifier { + int64_t logicalSequence = [sequenceCounter convertNext:sequenceNumber]; + NSMutableData* iv = [NSMutableData dataWithLength:IV_LENGTH]; + + [iv replaceBytesStartingAt:0 withData:cipherIvSalt]; + uint8_t* b = (uint8_t*)[iv bytes]; + + b[6] ^= (uint8_t)((synchronizationSourceIdentifier >> 8) & 0xFF); + b[7] ^= (uint8_t)((synchronizationSourceIdentifier >> 0) & 0xFF); + b[8] ^= (uint8_t)((logicalSequence >> 40) & 0xFF); + b[9] ^= (uint8_t)((logicalSequence >> 32) & 0xFF); + b[10] ^= (uint8_t)((logicalSequence >> 24) & 0xFF); + b[11] ^= (uint8_t)((logicalSequence >> 16) & 0xFF); + b[12] ^= (uint8_t)((logicalSequence >> 8) & 0xFF); + b[13] ^= (uint8_t)((logicalSequence >> 0) & 0xFF); + + return iv; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/HashChain.h b/Signal/src/network/rtp/zrtp/HashChain.h new file mode 100644 index 000000000..c8f07d9aa --- /dev/null +++ b/Signal/src/network/rtp/zrtp/HashChain.h @@ -0,0 +1,30 @@ +#import + +#define HASH_CHAIN_ITEM_LENGTH 32 + +/** + * + * HashChain's values are used in zrtp to prevent attackers from injecting packets after the handshake has started. + * The values h0 through h3 are what you get by repeatedly hashing h0, so e.g. h2 = Sha256(h1). + * The values in the chain are used, in reverse order, as the keys used to HMAC handshake packets. + * Each value is sent to the other party only in packets after the packet that was authenticated with the value. + * The idea is that attackers can't inject packets after the handshake starts, because finding satisfying hmac keys or hash pre-images is intractable. + * + * - Hellos contain h3, and are HMAC'ed with h2. + * - Commit (from initiator only) contains h2, allowing verification of (the initiator's) Hello, and is HMAC'ed with h1. + * - DHParts contain h1, allowing verification of (the initiator's) Commit, and are HMAC'ed with h0. + * - Confirms contain h0, allowing verification of DHParts + * +**/ + +@interface HashChain : NSObject + +@property (nonatomic, readonly) NSData* h0; +@property (nonatomic, readonly) NSData* h1; +@property (nonatomic, readonly) NSData* h2; +@property (nonatomic, readonly) NSData* h3; + ++(HashChain*) hashChainWithSeed:(NSData*)seed; ++(HashChain*) hashChainWithSecureGeneratedData; + +@end diff --git a/Signal/src/network/rtp/zrtp/HashChain.m b/Signal/src/network/rtp/zrtp/HashChain.m new file mode 100644 index 000000000..f2f592839 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/HashChain.m @@ -0,0 +1,23 @@ +#import "CryptoTools.h" +#import "Constraints.h" +#import "HashChain.h" + +@implementation HashChain + +@synthesize h0,h1,h2,h3; + ++(HashChain*) hashChainWithSeed:(NSData*)seed { + require(seed != nil); + require([seed length] == HASH_CHAIN_ITEM_LENGTH); + HashChain* s = [HashChain new]; + s->h0 = seed; + s->h1 = [s->h0 hashWithSha256]; + s->h2 = [s->h1 hashWithSha256]; + s->h3 = [s->h2 hashWithSha256]; + return s; +} ++(HashChain*) hashChainWithSecureGeneratedData { + return [HashChain hashChainWithSeed:[CryptoTools generateSecureRandomData:HASH_CHAIN_ITEM_LENGTH]]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/MasterSecret.h b/Signal/src/network/rtp/zrtp/MasterSecret.h new file mode 100644 index 000000000..fafc6ed66 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/MasterSecret.h @@ -0,0 +1,57 @@ +#import +#import "Zid.h" +#import "HelloPacket.h" +#import "CommitPacket.h" +#import "DhPacket.h" + +/** + * + * MasterSecret is responsible for computing and storing the crypto keys derived by both sides of the zrtp handshake. + * Both the authenticated portions of the handshake packets and the result of key agreement affect the master secret. + * +**/ + +@interface MasterSecret : NSObject + +@property (nonatomic, readonly) NSData* totalHash; +@property (nonatomic, readonly) NSData* counter; +@property (nonatomic, readonly) NSData* sharedSecret; +@property (nonatomic, readonly) NSData* shortAuthenticationStringData; + +@property (nonatomic, readonly) Zid* responderZid; +@property (nonatomic, readonly) NSData* responderSrtpKey; +@property (nonatomic, readonly) NSData* responderSrtpSalt; +@property (nonatomic, readonly) NSData* responderMacKey; +@property (nonatomic, readonly) NSData* responderZrtpKey; + +@property (nonatomic, readonly) Zid* initiatorZid; +@property (nonatomic, readonly) NSData* initiatorSrtpKey; +@property (nonatomic, readonly) NSData* initiatorSrtpSalt; +@property (nonatomic, readonly) NSData* initiatorMacKey; +@property (nonatomic, readonly) NSData* initiatorZrtpKey; + ++(MasterSecret*) masterSecretFromDhResult:(NSData*)dhResult + andInitiatorHello:(HelloPacket*)initiatorHello + andResponderHello:(HelloPacket*)responderHello + andCommit:(CommitPacket*)commit + andDhPart1:(DhPacket*)dhPart1 + andDhPart2:(DhPacket*)dhPart2; + ++(NSData*) calculateSharedSecretFromDhResult:(NSData*)dhResult + andTotalHash:(NSData*)totalHash + andInitiatorZid:(Zid*)initiatorZid + andResponderZid:(Zid*)responderZid; + ++(NSData*) calculateTotalHashFromResponderHello:(HelloPacket*)responderHello + andCommit:(CommitPacket*)commit + andDhPart1:(DhPacket*)dhPart1 + andDhPart2:(DhPacket*)dhPart2; + ++(MasterSecret*) masterSecretFromSharedSecret:(NSData*)sharedSecret + andTotalHash:(NSData*)totalHash + andInitiatorZid:(Zid*)initiatorZid + andResponderZid:(Zid*)responderZid; + +-(NSString*) shortAuthenticationString; + +@end diff --git a/Signal/src/network/rtp/zrtp/MasterSecret.m b/Signal/src/network/rtp/zrtp/MasterSecret.m new file mode 100644 index 000000000..57cc33a32 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/MasterSecret.m @@ -0,0 +1,168 @@ +#import "MasterSecret.h" +#import "Conversions.h" +#import "CryptoTools.h" +#import "ShortAuthenticationStringGenerator.h" +#import "Util.h" + +#define INITIATOR_SRTP_KEY_LABEL @"Initiator SRTP master key" +#define RESPONDER_SRTP_KEY_LABEL @"Responder SRTP master key" +#define INITIATOR_SRTP_SALT_LABEL @"Initiator SRTP master salt" +#define RESPONDER_SRTP_SALT_LABEL @"Responder SRTP master salt" +#define INITIATOR_MAC_KEY_LABEL @"Initiator HMAC key" +#define RESPONDER_MAC_KEY_LABEL @"Responder HMAC key" +#define INITIATOR_ZRTP_KEY_LABEL @"Initiator ZRTP key" +#define RESPONDER_ZRTP_KEY_LABEL @"Responder ZRTP key" +#define SAS_LABEL @"SAS" + +#define INITIATOR_SRTP_KEY_LENGTH 16 +#define RESPONDER_SRTP_KEY_LENGTH 16 +#define INITIATOR_SRTP_SALT_LENGTH 14 +#define RESPONDER_SRTP_SALT_LENGTH 14 +#define INITIATOR_MAC_KEY_LENGTH 20 +#define RESPONDER_MAC_KEY_LENGTH 20 +#define INITIATOR_ZRTP_KEY_LENGTH 16 +#define RESPONDER_ZRTP_KEY_LENGTH 16 +#define SAS_LENGTH 4 + +@implementation MasterSecret + +@synthesize initiatorMacKey, initiatorSrtpSalt, initiatorZrtpKey, initiatorSrtpKey, initiatorZid, + responderMacKey, responderSrtpSalt, responderZrtpKey, responderSrtpKey, responderZid, + shortAuthenticationStringData, sharedSecret, totalHash, counter; + ++(MasterSecret*) masterSecretFromDhResult:(NSData*)dhResult + andInitiatorHello:(HelloPacket*)initiatorHello + andResponderHello:(HelloPacket*)responderHello + andCommit:(CommitPacket*)commit + andDhPart1:(DhPacket*)dhPart1 + andDhPart2:(DhPacket*)dhPart2 { + require(dhResult != nil); + require(initiatorHello != nil); + require(responderHello != nil); + require(commit != nil); + require(dhPart1 != nil); + require(dhPart2 != nil); + + NSData* totalHash = [self calculateTotalHashFromResponderHello:responderHello + andCommit:commit + andDhPart1:dhPart1 + andDhPart2:dhPart2]; + + NSData* sharedSecret = [self calculateSharedSecretFromDhResult:dhResult + andTotalHash:totalHash + andInitiatorZid:[initiatorHello zid] + andResponderZid:[responderHello zid]]; + + return [MasterSecret masterSecretFromSharedSecret:sharedSecret + andTotalHash:totalHash + andInitiatorZid:[initiatorHello zid] + andResponderZid:[responderHello zid]]; + +} + ++(NSData*) calculateSharedSecretFromDhResult:(NSData*)dhResult + andTotalHash:(NSData*)totalHash + andInitiatorZid:(Zid*)initiatorZid + andResponderZid:(Zid*)responderZid { + require(dhResult != nil); + require(totalHash != nil); + require(initiatorZid != nil); + require(responderZid != nil); + + NSData* counter = [NSData dataWithBigEndianBytesOfUInt32:1]; + NSData* s1Length = [NSData dataWithBigEndianBytesOfUInt32:0]; + NSData* s2Length = [NSData dataWithBigEndianBytesOfUInt32:0]; + NSData* s3Length = [NSData dataWithBigEndianBytesOfUInt32:0]; + + NSData* data = [@[ + + counter, + dhResult, + [@"ZRTP-HMAC-KDF" encodedAsUtf8], + [initiatorZid getData], + [responderZid getData], + totalHash, + s1Length, + s2Length, + s3Length + + ] concatDatas]; + + return [data hashWithSha256]; +} + ++(NSData*) calculateTotalHashFromResponderHello:(HelloPacket*)responderHello + andCommit:(CommitPacket*)commit + andDhPart1:(DhPacket*)dhPart1 + andDhPart2:(DhPacket*)dhPart2 { + require(responderHello != nil); + require(commit != nil); + require(dhPart1 != nil); + require(dhPart2 != nil); + + NSData* data = [@[ + + [[responderHello embeddedIntoHandshakePacket] dataUsedForAuthentication], + [[commit embeddedIntoHandshakePacket] dataUsedForAuthentication], + [[dhPart1 embeddedIntoHandshakePacket] dataUsedForAuthentication], + [[dhPart2 embeddedIntoHandshakePacket] dataUsedForAuthentication] + + ] concatDatas]; + + return [data hashWithSha256]; + +} + ++(MasterSecret*) masterSecretFromSharedSecret:(NSData*)sharedSecret + andTotalHash:(NSData*)totalHash + andInitiatorZid:(Zid*)initiatorZid + andResponderZid:(Zid*)responderZid { + require(sharedSecret != nil); + require(totalHash != nil); + require(initiatorZid != nil); + require(responderZid != nil); + + MasterSecret* s = [MasterSecret new]; + + s->initiatorZid = initiatorZid; + s->responderZid = responderZid; + s->totalHash = totalHash; + s->sharedSecret = sharedSecret; + s->counter = [NSData dataWithBigEndianBytesOfUInt32:1]; + + s->initiatorSrtpKey = [s deriveKeyWithLabel:INITIATOR_SRTP_KEY_LABEL andTruncatedLength:INITIATOR_SRTP_KEY_LENGTH]; + s->responderSrtpKey = [s deriveKeyWithLabel:RESPONDER_SRTP_KEY_LABEL andTruncatedLength:RESPONDER_SRTP_KEY_LENGTH]; + s->initiatorSrtpSalt = [s deriveKeyWithLabel:INITIATOR_SRTP_SALT_LABEL andTruncatedLength:INITIATOR_SRTP_SALT_LENGTH]; + s->responderSrtpSalt = [s deriveKeyWithLabel:RESPONDER_SRTP_SALT_LABEL andTruncatedLength:RESPONDER_SRTP_SALT_LENGTH]; + s->initiatorMacKey = [s deriveKeyWithLabel:INITIATOR_MAC_KEY_LABEL andTruncatedLength:INITIATOR_MAC_KEY_LENGTH]; + s->responderMacKey = [s deriveKeyWithLabel:RESPONDER_MAC_KEY_LABEL andTruncatedLength:RESPONDER_MAC_KEY_LENGTH]; + s->initiatorZrtpKey = [s deriveKeyWithLabel:INITIATOR_ZRTP_KEY_LABEL andTruncatedLength:INITIATOR_ZRTP_KEY_LENGTH]; + s->responderZrtpKey = [s deriveKeyWithLabel:RESPONDER_ZRTP_KEY_LABEL andTruncatedLength:RESPONDER_ZRTP_KEY_LENGTH]; + s->shortAuthenticationStringData = [s deriveKeyWithLabel:SAS_LABEL andTruncatedLength:SAS_LENGTH]; + + return s; +} + +-(NSData*) deriveKeyWithLabel:(NSString*)label andTruncatedLength:(uint16_t)truncatedLength { + NSData* input = [@[ + + counter, + [label encodedAsUtf8], + [@[@0] toUint8Data], + [initiatorZid getData], + [responderZid getData], + totalHash, + [NSData dataWithBigEndianBytesOfUInt32:truncatedLength] + + ] concatDatas]; + + NSData* digest = [input hmacWithSha256WithKey:sharedSecret]; + + return [digest take:truncatedLength]; +} + +-(NSString*) shortAuthenticationString { + return [ShortAuthenticationStringGenerator generateFromData:shortAuthenticationStringData]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/NegotiationFailed.h b/Signal/src/network/rtp/zrtp/NegotiationFailed.h new file mode 100644 index 000000000..97fb8598f --- /dev/null +++ b/Signal/src/network/rtp/zrtp/NegotiationFailed.h @@ -0,0 +1,9 @@ +#import + +@interface NegotiationFailed : NSObject + +@property (nonatomic,readonly) NSString* reason; + ++(NegotiationFailed*) negotiationFailedWithReason:(NSString*)reason; + +@end diff --git a/Signal/src/network/rtp/zrtp/NegotiationFailed.m b/Signal/src/network/rtp/zrtp/NegotiationFailed.m new file mode 100644 index 000000000..4b03acf19 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/NegotiationFailed.m @@ -0,0 +1,17 @@ +#import "NegotiationFailed.h" + +@implementation NegotiationFailed + +@synthesize reason; + ++(NegotiationFailed*) negotiationFailedWithReason:(NSString*)reason { + NegotiationFailed* instance = [NegotiationFailed new]; + instance->reason = reason; + return instance; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Negotation failed: %@", reason]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/RecipientUnavailable.h b/Signal/src/network/rtp/zrtp/RecipientUnavailable.h new file mode 100644 index 000000000..14de19d24 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/RecipientUnavailable.h @@ -0,0 +1,7 @@ +#import + +@interface RecipientUnavailable : NSObject + ++(RecipientUnavailable*) recipientUnavailable; + +@end diff --git a/Signal/src/network/rtp/zrtp/RecipientUnavailable.m b/Signal/src/network/rtp/zrtp/RecipientUnavailable.m new file mode 100644 index 000000000..aa201e5e8 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/RecipientUnavailable.m @@ -0,0 +1,13 @@ +#import "RecipientUnavailable.h" + +@implementation RecipientUnavailable + ++(RecipientUnavailable*) recipientUnavailable { + return [RecipientUnavailable new]; +} + +-(NSString *)description { + return @"Recipient unavailable"; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.h b/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.h new file mode 100644 index 000000000..4c5918318 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.h @@ -0,0 +1,14 @@ +#import + +/** + * + * ShortAuthenticationStringGenerator is utility class responsible for generating the Short Authentication String. + * Speaking the SAS is used to detect man-in-the-middle attacks. + * +**/ + +@interface ShortAuthenticationStringGenerator : NSObject + ++(NSString*)generateFromData:(NSData*)sasBytes; + +@end diff --git a/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.m b/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.m new file mode 100644 index 000000000..b93cdab18 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ShortAuthenticationStringGenerator.m @@ -0,0 +1,91 @@ +#import "Constraints.h" +#import "ShortAuthenticationStringGenerator.h" +#import "Util.h" + + +#define MIN_SAS_BYTES 2 + +const char* PGP_LIST_EVEN[] = { + "aardvark", "absurd", "accrue", "acme", "adrift", "adult", "afflict", "ahead", "aimless", + "Algol", "allow", "alone", "ammo", "ancient", "apple", "artist", "assume", "Athens", "atlas", + "Aztec", "baboon", "backfield", "backward", "banjo", "beaming", "bedlamp", "beehive", "beeswax", + "befriend", "Belfast", "berserk", "billiard", "bison", "blackjack", "blockade", "blowtorch", + "bluebird", "bombast", "bookshelf", "brackish", "breadline", "breakup", "brickyard", + "briefcase", "Burbank", "button", "buzzard", "cement", "chairlift", "chatter", "checkup", + "chisel", "choking", "chopper", "Christmas", "clamshell", "classic", "classroom", "cleanup", + "clockwork", "cobra", "commence", "concert", "cowbell", "crackdown", "cranky", "crowfoot", + "crucial", "crumpled", "crusade", "cubic", "dashboard", "deadbolt", "deckhand", "dogsled", + "dragnet", "drainage", "dreadful", "drifter", "dropper", "drumbeat", "drunken", "Dupont", + "dwelling", "eating", "edict", "egghead", "eightball", "endorse", "endow", "enlist", "erase", + "escape", "exceed", "eyeglass", "eyetooth", "facial", "fallout", "flagpole", "flatfoot", + "flytrap", "fracture", "framework", "freedom", "frighten", "gazelle", "Geiger", "glitter", + "glucose", "goggles", "goldfish", "gremlin", "guidance", "hamlet", "highchair", "hockey", + "indoors", "indulge", "inverse", "involve", "island", "jawbone", "keyboard", "kickoff", "kiwi", + "klaxon", "locale", "lockup", "merit", "minnow", "miser", "Mohawk", "mural", "music", + "necklace", "Neptune", "newborn", "nightbird", "Oakland", "obtuse", "offload", "optic", + "orca", "payday", "peachy", "pheasant", "physique", "playhouse", "Pluto", "preclude", "prefer", + "preshrunk", "printer", "prowler", "pupil", "puppy", "python", "quadrant", "quiver", "quota", + "ragtime", "ratchet", "rebirth", "reform", "regain", "reindeer", "rematch", "repay", "retouch", + "revenge", "reward", "rhythm", "ribcage", "ringbolt", "robust", "rocker", "ruffled", "sailboat", + "sawdust", "scallion", "scenic", "scorecard", "Scotland", "seabird", "select", "sentence", + "shadow", "shamrock", "showgirl", "skullcap", "skydive", "slingshot", "slowdown", "snapline", + "snapshot", "snowcap", "snowslide", "solo", "southward", "soybean", "spaniel", "spearhead", + "spellbind", "spheroid", "spigot", "spindle", "spyglass", "stagehand", "stagnate", "stairway", + "standard", "stapler", "steamship", "sterling", "stockman", "stopwatch", "stormy", "sugar", + "surmount", "suspense", "sweatband", "swelter", "tactics", "talon", "tapeworm", "tempest", + "tiger", "tissue", "tonic", "topmost", "tracker", "transit", "trauma", "treadmill", "Trojan", + "trouble", "tumor", "tunnel", "tycoon", "uncut", "unearth", "unwind", "uproot", "upset", + "upshot", "vapor", "village", "virus", "Vulcan", "waffle", "wallet", "watchword", "wayside", + "willow", "woodlark", "Zulu"}; + +const char* PGP_LIST_ODD[] = { + "adroitness", "adviser", "aftermath", "aggregate", "alkali", "almighty", "amulet", "amusement", + "antenna", "applicant", "Apollo", "armistice", "article", "asteroid", "Atlantic", "atmosphere", + "autopsy", "Babylon", "backwater", "barbecue", "belowground", "bifocals", "bodyguard", + "bookseller", "borderline", "bottomless", "Bradbury", "bravado", "Brazilian", "breakaway", + "Burlington", "businessman", "butterfat", "Camelot", "candidate", "cannonball", "Capricorn", + "caravan", "caretaker", "celebrate", "cellulose", "certify", "chambermaid", "Cherokee", + "Chicago", "clergyman", "coherence", "combustion", "commando", "company", "component", + "concurrent", "confidence", "conformist", "congregate", "consensus", "consulting", "corporate", + "corrosion", "councilman", "crossover", "crucifix", "cumbersome", "customer", "Dakota", + "decadence", "December", "decimal", "designing", "detector", "detergent", "determine", + "dictator", "dinosaur", "direction", "disable", "disbelief", "disruptive", "distortion", + "document", "embezzle", "enchanting", "enrollment", "enterprise", "equation", "equipment", + "escapade", "Eskimo", "everyday", "examine", "existence", "exodus", "fascinate", "filament", + "finicky", "forever", "fortitude", "frequency", "gadgetry", "Galveston", "getaway", "glossary", + "gossamer", "graduate", "gravity", "guitarist", "hamburger", "Hamilton", "handiwork", + "hazardous", "headwaters", "hemisphere", "hesitate", "hideaway", "holiness", "hurricane", + "hydraulic", "impartial", "impetus", "inception", "indigo", "inertia", "infancy", "inferno", + "informant", "insincere", "insurgent", "integrate", "intention", "inventive", "Istanbul", + "Jamaica", "Jupiter", "leprosy", "letterhead", "liberty", "maritime", "matchmaker", "maverick", + "Medusa", "megaton", "microscope", "microwave", "midsummer", "millionaire", "miracle", + "misnomer", "molasses", "molecule", "Montana", "monument", "mosquito", "narrative", "nebula", + "newsletter", "Norwegian", "October", "Ohio", "onlooker", "opulent", "Orlando", "outfielder", + "Pacific", "pandemic", "Pandora", "paperweight", "paragon", "paragraph", "paramount", + "passenger", "pedigree", "Pegasus", "penetrate", "perceptive", "performance", "pharmacy", + "phonetic", "photograph", "pioneer", "pocketful", "politeness", "positive", "potato", + "processor", "provincial", "proximate", "puberty", "publisher", "pyramid", "quantity", + "racketeer", "rebellion", "recipe", "recover", "repellent", "replica", "reproduce", "resistor", + "responsive", "retraction", "retrieval", "retrospect", "revenue", "revival", "revolver", + "sandalwood", "sardonic", "Saturday", "savagery", "scavenger", "sensation", "sociable", + "souvenir", "specialist", "speculate", "stethoscope", "stupendous", "supportive", "surrender", + "suspicious", "sympathy", "tambourine", "telephone", "therapist", "tobacco", "tolerance", + "tomorrow", "torpedo", "tradition", "travesty", "trombonist", "truncated", "typewriter", + "ultimate", "undaunted", "underfoot", "unicorn", "unify", "universe", "unravel", "upcoming", + "vacancy", "vagabond", "vertigo", "Virginia", "visitor", "vocalist", "voyager", "warranty", + "Waterloo", "whimsical", "Wichita", "Wilmington", "Wyoming", "yesteryear", "Yucatan"}; + +@implementation ShortAuthenticationStringGenerator + ++(NSString*) generateFromData:(NSData*)sasBytes { + require(sasBytes != nil); + require([sasBytes length] >= MIN_SAS_BYTES); + uint8_t wordIndexOne = [sasBytes uint8At:0]; + uint8_t wordIndexTwo = [sasBytes uint8At:1]; + + return [NSString stringWithFormat:@"%@ %@", + [NSString stringWithUTF8String:PGP_LIST_EVEN[wordIndexOne]], + [NSString stringWithUTF8String:PGP_LIST_ODD[wordIndexTwo]]]; +} +@end + diff --git a/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.h b/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.h new file mode 100644 index 000000000..14f1f74a4 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.h @@ -0,0 +1,17 @@ +#import + +#import "MasterSecret.h" +#import "SrtpSocket.h" + +/** + * + * A ZrtpHandshakeResult stores the master secret and secure rtp communication channel produced by a successful zrtp handshake. + * +**/ + +@interface ZrtpHandshakeResult : NSObject +@property (nonatomic,readonly) SrtpSocket* secureRtpSocket; +@property (nonatomic,readonly) MasterSecret* masterSecret; + ++(ZrtpHandshakeResult*) zrtpHandshakeResultWithSecureChannel:(SrtpSocket*)secureRtpSocket andMasterSecret:(MasterSecret*)masterSecret; +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.m b/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.m new file mode 100644 index 000000000..17f24adc7 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpHandshakeResult.m @@ -0,0 +1,17 @@ +#import "ZrtpHandshakeResult.h" + +@implementation ZrtpHandshakeResult + +@synthesize masterSecret, secureRtpSocket; + ++(ZrtpHandshakeResult*) zrtpHandshakeResultWithSecureChannel:(SrtpSocket*)secureRtpSocket andMasterSecret:(MasterSecret*)masterSecret { + require(secureRtpSocket != nil); + require(masterSecret != nil); + + ZrtpHandshakeResult* z = [ZrtpHandshakeResult new]; + z->masterSecret = masterSecret; + z->secureRtpSocket = secureRtpSocket; + return z; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.h b/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.h new file mode 100644 index 000000000..73d900eaf --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.h @@ -0,0 +1,23 @@ +#import + +#import "HandshakePacket.h" +#import "Environment.h" +#import "RtpSocket.h" + +/** + * + * A ZrtpHandshakeSocket sends/receives handshake packets by serializing them onto/from an rtp socket. + * +**/ + +@interface ZrtpHandshakeSocket : NSObject { +@private RtpSocket* rtpSocket; +@private PacketHandler* handshakePacketHandler; +@private uint16_t nextPacketSequenceNumber; +@private id sentPacketsLogger; +@private id receivedPacketsLogger; +} ++(ZrtpHandshakeSocket*) zrtpHandshakeSocketOverRtp:(RtpSocket*)rtpSocket; +-(void) send:(HandshakePacket*)packet; +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken; +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.m b/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.m new file mode 100644 index 000000000..c845d38b2 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpHandshakeSocket.m @@ -0,0 +1,50 @@ +#import "ZrtpHandshakeSocket.h" + +@implementation ZrtpHandshakeSocket + ++(ZrtpHandshakeSocket*) zrtpHandshakeSocketOverRtp:(RtpSocket*)rtpSocket { + require(rtpSocket != nil); + + ZrtpHandshakeSocket* z = [ZrtpHandshakeSocket new]; + z->rtpSocket = rtpSocket; + z->sentPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"sent"]; + z->receivedPacketsLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"received"]; + return z; +} +-(void) send:(HandshakePacket*)packet { + require(packet != nil); + uint16_t sequenceNumber = nextPacketSequenceNumber; + nextPacketSequenceNumber += 1; + [sentPacketsLogger markOccurrence:packet]; + [rtpSocket send:[packet embeddedIntoRtpPacketWithSequenceNumber:sequenceNumber + usingInteropOptions:rtpSocket->interopOptions]]; +} +-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(id)untilCancelledToken { + require(handler != nil); + requireState(handshakePacketHandler == nil); + + handshakePacketHandler = handler; + + PacketHandlerBlock packetHandler = ^(id packet) { + require(packet != nil); + require([packet isKindOfClass:[RtpPacket class]]); + RtpPacket* rtpPacket = packet; + + HandshakePacket* handshakePacket = nil; + @try { + handshakePacket = [HandshakePacket handshakePacketParsedFromRtpPacket:rtpPacket]; + } @catch (OperationFailed* ex) { + [handler handleError:ex relatedInfo:packet causedTermination:false]; + } + if (handshakePacket != nil) { + [receivedPacketsLogger markOccurrence:handshakePacket]; + [handshakePacketHandler handlePacket:handshakePacket]; + } + }; + + [rtpSocket startWithHandler:[PacketHandler packetHandler:packetHandler + withErrorHandler:[handler errorHandler]] + untilCancelled:untilCancelledToken]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpInitiator.h b/Signal/src/network/rtp/zrtp/ZrtpInitiator.h new file mode 100644 index 000000000..9bb8cf5e2 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpInitiator.h @@ -0,0 +1,38 @@ +#import +#import "ZrtpRole.h" +#import "DhPacketSharedSecretHashes.h" +#import "CallController.h" + +/** + * + * A ZrtpInitiator implements the 'initiator' role of the zrtp handshake. + * + * The initiator is NOT the one responsible for sending the first handshake packet. + * The 'initiator' name is related to what happens during signaling, not the zrtp handshake. + * The initiator receives Hello, sends Hello, receives HelloAck, sends Commit, receives DH1, sends DH2, receives Confirm1, sends Confirm2, and receives ConfirmAck + * +**/ + +@interface ZrtpInitiator : NSObject { + +@private CommitPacket* commitPacket; +@private DhPacketSharedSecretHashes* dhSharedSecretHashes; +@private DhPacket* foreignDH; +@private DhPacket* localDH; +@private HashChain* hashChain; +@private HelloPacket* foreignHello; +@private HelloPacket* localHello; +@private id keyAgreementParticipant; +@private id badPacketLogger; +@private NSArray* allowedKeyAgreementProtocols; +@private NSData* confirmIv; +@private MasterSecret* masterSecret; +@private PacketExpectation packetExpectation; +@private Zid* zid; +@private CallController* callController; +} + ++(ZrtpInitiator*) zrtpInitiatorWithCallController:(CallController*)callController; + + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpInitiator.m b/Signal/src/network/rtp/zrtp/ZrtpInitiator.m new file mode 100644 index 000000000..01c29c6bd --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpInitiator.m @@ -0,0 +1,181 @@ +#import "ConfirmPacket.h" +#import "CryptoTools.h" +#import "DH3KKeyAgreementProtocol.h" +#import "Constraints.h" +#import "FunctionalUtil.h" +#import "KeyChainStorage.h" +#import "MasterSecret.h" +#import "Util.h" +#import "ZrtpInitiator.h" +#import "PreferencesUtil.h" + +#define DHRS1_LENGTH 8 +#define DHRS2_LENGTH 8 +#define DHAUX_LENGTH 8 +#define DHPBX_LENGTH 8 +#define IV_LENGTH 16 + +@implementation ZrtpInitiator + ++(ZrtpInitiator*) zrtpInitiatorWithCallController:(CallController*)callController { + require(callController != nil); + + ZrtpInitiator* s = [ZrtpInitiator new]; + + s->allowedKeyAgreementProtocols = [[Environment getCurrent] keyAgreementProtocolsInDescendingPriority]; + s->dhSharedSecretHashes = [DhPacketSharedSecretHashes dhPacketSharedSecretHashesRandomized]; + s->zid = [KeyChainStorage getOrGenerateZid]; + s->confirmIv = [CryptoTools generateSecureRandomData:IV_LENGTH]; + s->hashChain = [HashChain hashChainWithSecureGeneratedData]; + s->badPacketLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"Bad Packet"]; + s->packetExpectation = EXPECTING_HELLO; + s->callController = callController; + + return s; +} + +-(MasterSecret*) getMasterSecret { + requireState([self hasHandshakeFinishedSuccessfully]); + return masterSecret; +} + +-(HandshakePacket*) initialPacket { + return nil; +} +-(bool) hasHandshakeFinishedSuccessfully { + return packetExpectation == EXPECTING_NOTHING; +} +-(HandshakePacket*) handlePacket:(HandshakePacket*)packet { + @try { + if (packetExpectation == EXPECTING_NOTHING) return nil; + else if (packetExpectation == EXPECTING_HELLO) return [self handleHello:packet]; + else if (packetExpectation == EXPECTING_HELLO_ACK) return [self handleHelloAck:packet]; + else if (packetExpectation == EXPECTING_DH) return [self handleDH:packet]; + else if (packetExpectation == EXPECTING_CONFIRM) return [self handleConfirmOne:packet]; + else if (packetExpectation == EXPECTING_CONFIRM_ACK) return [self handleConfirmAck:packet]; + else return nil; + + }@catch (SecurityFailure *exception) { + [callController terminateWithReason:CallTerminationType_HandshakeFailed withFailureInfo:exception andRelatedInfo:packet]; + return nil; + } @catch (OperationFailed* ex) { + [badPacketLogger markOccurrence:ex]; + return nil; + } +} + +-(HandshakePacket*) handleHello:(HandshakePacket*) packet { + foreignHello = [packet parsedAsHello]; + + [callController advanceCallProgressTo:CallProgressType_Securing]; + + keyAgreementParticipant = [self retrieveKeyAgreementParticpant]; + + localHello = [HelloPacket helloPacketWithDefaultsAndHashChain:hashChain + andZid:zid + andKeyAgreementProtocols:allowedKeyAgreementProtocols]; + + packetExpectation = EXPECTING_HELLO_ACK; + + return [localHello embeddedIntoHandshakePacket]; +} + +-(HandshakePacket*) handleHelloAck:(HandshakePacket*) packet { + [packet parsedAsHelloAck]; + + [self retrieveKeyAgreementParticpant]; + + localDH = [DhPacket dh2PacketWithHashChain:hashChain + andSharedSecretHashes:dhSharedSecretHashes + andKeyAgreer:keyAgreementParticipant]; + + commitPacket = [CommitPacket commitPacketWithDefaultSpecsAndKeyAgreementProtocol:[keyAgreementParticipant getProtocol] + andHashChain:hashChain + andZid:zid + andCommitmentToHello:foreignHello + andDhPart2:localDH]; + + packetExpectation = EXPECTING_DH; + + return [commitPacket embeddedIntoHandshakePacket]; +} + +-(HandshakePacket*) handleDH:(HandshakePacket*) packet { + foreignDH = [packet parsedAsDh1]; + + [foreignHello verifyMacWithHashChainH2:[[foreignDH hashChainH1] hashWithSha256]]; + + NSData* dhResult = [keyAgreementParticipant calculateKeyAgreementAgainstRemotePublicKey:[foreignDH publicKeyData]]; + + masterSecret = [MasterSecret masterSecretFromDhResult:dhResult + andInitiatorHello:localHello + andResponderHello:foreignHello + andCommit:commitPacket + andDhPart1:foreignDH + andDhPart2:localDH]; + + packetExpectation = EXPECTING_CONFIRM; + return [localDH embeddedIntoHandshakePacket]; +} + +-(HandshakePacket*) handleConfirmOne:(HandshakePacket*) packet { + ConfirmPacket* confirmPacket = [packet parsedAsConfirm1AuthenticatedWithMacKey:[masterSecret responderMacKey] andCipherKey:[masterSecret responderZrtpKey]]; + + NSData* preimage = [confirmPacket hashChainH0]; + [foreignDH verifyMacWithHashChainH0:preimage]; + + packetExpectation = EXPECTING_CONFIRM_ACK; + ConfirmPacket* confirm2Packet = [ConfirmPacket confirm2PacketWithHashChain:hashChain + andMacKey:[masterSecret initiatorMacKey] + andCipherKey:[masterSecret initiatorZrtpKey] + andIv:confirmIv]; + return [confirm2Packet embeddedIntoHandshakePacket]; +} + +-(HandshakePacket*) handleConfirmAck:(HandshakePacket*) packet { + [packet parsedAsConfAck]; + + packetExpectation = EXPECTING_NOTHING; + return nil; +} + +-(bool) isAuthenticatedAudioDataImplyingConf2Ack:(id)packet { + if (packetExpectation != EXPECTING_CONFIRM_ACK) return false; + if (![packet isKindOfClass:[RtpPacket class]]) return false; + + @try { + SrtpStream* incomingContext = [SrtpStream srtpStreamWithCipherKey:[masterSecret responderSrtpKey] + andMacKey:[masterSecret responderMacKey] + andCipherIvSalt:[masterSecret responderSrtpSalt]]; + [incomingContext verifyAuthenticationAndDecryptSecuredRtpPacket:packet]; + return true; + } @catch (OperationFailed* ex) { + return false; + } +} + +-(id) retrieveKeyAgreementParticpant{ + NSArray* idsOfProtocolsAllowedByPeer = [foreignHello agreeIdsIncludingImplied]; + + id bestCommonKeyAgreementProtocol = [allowedKeyAgreementProtocols firstMatchingElseNil:^int(id locallyAllowedProtocol) { + return [idsOfProtocolsAllowedByPeer containsObject:[locallyAllowedProtocol getId]]; + }]; + + // Note: should never fail to find a common protocol because DH3k support is required and implied + checkOperation(bestCommonKeyAgreementProtocol != nil); + + return [bestCommonKeyAgreementProtocol generateParticipantWithNewKeys]; +} + +-(SrtpSocket*) useKeysToSecureRtpSocket:(RtpSocket*)rtpSocket { + requireState([self hasHandshakeFinishedSuccessfully]); + return [SrtpSocket srtpSocketOverRtp:rtpSocket + andIncomingCipherKey:[masterSecret responderSrtpKey] + andIncomingMacKey:[masterSecret responderMacKey] + andIncomingSalt:[masterSecret responderSrtpSalt] + andOutgoingCipherKey:[masterSecret initiatorSrtpKey] + andOutgoingMacKey:[masterSecret initiatorMacKey] + andOutgoingSalt:[masterSecret initiatorSrtpSalt]]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpManager.h b/Signal/src/network/rtp/zrtp/ZrtpManager.h new file mode 100644 index 000000000..22c3ff0fe --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpManager.h @@ -0,0 +1,56 @@ +#import + +#import "CallController.h" +#import "CancelTokenSource.h" +#import "FutureSource.h" +#import "HandshakePacket.h" +#import "Logging.h" +#import "NegotiationFailed.h" +#import "RecipientUnavailable.h" +#import "ZrtpHandshakeResult.h" +#import "ZrtpHandshakeSocket.h" +#import "ZrtpRole.h" + +/** + * + * ZrtpManager is the 'entry point' for the zrtp code. + * ZrtpManager is a utility class for performing ZRTP handshakes, securing an RtpSocket into an SrtpSocket. + * + **/ + +@interface ZrtpManager : NSObject { +@private int32_t currentPacketTransmitCount; +@private bool handshakeCompletedSuccesfully; +@private bool done; + +@private CancelTokenSource* cancelTokenSource; +@private CancelTokenSource* currentRetransmit; +@private RtpSocket* rtpSocketToSecure; +@private ZrtpHandshakeSocket* handshakeSocket; +@private HandshakePacket* currentPacketToRetransmit; +@private id zrtpRole; +@private FutureSource* futureHandshakeResultSource; +@private CallController* callController; +} + +/// Starts a zrtp handshake over the given RtpSocket. +/// The given role type determines if we play the initiator role or the responder role, +/// All cryptographic keys and settings are either generated on the fly or pulled from the Environment. +/// +/// @return +/// The asynchronous result has type Future(ZrtpHandshakeResult). +/// If the handshake completes succesfully, the resulting ZrtpHandshakeResult contains the SrtpSocket to be used for sending audio. +/// If the handshake timeout or otherwise fails to complete, the result will contain a failure. +/// If the handshake is cancelled, the result will contain a failure containing the cancellation token. +/// +/// @param rtpSocket +/// The socket to perform the handshake over. +/// ZrtpManager will start the socket, handling and sending rtp packets over it. +/// +/// @param callController +/// Used to notify the outside about the progress of termination of the handshake. +/// If callController's cancel token is cancelled before or while the handshake is running, the handshake will be promptly aborted. ++(Future*) asyncPerformHandshakeOver:(RtpSocket*)rtpSocket + andCallController:(CallController*)callController; + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpManager.m b/Signal/src/network/rtp/zrtp/ZrtpManager.m new file mode 100644 index 000000000..605705e5f --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpManager.m @@ -0,0 +1,188 @@ +#import "ZrtpManager.h" +#import "ThreadManager.h" +#import "ZrtpHandshakeSocket.h" +#import "ZrtpHandshakeResult.h" +#import "ZrtpInitiator.h" +#import "ZrtpResponder.h" +#import "ConfirmAckPacket.h" +#import "TimeUtil.h" + +#define MAX_RETRANSMIT_COUNT 45 +#define MIN_RETRANSMIT_INTERVAL_SECONDS 0.15 +#define RETRANSMIT_INTERVAL_GROW_FACTOR 2.0 +#define MAX_RETRANSMIT_INTERVAL_SECONDS 1.5 +#define MAX_WAIT_FOR_RESPONDER_HELLO_SECONDS 60.0 + +@implementation ZrtpManager + ++(Future*) asyncPerformHandshakeOver:(RtpSocket*)rtpSocket + andCallController:(CallController*)callController { + + require(rtpSocket != nil); + require(callController != nil); + + ZrtpHandshakeSocket* handshakeChannel = [ZrtpHandshakeSocket zrtpHandshakeSocketOverRtp:rtpSocket]; + + id role = [callController isInitiator] + ? [ZrtpInitiator zrtpInitiatorWithCallController:callController] + : [ZrtpResponder zrtpResponderWithCallController:callController]; + + ZrtpManager* manager = [ZrtpManager zrtpManagerWithHandshakeSocket:handshakeChannel + andRtpSocketToSecure:rtpSocket + andZrtpRole:role + andCallController:callController]; + + return [manager asyncPerformHandshake]; +} + ++(ZrtpManager*) zrtpManagerWithHandshakeSocket:(ZrtpHandshakeSocket*)handshakeSocket + andRtpSocketToSecure:(RtpSocket*)rtpSocket + andZrtpRole:(id)zrtpRole + andCallController:(CallController*)callController { + + require(handshakeSocket != nil); + require(rtpSocket != nil); + require(callController != nil); + require(zrtpRole != nil); + + ZrtpManager* manager = [ZrtpManager new]; + + manager->callController = callController; + manager->zrtpRole = zrtpRole; + manager->futureHandshakeResultSource = [FutureSource new]; + manager->rtpSocketToSecure = rtpSocket; + manager->handshakeSocket = handshakeSocket; + manager->cancelTokenSource = [CancelTokenSource cancelTokenSource]; + [[callController untilCancelledToken] whenCancelledTerminate:manager]; + + [manager->futureHandshakeResultSource catchDo:^(id error) { + [callController terminateWithReason:CallTerminationType_HandshakeFailed + withFailureInfo:error + andRelatedInfo:nil]; + }]; + + return manager; +} + +-(Future*) asyncPerformHandshake { + PacketHandlerBlock packetHandler = ^(id packet) { + require(packet != nil); + require([packet isKindOfClass:[HandshakePacket class]]); + [self handleHandshakePacket:(HandshakePacket*)packet]; + }; + + ErrorHandlerBlock errorHandler = ^(id error, id relatedInfo, bool causedTermination) { + if (causedTermination) { + [self terminate]; + [futureHandshakeResultSource trySetFailure:error]; + return; + } + + // was Conf2Ack lost, and we're receiving encrypted audio data instead of handshake packets? + // (the RFC says to treat this as implying a Conf2Ack) + if ([zrtpRole isAuthenticatedAudioDataImplyingConf2Ack:relatedInfo]) { + // low-priority todo: Can we cache this bit of audio data, so that when the srtp socket is started the data comes out? + [self handleHandshakePacket:[[ConfirmAckPacket confirmAckPacket] embeddedIntoHandshakePacket]]; + } + }; + + [handshakeSocket startWithHandler:[PacketHandler packetHandler:packetHandler withErrorHandler:errorHandler] + untilCancelled:[cancelTokenSource getToken]]; + + HandshakePacket* initialPacket = [zrtpRole initialPacket]; + if (initialPacket == nil) { + [self scheduleTimeoutIfNoHello]; + } else { + [self setAndSendPacketToTransmit:initialPacket]; + } + + return futureHandshakeResultSource; +} + +-(void) setAndSendPacketToTransmit:(HandshakePacket*)packet { + currentPacketTransmitCount = 0; + currentPacketToRetransmit = packet; + [self transmitCurrentHandshakePacket]; +} + +-(void) transmitCurrentHandshakePacket { + if (done) return; + + requireState(currentPacketToRetransmit != nil); + [handshakeSocket send:currentPacketToRetransmit]; + + [self scheduleRetransmit]; +} +-(void) scheduleRetransmit { + double falloffFactor = pow(RETRANSMIT_INTERVAL_GROW_FACTOR, currentPacketTransmitCount); + NSTimeInterval delay = MIN(MIN_RETRANSMIT_INTERVAL_SECONDS * falloffFactor, MAX_RETRANSMIT_INTERVAL_SECONDS); + currentPacketTransmitCount += 1; + + [currentRetransmit cancel]; + currentRetransmit = [CancelTokenSource cancelTokenSource]; + + [TimeUtil scheduleRun:^{[self handleRetransmit];} + afterDelay:delay + onRunLoop:[ThreadManager lowLatencyThreadRunLoop] + unlessCancelled:[currentRetransmit getToken]]; +} + +-(void) handleRetransmit { + if (done) return; + currentPacketTransmitCount = 0; + if (currentPacketTransmitCount > MAX_RETRANSMIT_COUNT) { + done = true; + if (currentPacketToRetransmit == nil) { + [callController terminateWithReason:CallTerminationType_RecipientUnavailable + withFailureInfo:nil + andRelatedInfo:@"retransmit threshold exceeded"]; + } else { + [futureHandshakeResultSource trySetFailure:[NegotiationFailed negotiationFailedWithReason:@"retransmit threshold exceeded"]]; + } + return; + } + + [self transmitCurrentHandshakePacket]; +} +-(void) scheduleTimeoutIfNoHello { + void (^timeoutFail)(void) = ^{ + [callController terminateWithReason:CallTerminationType_RecipientUnavailable + withFailureInfo:nil + andRelatedInfo:nil]; + [futureHandshakeResultSource trySetFailure:[RecipientUnavailable recipientUnavailable]]; + }; + + currentRetransmit = [CancelTokenSource cancelTokenSource]; + [TimeUtil scheduleRun:timeoutFail + afterDelay:MAX_WAIT_FOR_RESPONDER_HELLO_SECONDS + onRunLoop:[ThreadManager lowLatencyThreadRunLoop] + unlessCancelled:[currentRetransmit getToken]]; +} + +-(void) terminate { + done = true; + [cancelTokenSource cancel]; + [currentRetransmit cancel]; +} + +-(void) handleHandshakePacket:(HandshakePacket*)packet { + require(packet != nil); + if (done) return; + + HandshakePacket* response = [zrtpRole handlePacket:packet]; + if (response != nil) { + [self setAndSendPacketToTransmit:response]; + } + if ([zrtpRole hasHandshakeFinishedSuccessfully]) { + done = true; + handshakeCompletedSuccesfully = true; + + SrtpSocket* secureChannel = [zrtpRole useKeysToSecureRtpSocket:rtpSocketToSecure]; + MasterSecret* masterSecret = [zrtpRole getMasterSecret]; + + ZrtpHandshakeResult* result = [ZrtpHandshakeResult zrtpHandshakeResultWithSecureChannel:secureChannel andMasterSecret:masterSecret]; + + [futureHandshakeResultSource trySetResult:result]; + } +} +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpResponder.h b/Signal/src/network/rtp/zrtp/ZrtpResponder.h new file mode 100644 index 000000000..2137f2754 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpResponder.h @@ -0,0 +1,35 @@ +#import +#import "ZrtpRole.h" +#import "DhPacketSharedSecretHashes.h" +#import "CallController.h" + +/** + * + * A ZrtpResponder implements the 'responder' role of the zrtp handshake. + * + * The responder SENDS the first handshake packet. + * The 'responder' name is related to what happens during signaling, not the zrtp handshake. + * The responder sends Hello, receives Hello, sends HelloAck, receives Commit, sends DH1, receives DH2, sends Confirm1, receives Confirm2, and sends ConfirmAck + * +**/ + +@interface ZrtpResponder : NSObject { +@private HelloPacket* localHello; +@private HelloPacket* foreignHello; +@private CommitPacket* foreignCommit; +@private DhPacket* localDH; +@private DhPacket* foreignDH; +@private NSArray* allowedKeyAgreementProtocols; +@private id keyAgreementParticipant; +@private HashChain* hashChain; +@private MasterSecret* masterSecret; +@private NSData* confirmIv; +@private DhPacketSharedSecretHashes* dhSharedSecretHashes; +@private id badPacketLogger; +@private PacketExpectation packetExpectation; +@private CallController* callController; +} + ++(ZrtpResponder*) zrtpResponderWithCallController:(CallController*)callController; + +@end diff --git a/Signal/src/network/rtp/zrtp/ZrtpResponder.m b/Signal/src/network/rtp/zrtp/ZrtpResponder.m new file mode 100644 index 000000000..072bcbf57 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/ZrtpResponder.m @@ -0,0 +1,161 @@ +#import "CommitPacket.h" +#import "ConfirmPacket.h" +#import "DH3KKeyAgreementProtocol.h" +#import "KeyChainStorage.h" +#import "PreferencesUtil.h" +#import "FunctionalUtil.h" +#import "MasterSecret.h" +#import "Util.h" +#import "ZrtpResponder.h" +#import "HelloAckPacket.h" +#import "ConfirmAckPacket.h" + +#define DHRS1_LENGTH 8 +#define DHRS2_LENGTH 8 +#define DHAUX_LENGTH 8 +#define DHPBX_LENGTH 8 +#define IV_LENGTH 16 + +@implementation ZrtpResponder + ++(ZrtpResponder*) zrtpResponderWithCallController:(CallController*)callController { + require(callController != nil); + + ZrtpResponder* s = [ZrtpResponder new]; + + s->confirmIv = [CryptoTools generateSecureRandomData:IV_LENGTH]; + s->dhSharedSecretHashes = [DhPacketSharedSecretHashes dhPacketSharedSecretHashesRandomized]; + s->allowedKeyAgreementProtocols = [[Environment getCurrent] keyAgreementProtocolsInDescendingPriority]; + s->hashChain = [HashChain hashChainWithSecureGeneratedData]; + s->badPacketLogger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"Bad Packet"]; + + s->localHello = [HelloPacket helloPacketWithDefaultsAndHashChain:s->hashChain + andZid:[KeyChainStorage getOrGenerateZid] + andKeyAgreementProtocols:s->allowedKeyAgreementProtocols]; + s->packetExpectation = EXPECTING_HELLO; + s->callController = callController; + return s; +} + +-(HandshakePacket*) initialPacket { + [callController advanceCallProgressTo:CallProgressType_Securing]; + + return [localHello embeddedIntoHandshakePacket]; +} +-(bool) hasHandshakeFinishedSuccessfully { + return packetExpectation == EXPECTING_NOTHING; +} +-(HandshakePacket*) handlePacket:(HandshakePacket*)packet { + @try { + if (packetExpectation == EXPECTING_NOTHING){ return nil;} + else if (packetExpectation == EXPECTING_HELLO){ return [self handleHello:packet];} + else if (packetExpectation == EXPECTING_COMMIT){ return [self handleCommit:packet];} + else if (packetExpectation == EXPECTING_DH){ return [self handleDH:packet];} + else if (packetExpectation == EXPECTING_CONFIRM) { return [self handleConfirmTwo:packet];} + else {return nil;} + } @catch (SecurityFailure* ex) { + [callController terminateWithReason:CallTerminationType_HandshakeFailed withFailureInfo:ex andRelatedInfo:packet]; + return nil; + } @catch (OperationFailed* ex) { + [badPacketLogger markOccurrence:ex]; + return nil; + } +} + +-(HandshakePacket*) handleHello:(HandshakePacket*)packet { + foreignHello = [packet parsedAsHello]; + + packetExpectation = EXPECTING_COMMIT; + + return [[HelloAckPacket helloAckPacket] embeddedIntoHandshakePacket]; +} + + +-(HandshakePacket*) handleCommit:(HandshakePacket*)packet { + CommitPacket* cp = [packet parsedAsCommitPacket]; + [foreignHello verifyMacWithHashChainH2:[cp h2]]; + + foreignCommit = cp; + + keyAgreementParticipant = [self retrieveKeyAgreementParticipant]; + + localDH = [DhPacket dh1PacketWithHashChain:hashChain + andSharedSecretHashes:dhSharedSecretHashes + andKeyAgreer:keyAgreementParticipant]; + + packetExpectation = EXPECTING_DH; + + return [localDH embeddedIntoHandshakePacket]; +} + +-(MasterSecret*) getMasterSecret { + requireState([self hasHandshakeFinishedSuccessfully]); + return masterSecret; +} + +-(HandshakePacket*) handleDH:(HandshakePacket*)packet { + foreignDH = [packet parsedAsDh2]; + + [foreignCommit verifyMacWithHashChainH1:[foreignDH hashChainH1]]; + [foreignCommit verifyCommitmentAgainstHello:localHello + andDhPart2:foreignDH]; + + NSData* dhResult = [keyAgreementParticipant calculateKeyAgreementAgainstRemotePublicKey:[foreignDH publicKeyData]]; + + masterSecret = [MasterSecret masterSecretFromDhResult:dhResult + andInitiatorHello:foreignHello + andResponderHello:localHello + andCommit:foreignCommit + andDhPart1:localDH + andDhPart2:foreignDH]; + + packetExpectation = EXPECTING_CONFIRM; + + ConfirmPacket* confirm2Packet = [ConfirmPacket confirm1PacketWithHashChain:hashChain + andMacKey:[masterSecret responderMacKey] + andCipherKey:[masterSecret responderZrtpKey] + andIv:confirmIv]; + + return [confirm2Packet embeddedIntoHandshakePacket]; +} + + +-(HandshakePacket*) handleConfirmTwo:(HandshakePacket*)packet { + ConfirmPacket* confirmPacket = [packet parsedAsConfirm2AuthenticatedWithMacKey:[masterSecret initiatorMacKey] andCipherKey:[masterSecret initiatorZrtpKey]]; + + [foreignDH verifyMacWithHashChainH0:[confirmPacket hashChainH0]]; + + packetExpectation = EXPECTING_NOTHING; + + if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_TESTING_OPTION_LOSE_CONF_ACK_ON_PURPOSE]) { + return nil; + } + + return [[ConfirmAckPacket confirmAckPacket] embeddedIntoHandshakePacket]; +} + +-(bool) isAuthenticatedAudioDataImplyingConf2Ack:(id)packet { + return false; // responder doesn't expect to receive Conf2Ack +} + +-(id) retrieveKeyAgreementParticipant{ + id matchingKeyAgreeProtocol = [allowedKeyAgreementProtocols firstMatchingElseNil:^int(id a) { + return [[foreignCommit agreementSpecId] isEqualToData:[a getId]]; + }]; + + checkOperation(matchingKeyAgreeProtocol != nil); + return [matchingKeyAgreeProtocol generateParticipantWithNewKeys]; +} + +-(SrtpSocket*) useKeysToSecureRtpSocket:(RtpSocket*)rtpSocket { + requireState([self hasHandshakeFinishedSuccessfully]); + return [SrtpSocket srtpSocketOverRtp:rtpSocket + andIncomingCipherKey:[masterSecret initiatorSrtpKey] + andIncomingMacKey:[masterSecret initiatorMacKey] + andIncomingSalt:[masterSecret initiatorSrtpSalt] + andOutgoingCipherKey:[masterSecret responderSrtpKey] + andOutgoingMacKey:[masterSecret responderMacKey] + andOutgoingSalt:[masterSecret responderSrtpSalt]]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.h b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.h new file mode 100644 index 000000000..4a71aa212 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.h @@ -0,0 +1,23 @@ +#import + +#import "KeyAgreementParticipant.h" +#import "DH3KKeyAgreementProtocol.h" +#import "EvpKeyAgreement.h" + +/** + * + * DH3KKeyAgreementParticipant is used to do Diffie-Hellman key agreement. + * Each participant has access to the protocol parameters, and their own key material. + * +**/ + +@interface DH3KKeyAgreementParticipant : NSObject { + +@private DH3KKeyAgreementProtocol* protocol; +@private EvpKeyAgreement* evpKeyAgreement; + +} + ++(DH3KKeyAgreementParticipant*) participantWithPrivateKeyGeneratedForProtocol:(DH3KKeyAgreementProtocol*) protocol; + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.m b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.m new file mode 100644 index 000000000..e75c2f7d6 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementParticipant.m @@ -0,0 +1,27 @@ +#import "DH3KKeyAgreementParticipant.h" + +@implementation DH3KKeyAgreementParticipant + ++(DH3KKeyAgreementParticipant*) participantWithPrivateKeyGeneratedForProtocol:(DH3KKeyAgreementProtocol*) protocol { + assert(nil != protocol); + + DH3KKeyAgreementParticipant* participant = [DH3KKeyAgreementParticipant new]; + + participant->protocol = protocol; + participant->evpKeyAgreement = [EvpKeyAgreement evpDh3kKeyAgreementWithModulus:[protocol getModulus] andGenerator:[protocol getGenerator]]; + return participant; +} + +-(id) getProtocol{ + return self->protocol; +} + +-(NSData*) getPublicKeyData{ + return [self->evpKeyAgreement getPublicKey]; +} + +-(NSData*) calculateKeyAgreementAgainstRemotePublicKey:(NSData*)remotePublicKey{ + return [self->evpKeyAgreement getSharedSecretForRemotePublicKey:remotePublicKey]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.h b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.h new file mode 100644 index 000000000..a82f0013e --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.h @@ -0,0 +1,22 @@ +#import +#import "KeyAgreementProtocol.h" +#import "CryptoTools.h" + +#define DH3k_KEY_AGREEMENT_ID [@"DH3k" encodedAsUtf8] + +/** + * + * DH3KKeyAgreementProtocol is used to generate participants for Diffie-Hellman key agreement. + * +**/ + +@interface DH3KKeyAgreementProtocol : NSObject { +@private NSData* modulus; +@private NSData* generator; +} + ++(DH3KKeyAgreementProtocol*) protocolWithModulus:(NSData*)modulus andGenerator:(NSData*)generator; +-(NSData*) getGenerator; +-(NSData*) getModulus; + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.m b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.m new file mode 100644 index 000000000..90911e29b --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/DH3KKeyAgreementProtocol.m @@ -0,0 +1,33 @@ +#import "DH3KKeyAgreementProtocol.h" +#import "DH3KKeyAgreementParticipant.h" +#import "Constraints.h" +#import "CryptoTools.h" +#import "Util.h" +#import "Conversions.h" + +#import "EvpKeyAgreement.h" + +@implementation DH3KKeyAgreementProtocol + ++(DH3KKeyAgreementProtocol*) protocolWithModulus:(NSData*)modulus andGenerator:(NSData*)generator { + assert(nil != modulus); + assert(nil != generator); + + DH3KKeyAgreementProtocol* keyAgreementProtocol = [DH3KKeyAgreementProtocol new]; + keyAgreementProtocol->generator = generator; + keyAgreementProtocol->modulus = modulus; + return keyAgreementProtocol; +} +-(NSData*) getGenerator { + return generator; +} +-(NSData*) getModulus { + return modulus; +} +-(id) generateParticipantWithNewKeys { + return [DH3KKeyAgreementParticipant participantWithPrivateKeyGeneratedForProtocol:self]; +} +-(NSData*) getId { + return DH3k_KEY_AGREEMENT_ID; +} +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.h b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.h new file mode 100644 index 000000000..ce10bf652 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.h @@ -0,0 +1,14 @@ + +#import +#import "KeyAgreementParticipant.h" +#import "EC25KeyAgreementProtocol.h" +#import "EvpKeyAgreement.h" + +@interface EC25KeyAgreementParticipant : NSObject{ +@private EvpKeyAgreement* evpKeyAgreement; +@private EC25KeyAgreementProtocol* protocol; +} + ++(EC25KeyAgreementParticipant*) participantWithPrivateKeyGeneratedForProtocol:(EC25KeyAgreementProtocol*)protocol; + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.m b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.m new file mode 100644 index 000000000..f22f3752b --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementParticipant.m @@ -0,0 +1,29 @@ +#import "EC25KeyAgreementParticipant.h" +#import "EvpKeyAgreement.h" +#import "Constraints.h" + +@implementation EC25KeyAgreementParticipant + ++(EC25KeyAgreementParticipant*) participantWithPrivateKeyGeneratedForProtocol:(EC25KeyAgreementProtocol*)protocol{ + return [[EC25KeyAgreementParticipant alloc] initWithProtocol:protocol]; +} + +-(EC25KeyAgreementParticipant*) initWithProtocol:(EC25KeyAgreementProtocol*) proto { + protocol = proto; + evpKeyAgreement = [EvpKeyAgreement evpEc25KeyAgreement]; + [evpKeyAgreement generateKeyPair]; + return self; +} + +-(id) getProtocol{ + return protocol; +} + +-(NSData*) getPublicKeyData{ + return [evpKeyAgreement getPublicKey]; +} + +-(NSData*) calculateKeyAgreementAgainstRemotePublicKey:(NSData*)remotePublicKey{ + return [evpKeyAgreement getSharedSecretForRemotePublicKey:remotePublicKey]; +} +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.h b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.h new file mode 100644 index 000000000..7ebbc07f3 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.h @@ -0,0 +1,13 @@ +#import +#import "KeyAgreementProtocol.h" + +#define EC25_KEY_AGREEMENT_ID [@"EC25" encodedAsUtf8] + +@interface EC25KeyAgreementProtocol : NSObject{ +} + ++(EC25KeyAgreementProtocol*) protocol; +-(id) generateParticipantWithNewKeys; +-(NSData*) getId; + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.m b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.m new file mode 100644 index 000000000..82bec2a0a --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EC25KeyAgreementProtocol.m @@ -0,0 +1,15 @@ +#import "EC25KeyAgreementProtocol.h" +#import "EC25KeyAgreementParticipant.h" +@implementation EC25KeyAgreementProtocol + ++(EC25KeyAgreementProtocol*) protocol{ + return [EC25KeyAgreementProtocol new]; +} + +-(id) generateParticipantWithNewKeys{ + return [EC25KeyAgreementParticipant participantWithPrivateKeyGeneratedForProtocol:self]; +} +-(NSData*) getId{ + return EC25_KEY_AGREEMENT_ID; +} +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.h b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.h new file mode 100644 index 000000000..a49ff6661 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.h @@ -0,0 +1,16 @@ +#import +#import "DH3KKeyAgreementProtocol.h" +#import "KeyAgreementParticipant.h" + +@interface EvpKeyAgreement : NSObject { +} + ++(EvpKeyAgreement*) evpDh3kKeyAgreementWithModulus:(NSData*) modulus andGenerator:(NSData*) generator; ++(EvpKeyAgreement*) evpEc25KeyAgreement; + + +-(void) generateKeyPair; +-(NSData*) getPublicKey; +-(NSData*) getSharedSecretForRemotePublicKey:(NSData*) publicKey; + +@end diff --git a/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m new file mode 100644 index 000000000..faa5b7c9d --- /dev/null +++ b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m @@ -0,0 +1,330 @@ +#import "EvpKeyAgreement.h" +#import "Constraints.h" +#import "NumberUtil.h" +#import +#import +#import +#import + +#define checkEvpSucess(expr, desc) checkSecurityOperation((expr) == 1, desc) +#define checkEvpNotNull(expr, desc) checkSecurityOperation((expr) != NULL, desc) + +#define EC25_COORDINATE_LENGTH 32 +#define NAMED_ELLIPTIC_CURVE NID_X9_62_prime256v1 + +enum KeyAgreementType { + KeyAgreementType_DH = EVP_PKEY_DH, + KeyAgreementType_ECDH = EVP_PKEY_EC +}; + +@implementation EvpKeyAgreement { + EVP_PKEY *params; + EVP_PKEY *pkey; + enum KeyAgreementType keyAgreementType; +} + +#pragma mark Constructors / Initialization + ++(EvpKeyAgreement*) evpDh3kKeyAgreementWithModulus:(NSData*) modulus andGenerator:(NSData*) generator{ + EvpKeyAgreement* evpKeyAgreement = [[EvpKeyAgreement alloc] initWithKeyType:KeyAgreementType_DH ]; + assert(nil != modulus); + assert(nil != generator); + + [evpKeyAgreement generateDhParametersWithModulus:modulus andGenerator:generator]; + [evpKeyAgreement generateKeyPair]; + return evpKeyAgreement; +} + ++(EvpKeyAgreement*) evpEc25KeyAgreement { + EvpKeyAgreement* evpKeyAgreement = [[EvpKeyAgreement alloc] initWithKeyType:KeyAgreementType_ECDH ]; + [evpKeyAgreement generateEc25Parameters]; + [evpKeyAgreement generateKeyPair]; + return evpKeyAgreement; +} + + +-(EvpKeyAgreement*) initWithKeyType:(enum KeyAgreementType) keyType { + if( nil == [super init]){ + return nil; + } + + keyAgreementType = keyType; + return self; +} + +-(void)dealloc { + [self freeKey:params]; + [self freeKey:pkey]; +} + + + +-(EVP_PKEY_CTX*) createParameterContext{ + EVP_PKEY_CTX* ctx; + ctx = EVP_PKEY_CTX_new_id(keyAgreementType, NULL); + checkEvpNotNull(ctx , @"pctx_new_id"); + + int ret = EVP_PKEY_paramgen_init(ctx); + checkEvpSucess(ret, @"paramgen_init"); + + return ctx; +} + + +#pragma mark Parameter/Key Generation +-(void) generateDhParametersWithModulus:(NSData*) modulus andGenerator:(NSData*) generator { + + EVP_PKEY_CTX* pctx = [self createParameterContext]; + DH* dh = DH_new(); + + @try{ + checkEvpNotNull(dh, @"dh_new"); + + dh->p= [self generateBignumberFor:modulus]; + dh->g= [self generateBignumberFor:generator]; + + if ((dh->p == NULL) || (dh->g == NULL)) + { + [self reportError:@"DH Parameters uninitialized"]; + } + + [self createNewEvpKeyFreePreviousIfNessesary:¶ms]; + EVP_PKEY_set1_DH(params, dh); + + } @finally { + DH_free(dh); + EVP_PKEY_CTX_free(pctx); + } +} + +-(void) generateEc25Parameters { + EVP_PKEY_CTX* pctx = [self createParameterContext]; + + int ret; + ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NAMED_ELLIPTIC_CURVE); + checkEvpSucess(ret, @"pctx_ec_init"); + + ret = EVP_PKEY_paramgen(pctx, ¶ms); + checkEvpSucess(ret,@"pctx_paramgen" ); + + EVP_PKEY_CTX_free(pctx); +} + + +-(void) generateKeyPair { + int ret; + EVP_PKEY_CTX* kctx; + + checkEvpNotNull(params, @"parameters uninitialized"); + + kctx = EVP_PKEY_CTX_new(params, NULL); + checkEvpNotNull(kctx, @"key_ctx"); + + ret = EVP_PKEY_keygen_init(kctx); + checkEvpSucess(ret, @"keygen_init"); + + ret = EVP_PKEY_keygen(kctx, &pkey); + checkEvpSucess(ret, @"keygen"); + + EVP_PKEY_CTX_free(kctx); +} + + +-(NSData*) getSharedSecretForRemotePublicKey:(NSData*) publicKey{ + size_t secret_len; + unsigned char* secret; + + EVP_PKEY* peerkey = [self deserializePublicKey:[publicKey bytes] withLength:[publicKey length]]; + EVP_PKEY_CTX* ctx; + + + ctx = EVP_PKEY_CTX_new(pkey, NULL); + checkEvpNotNull(ctx, @"ctx_new"); + + checkEvpSucess(EVP_PKEY_derive_init(ctx), @"derive_init"); + checkEvpSucess(EVP_PKEY_derive_set_peer(ctx, peerkey), @"set_peer"); + checkEvpSucess(EVP_PKEY_derive(ctx, NULL, &secret_len), @"derive_step1"); + + secret = OPENSSL_malloc(secret_len); + checkEvpNotNull(secret, @"secret_malloc"); + + checkEvpSucess(EVP_PKEY_derive(ctx, secret, &secret_len), @"derive_step2"); + + NSData* secretData = [NSData dataWithBytes:secret length:secret_len]; + + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peerkey); + OPENSSL_free(secret); + + return secretData; +} + +#pragma mark Public Key Serialization + +-(NSData*) getPublicKey{ + switch (keyAgreementType) { + case KeyAgreementType_DH: + return [self serializeDhPublicKey:pkey]; + case KeyAgreementType_ECDH: + return [self serializeEcPublicKey:pkey]; + default: + [self reportError:@"Undefined KeyType"]; + } +} + +-(NSData*) serializeDhPublicKey:(EVP_PKEY*)evkey { + + DH* dh; + unsigned char* buf; + + dh = EVP_PKEY_get1_DH(evkey); + + int bufsize = BN_num_bytes(dh->pub_key); + buf = OPENSSL_malloc(bufsize); + checkEvpNotNull(buf, @"dh_pubkey_buffer"); + + BN_bn2bin(dh->pub_key, buf); + NSData* pub_key = [NSData dataWithBytes:buf length:(unsigned int)bufsize]; + + DH_free(dh); + OPENSSL_free(buf); + + return pub_key; +} + + +-(NSData*) serializeEcPublicKey:(EVP_PKEY*)evkey { + + EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(evkey); + const EC_POINT* ec_pub = EC_KEY_get0_public_key(ec_key); + const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); + + NSData* data = [self packEcCoordinatesFromEcPoint:ec_pub withEcGroup:ec_group]; + + EC_KEY_free(ec_key); + + return data; +} + +-(EVP_PKEY*) deserializePublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { + switch (keyAgreementType) { + case KeyAgreementType_DH: + return [self deserializeDhPublicKey:buf withLength:bufsize]; + case KeyAgreementType_ECDH: + return [self deserializeEcPublicKey:buf withLength:bufsize]; + default: + [self reportError:@"Undefined KeyType"]; + } +} + +-(EVP_PKEY*) deserializeDhPublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { + + EVP_PKEY* evpk = EVP_PKEY_new(); + DH* dh = DH_new(); + + BIGNUM* bn = BN_new(); + BN_bin2bn(buf, [NumberUtil assertConvertNSUIntegerToInt:bufsize], bn); + dh->pub_key = bn; + + EVP_PKEY_assign_DH(evpk, dh); + + return evpk; +} + + +-(EVP_PKEY*) deserializeEcPublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { + EC_KEY* eck = EC_KEY_new_by_curve_name(NAMED_ELLIPTIC_CURVE); + const EC_GROUP* ecg = EC_KEY_get0_group(eck); + + EC_POINT* ecp = EC_POINT_new(ecg); + [self unpackEcCoordinatesFromBuffer:buf ofSize:bufsize toEcPoint:ecp withEcGroup:ecg]; + + EVP_PKEY* evpk = EVP_PKEY_new(); + EC_KEY_set_public_key(eck, ecp); + EVP_PKEY_assign_EC_KEY(evpk, eck); + + EC_POINT_free(ecp); + + return evpk; + +} + +-(NSData*) packEcCoordinatesFromEcPoint:(const EC_POINT*) ec_point withEcGroup:(const EC_GROUP*) ec_group { + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + + unsigned char* buf_x; size_t bufsize_x; + unsigned char* buf_y; size_t bufsize_y; + + EC_POINT_get_affine_coordinates_GFp(ec_group, ec_point, x, y, NULL); + + bufsize_x = [NumberUtil assertConvertIntToNSUInteger:BN_num_bytes(x)]; + bufsize_y = [NumberUtil assertConvertIntToNSUInteger:BN_num_bytes(y)]; + + checkEvpNotNull( buf_x = OPENSSL_malloc(bufsize_x), @"ec_x_buffer"); + checkEvpNotNull( buf_y = OPENSSL_malloc(bufsize_y), @"ec_y_buffer"); + + BN_bn2bin(x, buf_x); + BN_bn2bin(y, buf_y); + + NSMutableData* data = [NSMutableData dataWithBytes:buf_x length:bufsize_x]; + [data appendData:[NSData dataWithBytes:buf_y length:bufsize_y]]; + + OPENSSL_free(buf_x); + OPENSSL_free(buf_y); + + BN_free(x); + BN_free(y); + + return data; +} + +-(void) unpackEcCoordinatesFromBuffer:(const unsigned char*) buffer + ofSize:(size_t) bufsize + toEcPoint:(EC_POINT*) ecp + withEcGroup:(const EC_GROUP*) ecg { + + checkOperation(2*EC25_COORDINATE_LENGTH == bufsize); + + BIGNUM* x = BN_new(); + BIGNUM* y = BN_new(); + + BN_bin2bn(buffer, EC25_COORDINATE_LENGTH, x); + BN_bin2bn(buffer+EC25_COORDINATE_LENGTH, EC25_COORDINATE_LENGTH, y); + + EC_POINT_set_affine_coordinates_GFp(ecg, ecp, x, y, NULL); + + BN_free(x); + BN_free(y); +} + +#pragma mark Helper Functions + +-(void) createNewEvpKeyFreePreviousIfNessesary:(EVP_PKEY**) evpKey{ + if(NULL != evpKey){ + [self freeKey:(*evpKey)]; + } + (*evpKey) = EVP_PKEY_new(); +} + +-(void) freeKey:(EVP_PKEY*) evpKey{ + if (NULL != evpKey){ + EVP_PKEY_free(evpKey); + } +} + +-(BIGNUM*) generateBignumberFor:(NSData*) data{ + assert([data length] <= INT_MAX ); + return BN_bin2bn([data bytes], (int)data.length, NULL); +} + +-(void) reportError:(NSString*) errorString{ + [SecurityFailure raise:[NSString stringWithFormat:@"Security related failure: %@ (in %s at line %d)", errorString,__FILE__,__LINE__]]; +} + + + + + + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/CommitPacket.h b/Signal/src/network/rtp/zrtp/packets/CommitPacket.h new file mode 100644 index 000000000..9571ab90b --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/CommitPacket.h @@ -0,0 +1,87 @@ +#import "HandshakePacket.h" +#import "HashChain.h" +#import "ZID.h" +#import "HelloPacket.h" +#import "DhPacket.h" + +/** + * + * The Commit packet sent by the initiator to commit to their key agreement details without revealing them. + * Specifies what crypto must be used by both sides. + * + * (extension header of RTP packet containing Commit handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length=29 words | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="Commit " (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Hash image H2 (8 words) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | ZID (3 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | hash algorithm | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | cipher algorithm | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | auth tag type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Key Agreement Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SAS Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | hvi (8 words) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MAC (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + + +@interface CommitPacket : NSObject { +@private HandshakePacket* embedding; +} +@property (nonatomic,readonly) NSData* h2; +@property (nonatomic,readonly) NSData* hashSpecId; +@property (nonatomic,readonly) NSData* cipherSpecId; +@property (nonatomic,readonly) NSData* authSpecId; +@property (nonatomic,readonly) NSData* agreementSpecId; +@property (nonatomic,readonly) NSData* sasSpecId; +@property (nonatomic,readonly) Zid* zid; +@property (nonatomic,readonly) NSData* dhPart2HelloCommitment; + ++(CommitPacket*) commitPacketWithDefaultSpecsAndKeyAgreementProtocol:(id)keyAgreementProtocol + andHashChain:(HashChain*)hashChain + andZid:(Zid*)zid + andCommitmentToHello:(HelloPacket*)hello + andDhPart2:(DhPacket*)dhPart2; + ++(CommitPacket*) commitPacketWithHashChainH2:(NSData*)h2 + andZid:(Zid*)zid + andHashSpecId:(NSData*)hashSpecId + andCipherSpecId:(NSData*)cipherSpecId + andAuthSpecId:(NSData*)authSpecId + andAgreeSpecId:(NSData*)agreeSpecId + andSasSpecId:(NSData*)sasSpecId + andDhPart2HelloCommitment:(NSData*)dhPart2HelloCommitment + andHmacKey:(NSData*)hmacKey; + +-(void) verifyCommitmentAgainstHello:(HelloPacket*)hello + andDhPart2:(DhPacket*)dhPart2; + ++(CommitPacket*) commitPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket; + +-(void) verifyMacWithHashChainH1:(NSData*)hashChainH1; + +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/CommitPacket.m b/Signal/src/network/rtp/zrtp/packets/CommitPacket.m new file mode 100644 index 000000000..369196d67 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/CommitPacket.m @@ -0,0 +1,180 @@ +#import "CommitPacket.h" +#import "Util.h" +#import "CryptoTools.h" + +@implementation CommitPacket + +@synthesize agreementSpecId, authSpecId, cipherSpecId, dhPart2HelloCommitment, h2, hashSpecId, sasSpecId, zid; + + +#define ZID_LENGTH 12 +#define HASH_SPEC_LENGTH 4 +#define CIPHER_SPEC_LENGTH 4 +#define AUTH_SPEC_LENGTH 4 +#define AGREE_SPEC_LENGTH 4 +#define SAS_SPEC_LENGTH 4 +#define COMMIT_LENGTH 32 + +#define HASH_SPEC_OFFSET HASH_CHAIN_ITEM_LENGTH + ZID_LENGTH +#define CIPHER_SPEC_OFFSET HASH_SPEC_OFFSET + HASH_SPEC_LENGTH +#define AUTH_SPEC_OFFSET CIPHER_SPEC_OFFSET + CIPHER_SPEC_LENGTH +#define AGREE_SPEC_OFFSET AUTH_SPEC_OFFSET + AUTH_SPEC_LENGTH +#define SAS_SPEC_OFFSET AGREE_SPEC_OFFSET + AGREE_SPEC_LENGTH +#define COMMIT_OFFSET SAS_SPEC_OFFSET + SAS_SPEC_LENGTH + + ++(CommitPacket*) commitPacketWithDefaultSpecsAndKeyAgreementProtocol:(id)keyAgreementProtocol + andHashChain:(HashChain*)hashChain + andZid:(Zid*)zid + andCommitmentToHello:(HelloPacket*)hello + andDhPart2:(DhPacket*)dhPart2 { + + require(keyAgreementProtocol != nil); + require(hashChain != nil); + require(zid != nil); + require(hello != nil); + require(dhPart2 != nil); + + NSData* dhPart2Data = [[dhPart2 embeddedIntoHandshakePacket] dataUsedForAuthentication]; + NSData* helloData = [[hello embeddedIntoHandshakePacket] dataUsedForAuthentication]; + return [CommitPacket commitPacketWithHashChainH2:[hashChain h2] + andZid:zid + andHashSpecId:COMMIT_DEFAULT_HASH_SPEC_ID + andCipherSpecId:COMMIT_DEFAULT_CIPHER_SPEC_ID + andAuthSpecId:COMMIT_DEFAULT_AUTH_SPEC_ID + andAgreeSpecId:[keyAgreementProtocol getId] + andSasSpecId:COMMIT_DEFAULT_SAS_SPEC_ID + andDhPart2HelloCommitment:[[@[dhPart2Data, helloData] concatDatas] hashWithSha256] + andHmacKey:[hashChain h1]]; +} + ++(CommitPacket*) commitPacketWithHashChainH2:(NSData*)h2 + andZid:(Zid*)zid + andHashSpecId:(NSData*)hashSpecId + andCipherSpecId:(NSData*)cipherSpecId + andAuthSpecId:(NSData*)authSpecId + andAgreeSpecId:(NSData*)agreeSpecId + andSasSpecId:(NSData*)sasSpecId + andDhPart2HelloCommitment:(NSData*)dhPart2HelloCommitment + andHmacKey:(NSData*)hmacKey { + + require(h2 != nil); + require(zid != nil); + require(hashSpecId != nil); + require(cipherSpecId != nil); + require(authSpecId != nil); + require(agreeSpecId != nil); + require(sasSpecId != nil); + require(dhPart2HelloCommitment != nil); + require(hmacKey != nil); + + require([h2 length] == HASH_CHAIN_ITEM_LENGTH); + require([hashSpecId length] == HASH_SPEC_LENGTH); + require([cipherSpecId length] == CIPHER_SPEC_LENGTH); + require([authSpecId length] == AUTH_SPEC_LENGTH); + require([agreeSpecId length] == AGREE_SPEC_LENGTH); + require([sasSpecId length] == SAS_SPEC_LENGTH); + + CommitPacket* p = [CommitPacket new]; + + p->h2 = h2; + p->zid = zid; + p->hashSpecId = hashSpecId; + p->cipherSpecId = cipherSpecId; + p->authSpecId = authSpecId; + p->agreementSpecId = agreeSpecId; + p->sasSpecId = sasSpecId; + p->dhPart2HelloCommitment = dhPart2HelloCommitment; + + p->embedding = [p embedInHandshakePacketAuthenticatedWith:hmacKey]; + + return p; +} +-(HandshakePacket*) embedInHandshakePacketAuthenticatedWith:(NSData*)hmacKey { + + require(hmacKey != nil); + requireState([h2 length] == HASH_CHAIN_ITEM_LENGTH); + requireState([hashSpecId length] == HASH_SPEC_LENGTH); + requireState([cipherSpecId length] == CIPHER_SPEC_LENGTH); + requireState([authSpecId length] == AUTH_SPEC_LENGTH); + requireState([agreementSpecId length] == AGREE_SPEC_LENGTH); + requireState([sasSpecId length] == SAS_SPEC_LENGTH); + + NSData* payload = [@[ + h2, + [zid getData], + hashSpecId, + cipherSpecId, + authSpecId, + agreementSpecId, + sasSpecId, + dhPart2HelloCommitment + ] concatDatas]; + + return [[HandshakePacket handshakePacketWithTypeId:HANDSHAKE_TYPE_COMMIT andPayload:payload] withHmacAppended:hmacKey]; +} +-(void) verifyCommitmentAgainstHello:(HelloPacket*)hello andDhPart2:(DhPacket*)dhPart2 { + require(hello != nil); + require(dhPart2 != nil); + + NSData* expected = [[@[ + [[dhPart2 embeddedIntoHandshakePacket] dataUsedForAuthentication], + [[hello embeddedIntoHandshakePacket] dataUsedForAuthentication]] + concatDatas] hashWithSha256]; + checkOperation([dhPart2HelloCommitment isEqualToData_TimingSafe:expected]); +} +-(void) verifyMacWithHashChainH1:(NSData*)hashChainH1 { + checkOperation([[hashChainH1 hashWithSha256] isEqualToData_TimingSafe:h2]); + [embedding withHmacVerifiedAndRemoved:hashChainH1]; +} + ++(NSData*) getH2FromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(0, HASH_CHAIN_ITEM_LENGTH)]; +} ++(Zid*) getZidFromPayload:(NSData*)payload { + return [Zid zidWithData:[payload subdataWithRange:NSMakeRange(HASH_CHAIN_ITEM_LENGTH, ZID_LENGTH)]]; +} ++(NSData*) getHashSpecIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(HASH_SPEC_OFFSET, HASH_SPEC_LENGTH)]; +} ++(NSData*) getCipherSpecIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(CIPHER_SPEC_OFFSET, CIPHER_SPEC_LENGTH)]; +} ++(NSData*) getAuthSpecIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(AUTH_SPEC_OFFSET, AUTH_SPEC_LENGTH)]; +} ++(NSData*) getAgreeSpecIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(AGREE_SPEC_OFFSET, AGREE_SPEC_LENGTH)]; +} ++(NSData*) getSasSpecIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(SAS_SPEC_OFFSET, SAS_SPEC_LENGTH)]; +} ++(NSData*) getCommitmentFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(COMMIT_OFFSET, COMMIT_LENGTH)]; +} ++(CommitPacket*) commitPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket { + require(handshakePacket != nil); + checkOperation([[handshakePacket typeId] isEqualToData:HANDSHAKE_TYPE_COMMIT]); + NSData* payload = [handshakePacket payload]; + checkOperation([payload length] == COMMIT_OFFSET + COMMIT_LENGTH + HANDSHAKE_TRUNCATED_HMAC_LENGTH); + + CommitPacket* p = [CommitPacket new]; + + p->h2 = [self getH2FromPayload:payload]; + p->zid = [self getZidFromPayload:payload]; + p->hashSpecId = [self getHashSpecIdFromPayload:payload]; + p->cipherSpecId = [self getCipherSpecIdFromPayload:payload]; + p->authSpecId = [self getAuthSpecIdFromPayload:payload]; + p->agreementSpecId = [self getAgreeSpecIdFromPayload:payload]; + p->sasSpecId = [self getSasSpecIdFromPayload:payload]; + p->dhPart2HelloCommitment = [self getCommitmentFromPayload:payload]; + p->embedding = handshakePacket; + + return p; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.h b/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.h new file mode 100644 index 000000000..18d8af343 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.h @@ -0,0 +1,28 @@ +#import "HandshakePacket.h" +#import "HashChain.h" +#import "ZID.h" + +/** + * + * The Conf2Ack packet sent by the responder to acknowledge that the handshake protocol has completed and stop retransmission of Confirm2. + * Receiving a properly encrypted/authenticated RTP packet with audio data also implies acknowledgement that the protocol has completed. + * + * (extension header of RTP packet containing Conf2Ack handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length=3 words | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="Conf2ACK" (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + +@interface ConfirmAckPacket : NSObject{ +@private HandshakePacket* embedding; +} + ++(ConfirmAckPacket*)confirmAckPacket; ++(ConfirmAckPacket*)confirmAckPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket; +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.m b/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.m new file mode 100644 index 000000000..1514bd87c --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/ConfirmAckPacket.m @@ -0,0 +1,24 @@ +#import "ConfirmAckPacket.h" + +@implementation ConfirmAckPacket + ++(ConfirmAckPacket*)confirmAckPacket { + ConfirmAckPacket* h = [ConfirmAckPacket new]; + h->embedding = [HandshakePacket handshakePacketWithTypeId:HANDSHAKE_TYPE_CONFIRM_ACK andPayload:[NSData data]]; + return h; +} + ++(ConfirmAckPacket*)confirmAckPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket { + checkOperation([[handshakePacket typeId] isEqualToData:HANDSHAKE_TYPE_CONFIRM_ACK]); + checkOperation([[handshakePacket payload] length] == 0); + + ConfirmAckPacket* h = [ConfirmAckPacket new]; + h->embedding = handshakePacket; + return h; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.h b/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.h new file mode 100644 index 000000000..e00342ecc --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.h @@ -0,0 +1,78 @@ +#import +#import "HandshakePacket.h" + +/** + * The Confirm1/Confirm2 packets sent by the initiator/responder to confirm that they know the shared secret. + * The confirm_mac field is encrypted/authenticated using keys derived from the shared secret. + * + * (extension header of RTP packet containing Confirm handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length=variable | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="Confirm1" or "Confirm2" (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | confirm_mac (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | CFB Initialization Vector (4 words) | + * | | + * | | + * +===============================================================+ + * | | + * | Hash preimage H0 (8 words) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Unused (15 bits of zeros) | sig len (9 bits)|0 0 0 0|E|V|A|D| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | cache expiration interval (1 word) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | optional signature type block (1 word if present) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | optional signature block (variable length) | + * | . . . | + * | | + * | | + * +===============================================================+ + * +**/ + +@interface ConfirmPacket : NSObject { +@private HandshakePacket* embedding; +} +@property (readonly,atomic) NSData* confirmMac; +@property (readonly,atomic) NSData* iv; +@property (readonly,atomic) NSData* hashChainH0; +@property (readonly,atomic) uint32_t unusedAndSignatureLengthAndFlags; // Unused (15 bits of zeros) | sig len (9 bits)|0 0 0 0|E|V|A|D +@property (readonly,atomic) uint32_t cacheExperationInterval; +@property (readonly,atomic) bool isPart1; + ++(ConfirmPacket*) confirm1PacketWithHashChain:(HashChain*)hashChain + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv; + ++(ConfirmPacket*) confirm2PacketWithHashChain:(HashChain*)hashChain + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv; + ++(ConfirmPacket*) confirmPacketWithHashChainH0:(NSData*)hashChainH0 + andUnusedAndSignatureLengthAndFlags:(uint32_t)unused + andCacheExpirationInterval:(uint32_t)cacheExpirationInterval + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv + andIsPart1:(bool)isPart1; + ++(ConfirmPacket*) confirmPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket + withMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIsPart1:(bool)isPart1; + +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.m b/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.m new file mode 100644 index 000000000..45995a104 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/ConfirmPacket.m @@ -0,0 +1,165 @@ +#import "ConfirmPacket.h" +#import "Util.h" +#import "Conversions.h" + +@implementation ConfirmPacket + +@synthesize confirmMac, hashChainH0, unusedAndSignatureLengthAndFlags, cacheExperationInterval, isPart1, iv; + +#define TRUNCATED_HMAC_LENGTH 8 +#define CONFIRM_IV_LENGTH 16 +#define WORD_LENGTH 4 + ++(ConfirmPacket*) confirm1PacketWithHashChain:(HashChain*)hashChain + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv { + return [ConfirmPacket confirmPacketWithHashChainH0:[hashChain h0] + andUnusedAndSignatureLengthAndFlags:0 + andCacheExpirationInterval:0xFFFFFFFF + andMacKey:macKey + andCipherKey:cipherKey + andIv:iv + andIsPart1:true]; +} + ++(ConfirmPacket*) confirm2PacketWithHashChain:(HashChain*)hashChain + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv { + return [ConfirmPacket confirmPacketWithHashChainH0:[hashChain h0] + andUnusedAndSignatureLengthAndFlags:0 + andCacheExpirationInterval:0xFFFFFFFF + andMacKey:macKey + andCipherKey:cipherKey + andIv:iv + andIsPart1:false]; +} + ++(ConfirmPacket*) confirmPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket + withMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIsPart1:(bool)isPart1 { + require(handshakePacket != nil); + require(macKey != nil); + require(cipherKey != nil); + NSData* expectedConfirmTypeId = isPart1 ? HANDSHAKE_TYPE_CONFIRM_1 : HANDSHAKE_TYPE_CONFIRM_2; + + checkOperation([[handshakePacket typeId] isEqualToData:expectedConfirmTypeId]); + checkOperation([[handshakePacket payload] length] == TRUNCATED_HMAC_LENGTH + CONFIRM_IV_LENGTH + HASH_CHAIN_ITEM_LENGTH + WORD_LENGTH + WORD_LENGTH); + NSData* decryptedData = [self getDecryptedDataFromPayload:[handshakePacket payload] withMacKey:macKey andCipherKey:cipherKey]; + + return [ConfirmPacket confirmPacketParsedFrom:handshakePacket + withHashChainH0:[self getHashChainH0FromDecryptedData:decryptedData] + andUnusedAndSignatureLengthAndFlags:[self getUnusedAndSignatureLengthAndFlagsFromDecryptedData:decryptedData] + andCacheExpirationInterval:[self getCacheExpirationIntervalFromDecryptedData:decryptedData] + andIncludedHmac:[self getIncludedHmacFromPayload:[handshakePacket payload]] + andIv:[self getIvFromPayload:[handshakePacket payload]] + andIsPart1:isPart1]; +} ++(ConfirmPacket*) confirmPacketWithHashChainH0:(NSData*)hashChainH0 + andUnusedAndSignatureLengthAndFlags:(uint32_t)unused + andCacheExpirationInterval:(uint32_t)cacheExpirationInterval + andMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey + andIv:(NSData*)iv + andIsPart1:(bool)isPart1 { + require(macKey != nil); + require(cipherKey != nil); + require(hashChainH0 != nil); + require(iv != nil); + require([iv length] == CONFIRM_IV_LENGTH); + require([hashChainH0 length] == HASH_CHAIN_ITEM_LENGTH); + + ConfirmPacket* p = [ConfirmPacket new]; + p->hashChainH0 = hashChainH0; + p->unusedAndSignatureLengthAndFlags = unused; + p->cacheExperationInterval = cacheExpirationInterval; + p->iv = iv; + p->isPart1 = isPart1; + p->embedding = [p generateEmbedding:cipherKey andMacKey:macKey]; + return p; +} + +-(HandshakePacket*) generateEmbedding:(NSData*)cipherKey andMacKey:(NSData*)macKey { + require(cipherKey != nil); + require(macKey != nil); + + NSData* sensitiveData = [@[ + hashChainH0, + [NSData dataWithBigEndianBytesOfUInt32:unusedAndSignatureLengthAndFlags], + [NSData dataWithBigEndianBytesOfUInt32:cacheExperationInterval] + ] concatDatas]; + + NSData* encrytedSensitiveData = [sensitiveData encryptWithAesInCipherFeedbackModeWithKey:cipherKey andIv:iv]; + NSData* hmacForSensitiveData = [[encrytedSensitiveData hmacWithSha256WithKey:macKey] take:TRUNCATED_HMAC_LENGTH]; + + NSData* typeId = isPart1 ? HANDSHAKE_TYPE_CONFIRM_1 : HANDSHAKE_TYPE_CONFIRM_2; + + NSData* payload = [@[ + hmacForSensitiveData, + iv, + encrytedSensitiveData + ] concatDatas]; + + return [HandshakePacket handshakePacketWithTypeId:typeId andPayload:payload]; +} + ++(ConfirmPacket*) confirmPacketParsedFrom:(HandshakePacket*)source + withHashChainH0:(NSData*)hashChainH0 + andUnusedAndSignatureLengthAndFlags:(uint32_t)unused + andCacheExpirationInterval:(uint32_t)cacheExpirationInterval + andIncludedHmac:(NSData*)includedHmac + andIv:(NSData*)iv + andIsPart1:(bool)isPart1 { + + ConfirmPacket* p = [ConfirmPacket new]; + + p->hashChainH0 = hashChainH0; + p->unusedAndSignatureLengthAndFlags = unused; + p->cacheExperationInterval = cacheExpirationInterval; + p->iv = iv; + p->isPart1 = isPart1; + p->confirmMac = includedHmac; + p->embedding = source; + return p; + +} + ++(NSData*) getIncludedHmacFromPayload:(NSData*)payload { + return [payload take:TRUNCATED_HMAC_LENGTH]; +} ++(NSData*) getIvFromPayload:(NSData*)payload { + return [payload subdataVolatileWithRange:NSMakeRange(TRUNCATED_HMAC_LENGTH, CONFIRM_IV_LENGTH)]; +} ++(NSData*) getVolatileEncryptedDataFromPayload:(NSData*)payload { + return [payload skipVolatile:TRUNCATED_HMAC_LENGTH + CONFIRM_IV_LENGTH]; +} ++(NSData*) getDecryptedDataFromPayload:(NSData*)payload + withMacKey:(NSData*)macKey + andCipherKey:(NSData*)cipherKey { + + NSData* iv = [self getIvFromPayload:payload]; + NSData* volatileEncryptedData = [self getVolatileEncryptedDataFromPayload:payload]; + NSData* includedHmac = [self getIncludedHmacFromPayload:payload]; + + NSData* expectedHmac = [[volatileEncryptedData hmacWithSha256WithKey:macKey] take:TRUNCATED_HMAC_LENGTH]; + checkOperation([includedHmac isEqualToData_TimingSafe:expectedHmac]); + + return [volatileEncryptedData decryptWithAesInCipherFeedbackModeWithKey:cipherKey andIv:iv]; +} ++(NSData*) getHashChainH0FromDecryptedData:(NSData*)decryptedData { + return [decryptedData take:HASH_CHAIN_ITEM_LENGTH]; +} ++(uint32_t) getUnusedAndSignatureLengthAndFlagsFromDecryptedData:(NSData*)decryptedData { + return [decryptedData bigEndianUInt32At:HASH_CHAIN_ITEM_LENGTH]; +} ++(uint32_t) getCacheExpirationIntervalFromDecryptedData:(NSData*)decryptedData { + return [decryptedData bigEndianUInt32At:HASH_CHAIN_ITEM_LENGTH+WORD_LENGTH]; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/DhPacket.h b/Signal/src/network/rtp/zrtp/packets/DhPacket.h new file mode 100644 index 000000000..4c334619d --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/DhPacket.h @@ -0,0 +1,74 @@ +#import "HandshakePacket.h" +#import "HashChain.h" +#import "DhPacketSharedSecretHashes.h" + +/** + * The DHPart1/DHPart2 packets sent by responder/initiator to perform key agreement. + * The 'pvr' field contains the public key data. + * + * (extension header of RTP packet containing Diffie-Helman Key Exchange handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length=depends on KA Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="DHPart1 " or "DHPart2 " (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Hash image H1 (8 words) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | rs1IDr (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | rs2IDr (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | auxsecretIDr (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pbxsecretIDr (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | pvr (length depends on KA Type) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MAC (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + + + +@interface DhPacket : NSObject { +@private HandshakePacket* embedding; +} + +@property (nonatomic,readonly) bool isPart1; +@property (nonatomic,readonly) DhPacketSharedSecretHashes* sharedSecretHashes; +@property (nonatomic,readonly) NSData* publicKeyData; +@property (nonatomic,readonly) NSData* hashChainH1; + ++(DhPacket*) dh1PacketWithHashChain:(HashChain*)hashChain + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andKeyAgreer:(id)agreer; + ++(DhPacket*) dh2PacketWithHashChain:(HashChain*)hashChain + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andKeyAgreer:(id)agreer; + ++(DhPacket*) dhPacketWithHashChainH0:(NSData*)hashChainH0 + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andPublicKeyData:(NSData*)publicKeyData + andIsPart1:(bool)isPart1; + ++(DhPacket*) dhPacketFromHandshakePacket:(HandshakePacket*)handshakePacket + andIsPart1:(bool)isPart1; + +-(void) verifyMacWithHashChainH0:(NSData*)hashChainH0; +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/DhPacket.m b/Signal/src/network/rtp/zrtp/packets/DhPacket.m new file mode 100644 index 000000000..397cbe33c --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/DhPacket.m @@ -0,0 +1,128 @@ +#import "DhPacket.h" +#import "Util.h" +#import "CryptoTools.h" + +@implementation DhPacket + +#define H1_PAYLOAD_OFFSET 0 +#define RS1_PAYLOAD_OFFSET DH_HASH_CHAIN_H1_LENGTH +#define RS2_PAYLOAD_OFFSET RS1_PAYLOAD_OFFSET + DH_RS1_LENGTH +#define AUX_PAYLOAD_OFFSET RS2_PAYLOAD_OFFSET + DH_RS2_LENGTH +#define PBX_PAYLOAD_OFFSET AUX_PAYLOAD_OFFSET + DH_AUX_LENGTH +#define PUBKEY_PAYLOAD_OFFSET PBX_PAYLOAD_OFFSET + DH_PBX_LENGTH + +#define MIN_DH_PKT_LENGTH (PUBKEY_PAYLOAD_OFFSET + HANDSHAKE_TRUNCATED_HMAC_LENGTH) + +@synthesize hashChainH1, isPart1, publicKeyData, sharedSecretHashes; + ++(DhPacket*) dh1PacketWithHashChain:(HashChain*)hashChain + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andKeyAgreer:(id)agreer { + + require(hashChain != nil); + require(sharedSecretHashes != nil); + require(agreer != nil); + + return [self dhPacketWithHashChainH0:[hashChain h0] + andSharedSecretHashes:sharedSecretHashes + andPublicKeyData:[agreer getPublicKeyData] + andIsPart1:true]; +} + ++(DhPacket*) dh2PacketWithHashChain:(HashChain*)hashChain + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andKeyAgreer:(id)agreer { + + require(hashChain != nil); + require(sharedSecretHashes != nil); + require(agreer != nil); + + return [self dhPacketWithHashChainH0:[hashChain h0] + andSharedSecretHashes:sharedSecretHashes + andPublicKeyData:[agreer getPublicKeyData] + andIsPart1:false]; +} + ++(DhPacket*) dhPacketWithHashChainH0:(NSData*)hashChainH0 + andSharedSecretHashes:(DhPacketSharedSecretHashes*)sharedSecretHashes + andPublicKeyData:(NSData*)publicKeyData + andIsPart1:(bool)isPart1 { + + require(hashChainH0 != nil); + require(sharedSecretHashes != nil); + require(publicKeyData != nil); + + require([hashChainH0 length] == HASH_CHAIN_ITEM_LENGTH); + + DhPacket* p = [DhPacket new]; + p->isPart1 = isPart1; + p->hashChainH1 = [hashChainH0 hashWithSha256]; + p->sharedSecretHashes = sharedSecretHashes; + p->publicKeyData = publicKeyData; + p->embedding = [p embedIntoHandshakePacketAuthenticatedWithMacKey:hashChainH0]; + return p; +} + +-(void) verifyMacWithHashChainH0:(NSData*)hashChainH0 { + checkOperation([[hashChainH0 hashWithSha256] isEqualToData_TimingSafe:hashChainH1]); + [embedding withHmacVerifiedAndRemoved:hashChainH0]; +} + ++(NSData*) getHashChainH1FromPayload:(NSData*)payload { + return [payload take:HASH_CHAIN_ITEM_LENGTH]; +} + ++(DhPacketSharedSecretHashes*) getSharedSecretHashesFromPayload:(NSData*)payload { + + NSData* rs1 = [payload subdataWithRange:NSMakeRange(RS1_PAYLOAD_OFFSET, DH_RS1_LENGTH)]; + NSData* rs2 = [payload subdataWithRange:NSMakeRange(RS2_PAYLOAD_OFFSET, DH_RS2_LENGTH)]; + NSData* aux = [payload subdataWithRange:NSMakeRange(AUX_PAYLOAD_OFFSET, DH_AUX_LENGTH)]; + NSData* pbx = [payload subdataWithRange:NSMakeRange(PBX_PAYLOAD_OFFSET, DH_PBX_LENGTH)]; + + return [DhPacketSharedSecretHashes dhPacketSharedSecretHashesWithRs1:rs1 andRs2:rs2 andAux:aux andPbx:pbx]; +} + ++(NSData*) getPublicKeyDataFromPayload:(NSData*)payload { + return [[payload skip:PUBKEY_PAYLOAD_OFFSET] skipLast:HANDSHAKE_TRUNCATED_HMAC_LENGTH]; +} + ++(DhPacket*) dhPacketFromHandshakePacket:(HandshakePacket*)handshakePacket andIsPart1:(bool)isPart1 { + + require(handshakePacket != nil); + NSData* expectedTypeIdDhPacket = isPart1 ? HANDSHAKE_TYPE_DH_1 : HANDSHAKE_TYPE_DH_2; + NSData* payload = [handshakePacket payload]; + + checkOperation([[handshakePacket typeId] isEqualToData:expectedTypeIdDhPacket]); + checkOperation([payload length] >= MIN_DH_PKT_LENGTH); + + DhPacket* p = [DhPacket new]; + p->hashChainH1 = [self getHashChainH1FromPayload:payload]; + p->sharedSecretHashes = [self getSharedSecretHashesFromPayload:payload]; + p->publicKeyData = [self getPublicKeyDataFromPayload:payload]; + p->embedding = handshakePacket; + return p; +} + +-(HandshakePacket*) embedIntoHandshakePacketAuthenticatedWithMacKey:(NSData*)macKey { + require(macKey != nil); + requireState([hashChainH1 length] == HASH_CHAIN_ITEM_LENGTH); + + NSData* typeId = isPart1 ? HANDSHAKE_TYPE_DH_1 : HANDSHAKE_TYPE_DH_2; + + NSData* payload = [@[ + hashChainH1, + [sharedSecretHashes rs1], + [sharedSecretHashes rs2], + [sharedSecretHashes aux], + [sharedSecretHashes pbx], + publicKeyData + ] concatDatas]; + + return [[HandshakePacket handshakePacketWithTypeId:typeId andPayload:payload] withHmacAppended:macKey]; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.h b/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.h new file mode 100644 index 000000000..db91dd8e4 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.h @@ -0,0 +1,32 @@ +#import + +#define DH_HASH_CHAIN_H0_LENGTH 32 +#define DH_HASH_CHAIN_H1_LENGTH 32 +#define DH_RS1_LENGTH 8 +#define DH_RS2_LENGTH 8 +#define DH_AUX_LENGTH 8 +#define DH_PBX_LENGTH 8 + +/** + * + * DhPacketSharedSecretHashes encapsulates values, related to the caching shared secrets, from DhPacket. + * + * We don't support caching shared secrets, so these values are only ever generated randomly (or parsed from one of the DH packets). + * +**/ + +@interface DhPacketSharedSecretHashes : NSObject + +@property (nonatomic,readonly) NSData* rs1; +@property (nonatomic,readonly) NSData* rs2; +@property (nonatomic,readonly) NSData* aux; +@property (nonatomic,readonly) NSData* pbx; + ++(DhPacketSharedSecretHashes*) dhPacketSharedSecretHashesRandomized; + ++(DhPacketSharedSecretHashes*) dhPacketSharedSecretHashesWithRs1:(NSData*)rs1 + andRs2:(NSData*)rs2 + andAux:(NSData*)aux + andPbx:(NSData*)pbx; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.m b/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.m new file mode 100644 index 000000000..b1e1066e5 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/DhPacketSharedSecretHashes.m @@ -0,0 +1,38 @@ +#import "DhPacketSharedSecretHashes.h" +#import "Constraints.h" +#import "CryptoTools.h" + +@implementation DhPacketSharedSecretHashes + +@synthesize aux, pbx, rs1, rs2; + ++(DhPacketSharedSecretHashes*) dhPacketSharedSecretHashesRandomized { + return [DhPacketSharedSecretHashes dhPacketSharedSecretHashesWithRs1:[CryptoTools generateSecureRandomData:DH_RS1_LENGTH] + andRs2:[CryptoTools generateSecureRandomData:DH_RS2_LENGTH] + andAux:[CryptoTools generateSecureRandomData:DH_AUX_LENGTH] + andPbx:[CryptoTools generateSecureRandomData:DH_PBX_LENGTH]]; +} + ++(DhPacketSharedSecretHashes*) dhPacketSharedSecretHashesWithRs1:(NSData*)rs1 + andRs2:(NSData*)rs2 + andAux:(NSData*)aux + andPbx:(NSData*)pbx { + require(rs1 != nil); + require(rs2 != nil); + require(aux != nil); + require(pbx != nil); + + require([rs1 length] == DH_RS1_LENGTH); + require([rs2 length] == DH_RS2_LENGTH); + require([aux length] == DH_AUX_LENGTH); + require([pbx length] == DH_PBX_LENGTH); + + DhPacketSharedSecretHashes* h = [DhPacketSharedSecretHashes new]; + h->rs1 = rs1; + h->rs2 = rs2; + h->aux = aux; + h->pbx = pbx; + return h; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HandshakePacket.h b/Signal/src/network/rtp/zrtp/packets/HandshakePacket.h new file mode 100644 index 000000000..8c5d8dcca --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HandshakePacket.h @@ -0,0 +1,78 @@ +#import +#import "RtpPacket.h" +#import "KeyAgreementParticipant.h" +#import "KeyAgreementProtocol.h" +#import "Util.h" +#import "Zid.h" + +/** + * + * HandshakePacket represents a zrtp handshake packet, of whatever type. + * These packets are exchanged during the zrtp handshake, to agree on crypto keys and such. + * + * (embedded in RTP packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 0 1|Not Used (set to zero) | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Magic Cookie 'ZRTP' (0x5a525450) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | ZRTP Message (length depends on Message Type) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | CRC (1 word) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + +#define HANDSHAKE_TYPE_HELLO [@"Hello " encodedAsAscii] +#define HANDSHAKE_TYPE_HELLO_ACK [@"HelloAck" encodedAsAscii] +#define HANDSHAKE_TYPE_COMMIT [@"Commit " encodedAsAscii] +#define HANDSHAKE_TYPE_DH_1 [@"DHPart1 " encodedAsAscii] +#define HANDSHAKE_TYPE_DH_2 [@"DHPart2 " encodedAsAscii] +#define HANDSHAKE_TYPE_CONFIRM_1 [@"Confirm1" encodedAsAscii] +#define HANDSHAKE_TYPE_CONFIRM_2 [@"Confirm2" encodedAsAscii] +#define HANDSHAKE_TYPE_CONFIRM_ACK [@"Conf2Ack" encodedAsAscii] + +#define COMMIT_DEFAULT_HASH_SPEC_ID [@"S256" encodedAsAscii] +#define COMMIT_DEFAULT_CIPHER_SPEC_ID [@"AES1" encodedAsAscii] +#define COMMIT_DEFAULT_AUTH_SPEC_ID [@"HS80" encodedAsAscii] +#define COMMIT_DEFAULT_AGREE_SPEC_ID [@"DH3k" encodedAsAscii] +#define COMMIT_DEFAULT_SAS_SPEC_ID [@"B256" encodedAsAscii] + +#define HANDSHAKE_TRUNCATED_HMAC_LENGTH 8 + +@class DhPacket; +@class CommitPacket; +@class ConfirmPacket; +@class HelloPacket; +@class HelloAckPacket; +@class ConfirmAckPacket; + +@interface HandshakePacket : NSObject + +@property (nonatomic,readonly) NSData* typeId; +@property (nonatomic,readonly) NSData* payload; + ++(HandshakePacket*) handshakePacketWithTypeId:(NSData*)typeId andPayload:(NSData*)payload; ++(HandshakePacket*) handshakePacketParsedFromRtpPacket:(RtpPacket*)rtpPacket; +-(HandshakePacket*) withHmacAppended:(NSData*)macKey; +-(HandshakePacket*) withHmacVerifiedAndRemoved:(NSData*)macKey; + +-(NSData*)rtpExtensionPayloadExceptCrc; +-(NSData*)dataUsedForAuthentication; +-(RtpPacket*) embeddedIntoRtpPacketWithSequenceNumber:(uint16_t)sequenceNumber usingInteropOptions:(NSArray*)interopOptions; + +-(HelloPacket*) parsedAsHello; +-(HelloAckPacket*) parsedAsHelloAck; +-(CommitPacket*) parsedAsCommitPacket; +-(DhPacket*) parsedAsDh1; +-(DhPacket*) parsedAsDh2; +-(ConfirmPacket*) parsedAsConfirm1AuthenticatedWithMacKey:(NSData*)macKey andCipherKey:(NSData*)cipherKey; +-(ConfirmPacket*) parsedAsConfirm2AuthenticatedWithMacKey:(NSData*)macKey andCipherKey:(NSData*)cipherKey; +-(ConfirmAckPacket*) parsedAsConfAck; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HandshakePacket.m b/Signal/src/network/rtp/zrtp/packets/HandshakePacket.m new file mode 100644 index 000000000..bfa488841 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HandshakePacket.m @@ -0,0 +1,187 @@ +#import "HandshakePacket.h" +#import "Constraints.h" +#import "Conversions.h" +#import "Util.h" +#import "CryptoTools.h" +#import "Crc32.h" +#import "DhPacket.h" +#import "ConfirmPacket.h" +#import "CommitPacket.h" +#import "HelloPacket.h" +#import "ConfirmAckPacket.h" +#import "HelloAckPacket.h" + +#define HANDSHAKE_PACKET_EXTENSION_IDENTIFIER [[@"PZ" encodedAsAscii] bigEndianUInt16At:0] +#define HANDSHAKE_PACKET_TIMESTAMP_COOKIE [[@"ZRTP" encodedAsAscii] bigEndianUInt32At:0] + +#define HANDSHAKE_TYPE_ID_LENGTH 8 +#define HANDSHAKE_CRC_LENGTH 4 + +#define HEADER_FOOTER_LENGTH_WITHOUT_HMAC (HANDSHAKE_TYPE_ID_LENGTH + HANDSHAKE_CRC_LENGTH) +#define HEADER_FOOTER_LENGTH_WITH_HMAC (HANDSHAKE_TYPE_ID_LENGTH + HANDSHAKE_CRC_LENGTH + HANDSHAKE_TRUNCATED_HMAC_LENGTH) + +@implementation HandshakePacket + +@synthesize payload, typeId; + ++(HandshakePacket*) handshakePacketWithTypeId:(NSData*)typeId andPayload:(NSData*)payload { + require(typeId != nil); + require(payload != nil); + require([typeId length] == HANDSHAKE_TYPE_ID_LENGTH); + + HandshakePacket* p = [HandshakePacket new]; + p->typeId = typeId; + p->payload = payload; + return p; +} + ++(NSData*) getHandshakeTypeIdFromRtp:(RtpPacket*)rtpPacket { + return [[rtpPacket extensionHeaderData] take:HANDSHAKE_TYPE_ID_LENGTH]; +} ++(NSData*) getHandshakePayloadFromRtp:(RtpPacket*)rtpPacket { + return [[[rtpPacket extensionHeaderData] skip:HANDSHAKE_TYPE_ID_LENGTH] skipLast:HANDSHAKE_CRC_LENGTH]; +} ++(uint32_t) getIncludedHandshakeCrcFromRtp:(RtpPacket*)rtpPacket { + return [[[rtpPacket extensionHeaderData] takeLast:HANDSHAKE_CRC_LENGTH] bigEndianUInt32At:0]; +} ++(HandshakePacket*) handshakePacketParsedFromRtpPacket:(RtpPacket*)rtpPacket { + require(rtpPacket != nil); + checkOperation([rtpPacket timeStamp] == HANDSHAKE_PACKET_TIMESTAMP_COOKIE); + checkOperation([rtpPacket version] == 0); + checkOperation([rtpPacket hasExtensionHeader]); + checkOperation([rtpPacket extensionHeaderIdentifier] == HANDSHAKE_PACKET_EXTENSION_IDENTIFIER); + checkOperation([[rtpPacket extensionHeaderData] length] >= HEADER_FOOTER_LENGTH_WITHOUT_HMAC); + + NSData* handshakeTypeId = [self getHandshakeTypeIdFromRtp:rtpPacket]; + NSData* handshakePayload = [self getHandshakePayloadFromRtp:rtpPacket]; + uint32_t includedCrc = [self getIncludedHandshakeCrcFromRtp:rtpPacket]; + + HandshakePacket* p = [HandshakePacket new]; + p->typeId = handshakeTypeId; + p->payload = handshakePayload; + + uint32_t expectedCrc = [[[rtpPacket rawPacketDataUsingInteropOptions:nil] skipLastVolatile:4] crc32]; + checkOperation(includedCrc == expectedCrc); + + return p; +} + +-(HandshakePacket*) withHmacAppended:(NSData*)macKey { + require(macKey != nil); + NSData* digest = [[[self rtpExtensionPayloadUsedForHmacBeforeHmacAppended] hmacWithSha256WithKey:macKey] take:HANDSHAKE_TRUNCATED_HMAC_LENGTH]; + NSData* authenticatedPayload = [@[payload, digest] concatDatas]; + return [HandshakePacket handshakePacketWithTypeId:typeId andPayload:authenticatedPayload]; +} +-(HandshakePacket*) withHmacVerifiedAndRemoved:(NSData*)macKey { + require(macKey != nil); + + NSData* authenticatedData = [self rtpExtensionHeaderAndPayloadExceptCrc]; + + NSData* expectedHmac = [[[authenticatedData skipLastVolatile:HANDSHAKE_TRUNCATED_HMAC_LENGTH] hmacWithSha256WithKey:macKey] take:HANDSHAKE_TRUNCATED_HMAC_LENGTH]; + NSData* includedHmac = [authenticatedData takeLastVolatile:HANDSHAKE_TRUNCATED_HMAC_LENGTH]; + + checkOperation([expectedHmac isEqualToData_TimingSafe:includedHmac]); + + return [HandshakePacket handshakePacketWithTypeId:typeId andPayload:[payload skipLast:HANDSHAKE_TRUNCATED_HMAC_LENGTH]]; +} + +-(NSData*)dataUsedForAuthentication { + return [self rtpExtensionHeaderAndPayloadExceptCrc]; +} + +-(NSData*) rtpExtensionHeaderAndPayloadExceptCrc { + // The data corresponding to the rtp extension header is used when hmac-ing + return [@[ + [NSData dataWithBigEndianBytesOfUInt16:HANDSHAKE_PACKET_EXTENSION_IDENTIFIER], + [NSData dataWithBigEndianBytesOfUInt16:(uint16_t)([payload length] + HEADER_FOOTER_LENGTH_WITHOUT_HMAC)], + typeId, + payload + ] concatDatas]; +} + +-(NSData*) rtpExtensionPayloadExceptCrc { + return [@[ + typeId, + payload + ] concatDatas]; +} + +-(NSData*) rtpExtensionPayloadUsedForHmacBeforeHmacAppended { + // When we hmac the rtp extension header, the hmac length must be counted even though it's not appended yet + return [@[ + [NSData dataWithBigEndianBytesOfUInt16:HANDSHAKE_PACKET_EXTENSION_IDENTIFIER], + [NSData dataWithBigEndianBytesOfUInt16:(uint16_t)([payload length] + HEADER_FOOTER_LENGTH_WITH_HMAC)], + typeId, + payload + ] concatDatas]; +} + +-(RtpPacket*) embeddedIntoRtpPacketWithSequenceNumber:(uint16_t)sequenceNumber usingInteropOptions:(NSArray*)interopOptions { + requireState([typeId length] == HANDSHAKE_TYPE_ID_LENGTH); + + NSData* payloadExceptCrc = [self rtpExtensionPayloadExceptCrc]; + NSData* extensionDataWithZeroCrc = [(@[payloadExceptCrc, [NSData dataWithBigEndianBytesOfUInt32:0]]) concatDatas]; + RtpPacket* packetWithZeroCrc = [RtpPacket rtpPacketWithVersion:0 // invalid version 0 indicates a zrtp handshake packet + andPadding:false + andContributingSourceIdentifiers:[NSArray array] + andSynchronizationSourceIdentifier:0 + andExtensionIdentifier:HANDSHAKE_PACKET_EXTENSION_IDENTIFIER + andExtensionData:extensionDataWithZeroCrc + andMarkerBit:false + andPayloadtype:0 + andSequenceNumber:sequenceNumber + andTimeStamp:HANDSHAKE_PACKET_TIMESTAMP_COOKIE + andPayload:[NSData data]]; + + uint32_t crc = [[[packetWithZeroCrc rawPacketDataUsingInteropOptions:interopOptions] skipLastVolatile:4] crc32]; + NSData* extensionData = [(@[payloadExceptCrc, [NSData dataWithBigEndianBytesOfUInt32:crc]]) concatDatas]; + + return [RtpPacket rtpPacketWithVersion:0 // invalid version 0 indicates a zrtp handshake packet + andPadding:false + andContributingSourceIdentifiers:[NSArray array] + andSynchronizationSourceIdentifier:0 + andExtensionIdentifier:HANDSHAKE_PACKET_EXTENSION_IDENTIFIER + andExtensionData:extensionData + andMarkerBit:false + andPayloadtype:0 + andSequenceNumber:sequenceNumber + andTimeStamp:HANDSHAKE_PACKET_TIMESTAMP_COOKIE + andPayload:[NSData data]]; +} + +-(HelloPacket*) parsedAsHello { + return [HelloPacket helloPacketParsedFromHandshakePacket:self]; +} +-(HelloAckPacket*) parsedAsHelloAck { + return [HelloAckPacket helloAckPacketParsedFromHandshakePacket:self]; +} +-(CommitPacket*) parsedAsCommitPacket { + return [CommitPacket commitPacketParsedFromHandshakePacket:self]; +} +-(DhPacket*) parsedAsDh1 { + return [DhPacket dhPacketFromHandshakePacket:self andIsPart1:true]; +} +-(DhPacket*) parsedAsDh2 { + return [DhPacket dhPacketFromHandshakePacket:self andIsPart1:false]; +} +-(ConfirmPacket*) parsedAsConfirm1AuthenticatedWithMacKey:(NSData*)macKey andCipherKey:(NSData*)cipherKey { + return [ConfirmPacket confirmPacketParsedFromHandshakePacket:self + withMacKey:macKey + andCipherKey:cipherKey + andIsPart1:true]; +} +-(ConfirmPacket*) parsedAsConfirm2AuthenticatedWithMacKey:(NSData*)macKey andCipherKey:(NSData*)cipherKey { + return [ConfirmPacket confirmPacketParsedFromHandshakePacket:self + withMacKey:macKey + andCipherKey:cipherKey + andIsPart1:false]; +} +-(ConfirmAckPacket*) parsedAsConfAck { + return [ConfirmAckPacket confirmAckPacketParsedFromHandshakePacket:self]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"Handshake: %@", [[self typeId] decodedAsAsciiReplacingErrorsWithDots]]; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.h b/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.h new file mode 100644 index 000000000..b6966684d --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.h @@ -0,0 +1,27 @@ +#import "HandshakePacket.h" +#import "HashChain.h" +#import "ZID.h" + +/** + * + * The HelloAck packet sent by the responder to stop retransmission of the Hello packet. + * + * (extension header of RTP packet containing HelloAck handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length=3 words | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="HelloACK" (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + +@interface HelloAckPacket : NSObject{ +@private HandshakePacket* embedding; +} + ++(HelloAckPacket*)helloAckPacket; ++(HelloAckPacket*)helloAckPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket; +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.m b/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.m new file mode 100644 index 000000000..1555cc61c --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HelloAckPacket.m @@ -0,0 +1,24 @@ +#import "HelloAckPacket.h" + +@implementation HelloAckPacket + ++(HelloAckPacket*)helloAckPacket { + HelloAckPacket* h = [HelloAckPacket new]; + h->embedding = [HandshakePacket handshakePacketWithTypeId:HANDSHAKE_TYPE_HELLO_ACK andPayload:[NSData data]]; + return h; +} + ++(HelloAckPacket*)helloAckPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket { + checkOperation([[handshakePacket typeId] isEqualToData:HANDSHAKE_TYPE_HELLO_ACK]); + checkOperation([[handshakePacket payload] length] == 0); + + HelloAckPacket* h = [HelloAckPacket new]; + h->embedding = handshakePacket; + return h; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HelloPacket.h b/Signal/src/network/rtp/zrtp/packets/HelloPacket.h new file mode 100644 index 000000000..2a3c64648 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HelloPacket.h @@ -0,0 +1,90 @@ +#import "HandshakePacket.h" +#import "HashChain.h" +#import "ZID.h" + +/** + * + * The Hello packet sent by the initiator/responder to indicate what they are and which protocols they support. + * + * (extension header of RTP packet containing Hello handshake packet) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0| length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Type Block="Hello " (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | version="1.10" (1 word) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Client Identifier (4 words) | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Hash image H3 (8 words) | + * | . . . | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | ZID (3 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0|S|M|P| unused (zeros)| hc | cc | ac | kc | sc | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | hash algorithms (0 to 7 values) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | cipher algorithms (0 to 7 values) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | auth tag types (0 to 7 values) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Key Agreement Types (0 to 7 values) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SAS Types (0 to 7 values) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MAC (2 words) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * +**/ + +@interface HelloPacket : NSObject { +@private HandshakePacket* embedding; +} +@property (nonatomic,readonly) NSData* versionId; +@property (nonatomic,readonly) NSData* clientId; +@property (nonatomic,readonly) NSData* hashChainH3; +@property (nonatomic,readonly) Zid* zid; +@property (nonatomic,readonly) uint8_t flags0SMP; +@property (nonatomic,readonly) uint8_t flagsUnusedLow4; +@property (nonatomic,readonly) uint8_t flagsUnusedHigh4; +@property (nonatomic,readonly) NSArray* hashIds; +@property (nonatomic,readonly) NSArray* cipherIds; +@property (nonatomic,readonly) NSArray* authIds; +@property (nonatomic,readonly) NSArray* agreeIds; +@property (nonatomic,readonly) NSArray* sasIds; + ++(HelloPacket*) helloPacketWithDefaultsAndHashChain:(HashChain*)hashChain + andZid:(Zid*)zid + andKeyAgreementProtocols:(NSArray*)keyAgreementProtocols; + ++(HelloPacket*) helloPacketWithVersion:(NSData*)versionId + andClientId:(NSData*)clientId + andHashChainH3:(NSData*)hashChainH3 + andZid:(Zid*)zid + andFlags0SMP:(uint8_t)flags0SMP + andFlagsUnusedLow4:(uint8_t)flagsUnusedLow4 + andFlagsUnusedHigh4:(uint8_t)flagsUnusedHigh4 + andHashSpecIds:(NSArray*)hashIds + andCipherSpecIds:(NSArray*)cipherIds + andAuthSpecIds:(NSArray*)authIds + andAgreeSpecIds:(NSArray*)agreeIds + andSasSpecIds:(NSArray*)sasIds + authenticatedWithHmacKey:(NSData*)hmacKey; + ++(HelloPacket*) helloPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket; + +-(void) verifyMacWithHashChainH2:(NSData*)hashChainH2; +-(NSArray*) agreeIdsIncludingImplied; +-(HandshakePacket*) embeddedIntoHandshakePacket; + +@end diff --git a/Signal/src/network/rtp/zrtp/packets/HelloPacket.m b/Signal/src/network/rtp/zrtp/packets/HelloPacket.m new file mode 100644 index 000000000..cf7f04573 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/packets/HelloPacket.m @@ -0,0 +1,250 @@ +#import "HelloPacket.h" +#import "Util.h" +#import "DH3KKeyAgreementProtocol.h" +#import "FunctionalUtil.h" +#import "Environment.h" + +@implementation HelloPacket + +@synthesize zid, agreeIds, authIds, cipherIds, clientId, flags0SMP, flagsUnusedHigh4, flagsUnusedLow4, hashChainH3, hashIds, sasIds, versionId; + +#define MAX_SPEC_IDS 7 +#define CLIENT_ID_LENGTH 16 +#define VERSION_ID_LENGTH 4 +#define ZID_LENGTH 12 +#define FLAGS_LENGTH 4 +#define SPEC_ID_LENGTH 4 + +#define VERSION_ID_OFFSET 0 +#define CLIENT_ID_OFFSET (VERSION_ID_OFFSET + VERSION_ID_LENGTH) +#define HASH_CHAIN_H3_OFFSET (CLIENT_ID_LENGTH + CLIENT_ID_OFFSET) +#define ZID_OFFSET (HASH_CHAIN_H3_OFFSET + HASH_CHAIN_ITEM_LENGTH) +#define FLAGS_OFFSET (ZID_OFFSET + ZID_LENGTH) +#define SPEC_IDS_OFFSET (FLAGS_OFFSET + FLAGS_LENGTH) + +#define UNUSED_HIGH_AND_0SMP_FLAG_INDEX 0 +#define HASH_ID_AND_UNUSED_LOW_FLAG_INDEX 1 +#define AUTH_ID_AND_CIPHER_ID_FLAG_INDEX 2 +#define SAS_ID_AND_AGREE_ID_FLAG_INDEX 3 + +#define FLAGS_0SMP_MASK 0xF +#define FLAGS_UNUSED_LOW_MASK 0xF +#define FLAGS_UNUSED_HIGH_MASK 0xF + +#define HASH_IDS_INDEX 0 +#define CIPHER_IDS_INDEX 1 +#define AUTH_IDS_INDEX 2 +#define AGREE_IDS_INDEX 3 +#define SAS_IDS_INDEX 4 + ++(NSArray*) getAgreeIdsFromKeyAgreementProtocols:(NSArray*)keyAgreementProtocols { + NSMutableArray* agreeSpecIds = [NSMutableArray array]; + bool hasDH3k = false; + + for (id e in keyAgreementProtocols) { + if ([[e getId] isEqualToData:COMMIT_DEFAULT_AGREE_SPEC_ID]) { + hasDH3k = true; + } else { + [agreeSpecIds addObject:[e getId]]; + } + } + + require(hasDH3k); + return agreeSpecIds; +} ++(HelloPacket*) helloPacketWithDefaultsAndHashChain:(HashChain*)hashChain + andZid:(Zid*)zid + andKeyAgreementProtocols:(NSArray*)keyAgreementProtocols { + + require(hashChain != nil); + require(zid != nil); + require(keyAgreementProtocols != nil); + + return [HelloPacket helloPacketWithVersion:[Environment getCurrent].zrtpVersionId + andClientId:[Environment getCurrent].zrtpClientId + andHashChainH3:[hashChain h3] + andZid:zid + andFlags0SMP:0 + andFlagsUnusedLow4:0 + andFlagsUnusedHigh4:0 + andHashSpecIds:[NSArray array] + andCipherSpecIds:[NSArray array] + andAuthSpecIds:[NSArray array] + andAgreeSpecIds:[self getAgreeIdsFromKeyAgreementProtocols:keyAgreementProtocols] + andSasSpecIds:[NSArray array] + authenticatedWithHmacKey:[hashChain h2]]; +} + ++(HelloPacket*) helloPacketWithVersion:(NSData*)versionId + andClientId:(NSData*)clientId + andHashChainH3:(NSData*)hashChainH3 + andZid:(Zid*)zid + andFlags0SMP:(uint8_t)flags0SMP + andFlagsUnusedLow4:(uint8_t)flagsUnusedLow4 + andFlagsUnusedHigh4:(uint8_t)flagsUnusedHigh4 + andHashSpecIds:(NSArray*)hashIds + andCipherSpecIds:(NSArray*)cipherIds + andAuthSpecIds:(NSArray*)authIds + andAgreeSpecIds:(NSArray*)agreeIds + andSasSpecIds:(NSArray*)sasIds + authenticatedWithHmacKey:(NSData*)hmacKey { + + require(versionId != nil); + require(clientId != nil); + require(hashChainH3 != nil); + require(hashIds != nil); + require(cipherIds != nil); + require(authIds != nil); + require(agreeIds != nil); + require(sasIds != nil); + require((flags0SMP & ~FLAGS_0SMP_MASK) == 0); + require((flagsUnusedLow4 & ~FLAGS_UNUSED_LOW_MASK) == 0); + require((flagsUnusedHigh4 & ~FLAGS_UNUSED_HIGH_MASK) == 0); + + require([versionId length] == VERSION_ID_LENGTH); + require([clientId length] == CLIENT_ID_LENGTH); + require([hashChainH3 length] == HASH_CHAIN_ITEM_LENGTH); + require([hashIds count] <= MAX_SPEC_IDS); + require([cipherIds count] <= MAX_SPEC_IDS); + require([authIds count] <= MAX_SPEC_IDS); + require([agreeIds count] <= MAX_SPEC_IDS); + require([sasIds count] <= MAX_SPEC_IDS); + + HelloPacket* p = [HelloPacket new]; + p->flagsUnusedLow4 = flagsUnusedLow4; + p->flagsUnusedHigh4 = flagsUnusedHigh4; + p->flags0SMP = flags0SMP; + p->agreeIds = agreeIds; + p->authIds = authIds; + p->cipherIds = cipherIds; + p->clientId = clientId; + p->hashChainH3 = hashChainH3; + p->hashIds = hashIds; + p->sasIds = sasIds; + p->versionId = versionId; + p->zid = zid; + + HandshakePacket* unauthenticatedEmbedding = [HandshakePacket handshakePacketWithTypeId:HANDSHAKE_TYPE_HELLO andPayload:[p generatePayload]]; + p->embedding = [unauthenticatedEmbedding withHmacAppended:hmacKey]; + return p; +} +-(NSData*) generateFlags { + NSMutableData* flags = [NSMutableData dataWithLength:FLAGS_LENGTH]; + + [flags setUint8At:UNUSED_HIGH_AND_0SMP_FLAG_INDEX + to:[NumberUtil uint8FromLowUInt4:flagsUnusedHigh4 + andHighUInt4:flags0SMP]]; + + [flags setUint8At:UNUSED_HIGH_AND_0SMP_FLAG_INDEX + to:[NumberUtil uint8FromLowUInt4:(uint8_t)[hashIds count] + andHighUInt4:flagsUnusedLow4]]; + + [flags setUint8At:AUTH_ID_AND_CIPHER_ID_FLAG_INDEX + to:[NumberUtil uint8FromLowUInt4:(uint8_t)[authIds count] + andHighUInt4:(uint8_t)[cipherIds count]]]; + + [flags setUint8At:SAS_ID_AND_AGREE_ID_FLAG_INDEX + to:[NumberUtil uint8FromLowUInt4:(uint8_t)[sasIds count] + andHighUInt4:(uint8_t)[agreeIds count]]]; + + return flags; +} +-(NSData*) generatePayload { + return [@[ + + versionId, + clientId, + hashChainH3, + [zid getData], + [self generateFlags], + [[@[hashIds, cipherIds, authIds, agreeIds, sasIds] concatArrays] concatDatas] + + ] concatDatas]; +} + +-(void) verifyMacWithHashChainH2:(NSData*)hashChainH2 { + checkOperationDescribe([[hashChainH2 hashWithSha256] isEqualToData_TimingSafe:hashChainH3], @"Invalid preimage"); + [embedding withHmacVerifiedAndRemoved:hashChainH2]; +} ++(NSData*) getVersionIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(VERSION_ID_OFFSET, VERSION_ID_LENGTH)]; +} ++(NSData*) getClientIdFromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(CLIENT_ID_OFFSET, CLIENT_ID_LENGTH)]; +} ++(NSData*) getHashChainH3FromPayload:(NSData*)payload { + return [payload subdataWithRange:NSMakeRange(HASH_CHAIN_H3_OFFSET, HASH_CHAIN_ITEM_LENGTH)]; +} ++(Zid*) getZidFromPayload:(NSData*)payload { + return [Zid zidWithData:[payload subdataWithRange:NSMakeRange(ZID_OFFSET, ZID_LENGTH)]]; +} ++(NSArray*) getSpecIdsFromPayload:(NSData*)payload counts:(NSArray*)counts { + checkOperation([payload length] >= SPEC_IDS_OFFSET + SPEC_ID_LENGTH*[counts sumNSUInteger]); + + NSMutableArray* result = [NSMutableArray array]; + NSUInteger offset = SPEC_IDS_OFFSET; + for (NSNumber* count in counts) { + NSMutableArray* subResult = [NSMutableArray array]; + for (NSUInteger i = 0; i < [count unsignedIntegerValue]; i++) { + [subResult addObject:[payload subdataWithRange:NSMakeRange(offset, SPEC_ID_LENGTH)]]; + offset += SPEC_ID_LENGTH; + } + [result addObject:subResult]; + } + return result; +} +-(void) setFlagsAndSpecIdsFromPayload:(NSData*)payload { + NSData* flags = [payload subdataWithRange:NSMakeRange(FLAGS_OFFSET, FLAGS_LENGTH)]; + + flags0SMP = [flags highUint4AtByteOffset:UNUSED_HIGH_AND_0SMP_FLAG_INDEX]; + flagsUnusedHigh4 = [flags lowUint4AtByteOffset:UNUSED_HIGH_AND_0SMP_FLAG_INDEX]; + flagsUnusedLow4 = [flags highUint4AtByteOffset:HASH_ID_AND_UNUSED_LOW_FLAG_INDEX]; + + uint8_t hashIdsCount = [flags lowUint4AtByteOffset:HASH_ID_AND_UNUSED_LOW_FLAG_INDEX]; + uint8_t cipherIdsCount = [flags highUint4AtByteOffset:AUTH_ID_AND_CIPHER_ID_FLAG_INDEX]; + uint8_t authIdsCount = [flags lowUint4AtByteOffset:AUTH_ID_AND_CIPHER_ID_FLAG_INDEX]; + uint8_t agreeIdsCount = [flags highUint4AtByteOffset:SAS_ID_AND_AGREE_ID_FLAG_INDEX]; + uint8_t sasIdsCount = [flags lowUint4AtByteOffset:SAS_ID_AND_AGREE_ID_FLAG_INDEX]; + NSArray* counts = @[[NSNumber numberWithUnsignedInteger:hashIdsCount], + [NSNumber numberWithUnsignedInteger:cipherIdsCount], + [NSNumber numberWithUnsignedInteger:authIdsCount], + [NSNumber numberWithUnsignedInteger:agreeIdsCount], + [NSNumber numberWithUnsignedInteger:sasIdsCount]]; + + NSArray* specIds = [HelloPacket getSpecIdsFromPayload:payload counts:counts]; + hashIds = [specIds objectAtIndex:HASH_IDS_INDEX]; + cipherIds = [specIds objectAtIndex:CIPHER_IDS_INDEX]; + authIds = [specIds objectAtIndex:AUTH_IDS_INDEX]; + agreeIds = [specIds objectAtIndex:AGREE_IDS_INDEX]; + sasIds = [specIds objectAtIndex:SAS_IDS_INDEX]; +} ++(HelloPacket*) helloPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket { + require(handshakePacket != nil); + checkOperationDescribe([[handshakePacket typeId] isEqualToData:HANDSHAKE_TYPE_HELLO], @"Not a hello packet"); + + NSData* payload = [handshakePacket payload]; + checkOperation([payload length] >= SPEC_IDS_OFFSET); + + HelloPacket* p = [HelloPacket new]; + + p->versionId = [self getVersionIdFromPayload:payload]; + p->clientId = [self getClientIdFromPayload:payload]; + p->hashChainH3 = [self getHashChainH3FromPayload:payload]; + p->zid = [self getZidFromPayload:payload]; + [p setFlagsAndSpecIdsFromPayload:payload]; + + p->embedding = handshakePacket; + + return p; +} + +-(HandshakePacket*) embeddedIntoHandshakePacket { + return embedding; +} +-(NSArray*) agreeIdsIncludingImplied { + NSMutableArray* a = [agreeIds mutableCopy]; + [a addObject:COMMIT_DEFAULT_AGREE_SPEC_ID]; + return a; +} + +@end diff --git a/Signal/src/network/rtp/zrtp/protocols/KeyAgreementParticipant.h b/Signal/src/network/rtp/zrtp/protocols/KeyAgreementParticipant.h new file mode 100644 index 000000000..ab67073a1 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/protocols/KeyAgreementParticipant.h @@ -0,0 +1,18 @@ +#import +#import "HashChain.h" +#import "HandshakePacket.h" + +@protocol KeyAgreementProtocol; + +/** + * + * KeyAgreementParticipant is used to represent a participant, with their own private/public key, in a key agreement protocol. + * Two compatible participants generate shared key when given each others' public key data. + * +**/ + +@protocol KeyAgreementParticipant +-(id) getProtocol; +-(NSData*) getPublicKeyData; +-(NSData*) calculateKeyAgreementAgainstRemotePublicKey:(NSData*)remotePublicKey; +@end diff --git a/Signal/src/network/rtp/zrtp/protocols/KeyAgreementProtocol.h b/Signal/src/network/rtp/zrtp/protocols/KeyAgreementProtocol.h new file mode 100644 index 000000000..242779137 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/protocols/KeyAgreementProtocol.h @@ -0,0 +1,17 @@ +#import +#import "HashChain.h" +#import "HandshakePacket.h" + +@protocol KeyAgreementParticipant; + +/** + * + * KeyAgreementProtocol is used to generate compatible participants for a key agreement protocol. + * Two compatible participants generate shared key when given each others' public key data. + * +**/ + +@protocol KeyAgreementProtocol +-(id) generateParticipantWithNewKeys; +-(NSData*) getId; +@end diff --git a/Signal/src/network/rtp/zrtp/protocols/ZrtpRole.h b/Signal/src/network/rtp/zrtp/protocols/ZrtpRole.h new file mode 100644 index 000000000..eddc59156 --- /dev/null +++ b/Signal/src/network/rtp/zrtp/protocols/ZrtpRole.h @@ -0,0 +1,46 @@ +#import +#import "Environment.h" +#import "HandshakePacket.h" +#import "MasterSecret.h" +#import "SrtpSocket.h" + +typedef enum { + EXPECTING_HELLO, + EXPECTING_COMMIT, + EXPECTING_HELLO_ACK, + EXPECTING_DH, + EXPECTING_CONFIRM, + EXPECTING_CONFIRM_ACK, + EXPECTING_NOTHING +} PacketExpectation; + +/** + * + * A ZrtpRole represents the responsibilities of a participant in a Zrtp handshake (either the initiator or the responder). + * The role determines how packets are handled, which packets to send, and exposes the eventual results of the handshake. + * +**/ + +@protocol ZrtpRole + +// The packet to be sent when the handshake starts. Nil indicates 'Do not send an initial packet.'. +-(HandshakePacket*) initialPacket; + +// Called when a packet arrives from the remote end of the handshake. +// Returns the packet to reply with. A nil result indicates 'Ignore Packet. Continue as before.'. +-(HandshakePacket*) handlePacket:(HandshakePacket*)packet; + +// Determines if the handshake process has completed successfully. +-(bool) hasHandshakeFinishedSuccessfully; + +// Determines if a 'bad' packet is actually valid authenticated srtp audio data, being received due to Conf2Ack being lost. +-(bool) isAuthenticatedAudioDataImplyingConf2Ack:(id)packet; + +// Retrieves an srtp socket that has been keyed by the handshake process. +// Should only be called when 'hasHandshakeFinishedSuccessfully' is true. +-(SrtpSocket*) useKeysToSecureRtpSocket:(RtpSocket*)rtpSocket; + +// Retrieves the computed master secret. +// Should only be called when 'hasHandshakeFinishedSuccessfully' is true. +-(MasterSecret*) getMasterSecret; +@end diff --git a/Signal/src/network/tcp/LowLatencyCandidate.h b/Signal/src/network/tcp/LowLatencyCandidate.h new file mode 100644 index 000000000..1daf48e40 --- /dev/null +++ b/Signal/src/network/tcp/LowLatencyCandidate.h @@ -0,0 +1,19 @@ +#import +#import "IpEndPoint.h" +#import "NetworkStream.h" +#import "AsyncUtil.h" + +@interface LowLatencyCandidate : NSObject + +@property (readonly,nonatomic) IpEndPoint* remoteEndPoint; +@property (readonly,nonatomic) NetworkStream* networkStream; + ++(LowLatencyCandidate*) lowLatencyCandidateToRemoteEndPoint:(id)remoteEndPoint; + +-(void) preStart; + +-(CancellableOperationStarter) tcpHandshakeCompleter; + +-(Future*) delayedUntilAuthenticated; + +@end diff --git a/Signal/src/network/tcp/LowLatencyCandidate.m b/Signal/src/network/tcp/LowLatencyCandidate.m new file mode 100644 index 000000000..cccf6c6b5 --- /dev/null +++ b/Signal/src/network/tcp/LowLatencyCandidate.m @@ -0,0 +1,46 @@ +#import "LowLatencyCandidate.h" +#import "Util.h" + +@implementation LowLatencyCandidate + +@synthesize networkStream, remoteEndPoint; + ++(LowLatencyCandidate*) lowLatencyCandidateToRemoteEndPoint:(id)remoteEndPoint { + + require(remoteEndPoint != nil); + + LowLatencyCandidate* r = [LowLatencyCandidate new]; + r->remoteEndPoint = remoteEndPoint; + r->networkStream = [NetworkStream networkStreamToEndPoint:remoteEndPoint]; + return r; +} + +-(void)terminate { + [networkStream terminate]; +} + +-(void) preStart { + [networkStream startProcessingStreamEventsEvenWithoutHandler]; +} + +-(CancellableOperationStarter) tcpHandshakeCompleter { + return ^(id untilCancelledToken) { + return [self completer:untilCancelledToken]; + }; +} + +-(Future*) completer:(id)untilCancelledToken { + Future* tcpHandshakeCompleted = [networkStream asyncTcpHandshakeCompleted]; + + [untilCancelledToken whenCancelledTerminate:self]; + + return [Future delayed:self + untilAfter:tcpHandshakeCompleted]; +} + +-(Future*) delayedUntilAuthenticated { + return [Future delayed:self + untilAfter:[networkStream asyncConnectionCompleted]]; +} + +@end diff --git a/Signal/src/network/tcp/LowLatencyConnector.h b/Signal/src/network/tcp/LowLatencyConnector.h new file mode 100644 index 000000000..be6fcee11 --- /dev/null +++ b/Signal/src/network/tcp/LowLatencyConnector.h @@ -0,0 +1,21 @@ +#import +#import "IpEndPoint.h" +#import "FutureSource.h" +#import "NetworkStream.h" +#import "CancelToken.h" +#import "LowLatencyCandidate.h" + +/** + * + * Responsible for racing connections to all ip addresses associated with a host name simulatenously. + * The first connection to complete its tcp handshake wins. + * + **/ + +@interface LowLatencyConnector : NSObject + +/// Result has type Future(LowLatencyCandidate). ++(Future*) asyncLowLatencyConnectToEndPoint:(id)endPoint + untilCancelled:(id)untilCancelledToken; + +@end diff --git a/Signal/src/network/tcp/LowLatencyConnector.m b/Signal/src/network/tcp/LowLatencyConnector.m new file mode 100644 index 000000000..bd52da2b6 --- /dev/null +++ b/Signal/src/network/tcp/LowLatencyConnector.m @@ -0,0 +1,52 @@ +#import "LowLatencyConnector.h" +#import "Constraints.h" +#import "Util.h" +#import "DnsManager.h" +#import "IpAddress.h" +#import "CancelToken.h" +#import "FunctionalUtil.h" +#import "AsyncUtil.h" +#import "NetworkStream.h" + +@implementation LowLatencyConnector + ++(Future*) asyncLowLatencyConnectToEndPoint:(id)endPoint + untilCancelled:(id)untilCancelledToken { + + require(endPoint != nil); + + Future* futureSpecificEndPoints = [endPoint asyncResolveToSpecificEndPointsUnlessCancelled:untilCancelledToken]; + + return [futureSpecificEndPoints then:^(NSArray* specificEndPoints) { + return [LowLatencyConnector startConnectingToAll:specificEndPoints + untilCancelled:untilCancelledToken]; + }]; +} + ++(Future*) startConnectingToAll:(NSArray*)specificEndPoints + untilCancelled:(id)untilCancelledToken { + + require(specificEndPoints != nil); + + + NSArray* candidates = [specificEndPoints map:^id(id endPoint) { + return [LowLatencyCandidate lowLatencyCandidateToRemoteEndPoint:endPoint]; + }]; + + for (LowLatencyCandidate* candidate in candidates) { + [candidate preStart]; + } + + NSArray* candidateCompleters = [candidates map:^id(LowLatencyCandidate* candidate) { + return [candidate tcpHandshakeCompleter]; + }]; + + Future* futureFastestCandidate = [AsyncUtil raceCancellableOperations:candidateCompleters + untilCancelled:untilCancelledToken]; + + return [futureFastestCandidate then:^(LowLatencyCandidate* fastestCandidate) { + return [fastestCandidate delayedUntilAuthenticated]; + }]; +} + +@end diff --git a/Signal/src/network/tcp/StreamPair.h b/Signal/src/network/tcp/StreamPair.h new file mode 100644 index 000000000..9b13795e7 --- /dev/null +++ b/Signal/src/network/tcp/StreamPair.h @@ -0,0 +1,15 @@ +#import + +/** + * + * Stores an NSInputStream and an NSOutputStream in one object + * +**/ + +@interface StreamPair : NSObject +@property (nonatomic, readonly) NSInputStream* inputStream; +@property (nonatomic, readonly) NSOutputStream* outputStream; + ++(StreamPair*) streamPairWithInput:(NSInputStream*)input andOutput:(NSOutputStream*)output; + +@end diff --git a/Signal/src/network/tcp/StreamPair.m b/Signal/src/network/tcp/StreamPair.m new file mode 100644 index 000000000..c4f28a627 --- /dev/null +++ b/Signal/src/network/tcp/StreamPair.m @@ -0,0 +1,20 @@ +#import "StreamPair.h" +#import "Constraints.h" + +@implementation StreamPair +@synthesize inputStream, outputStream; + ++(StreamPair*) streamPairWithInput:(NSInputStream*)input andOutput:(NSOutputStream*)output { + require(input != nil); + require(output != nil); + + StreamPair* r = [StreamPair new]; + r->inputStream = input; + r->outputStream = output; + + [r->inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType]; + [r->outputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType]; + return r; +} + +@end diff --git a/Signal/src/network/tcp/tls/Certificate.h b/Signal/src/network/tcp/tls/Certificate.h new file mode 100644 index 000000000..01f773d95 --- /dev/null +++ b/Signal/src/network/tcp/tls/Certificate.h @@ -0,0 +1,20 @@ +#import + +/** + * + * Certificate is responsible for loading, exposing, and managing a SecCertificateRef. + * + */ +@interface Certificate : NSObject { +@private SecCertificateRef secCertificateRef; +} + ++(Certificate*) certificateFromTrust:(SecTrustRef)trust + atIndex:(CFIndex)index; + ++(Certificate*) certificateFromResourcePath:(NSString*)resourcePath + ofType:(NSString*)resourceType; + +-(void) setAsAnchorForTrust:(SecTrustRef)trust; + +@end diff --git a/Signal/src/network/tcp/tls/Certificate.m b/Signal/src/network/tcp/tls/Certificate.m new file mode 100644 index 000000000..678173229 --- /dev/null +++ b/Signal/src/network/tcp/tls/Certificate.m @@ -0,0 +1,60 @@ +#import "Certificate.h" +#import "Util.h" + +@implementation Certificate + ++(Certificate*) certificateFromTrust:(SecTrustRef)trust + atIndex:(CFIndex)index { + require(trust != nil); + require(index >= 0); + + SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, index); + checkOperation(cert != nil); + CFRetain(cert); + + Certificate* instance = [Certificate new]; + instance->secCertificateRef = cert; + return instance; +} + ++(Certificate*) certificateFromResourcePath:(NSString*)resourcePath + ofType:(NSString*)resourceType { + require(resourcePath != nil); + require(resourceType != nil); + + NSString *certPath = [[NSBundle mainBundle] pathForResource:resourcePath ofType:resourceType]; + NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath]; + checkOperation(certData != nil); + + SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData); + checkOperation(cert != nil); + + Certificate* instance = [Certificate new]; + instance->secCertificateRef = cert; + return instance; +} + +-(void) dealloc { + CFRelease(secCertificateRef); +} + +-(void) setAsAnchorForTrust:(SecTrustRef)trust { + require(trust != nil); + + CFMutableArrayRef anchorCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); + checkOperation(anchorCerts != NULL); + + CFArrayAppendValue(anchorCerts, secCertificateRef); + OSStatus setAnchorResult = SecTrustSetAnchorCertificates(trust, anchorCerts); + CFRelease(anchorCerts); + + checkOperationDescribe(setAnchorResult == 0, + ([NSString stringWithFormat:@"SecTrustSetAnchorCertificates failed with error code: %ld", + setAnchorResult])); +} + +-(NSString *)description { + return (__bridge_transfer NSString*)SecCertificateCopySubjectSummary(secCertificateRef); +} + +@end diff --git a/Signal/src/network/tcp/tls/NetworkStream.h b/Signal/src/network/tcp/tls/NetworkStream.h new file mode 100644 index 000000000..02683d548 --- /dev/null +++ b/Signal/src/network/tcp/tls/NetworkStream.h @@ -0,0 +1,45 @@ +#import +#import "PacketHandler.h" +#import "CyclicalBuffer.h" +#import "FutureSource.h" +#import "NetworkEndPoint.h" +#import "Terminable.h" + +@class SecureEndPoint; +@class HostNameEndPoint; +@class IpEndPoint; + +/** + * + * The network stream class handles connecting to and communicating with a server over tcp or ssl. + * To make an SSL connection, connect to a SecureEndPoint instead of a raw IpEndPoint or HostNameEndPoint. + * +**/ + +@interface NetworkStream : NSObject { +@private NSMutableData* readBuffer; +@private NSInputStream* inputStream; +@private NSOutputStream* outputStream; +@private PacketHandler* rawDataHandler; +@private bool closedLocally; +@private CyclicalBuffer* writeBuffer; +@private FutureSource* futureConnectedAndWritableSource; +@private FutureSource* futureOpenedSource; +@private id remoteEndPoint; +@private NSRunLoop* runLoop; +@private bool started; +} + ++(NetworkStream*) networkStreamToEndPoint:(id)remoteEndPoint; + +-(Future*) asyncConnectionCompleted; + +-(Future*) asyncTcpHandshakeCompleted; + +-(void) send:(NSData*)data; + +-(void) startWithHandler:(PacketHandler*)handler; + +-(void) startProcessingStreamEventsEvenWithoutHandler; + +@end diff --git a/Signal/src/network/tcp/tls/NetworkStream.m b/Signal/src/network/tcp/tls/NetworkStream.m new file mode 100644 index 000000000..04b13f522 --- /dev/null +++ b/Signal/src/network/tcp/tls/NetworkStream.m @@ -0,0 +1,240 @@ +#import "Environment.h" +#import "NetworkStream.h" +#import "Constraints.h" +#import "Util.h" +#import "SecureEndPoint.h" +#import "HostNameEndPoint.h" +#import "IpEndPoint.h" +#import "IpAddress.h" +#import "ThreadManager.h" + +#define READ_BUFFER_LENGTH 1024 + +@implementation NetworkStream + ++(NetworkStream*) networkStreamToEndPoint:(id)remoteEndPoint { + require(remoteEndPoint != nil); + + // all connections must be secure, unless testing + bool isSecureEndPoint = [remoteEndPoint isKindOfClass:[SecureEndPoint class]]; + bool allowTestNonSecure = [Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS]; + require(allowTestNonSecure || isSecureEndPoint); + + StreamPair* streams = [remoteEndPoint createStreamPair]; + + NetworkStream* s = [NetworkStream new]; + s->readBuffer = [NSMutableData dataWithLength:READ_BUFFER_LENGTH]; + s->inputStream = [streams inputStream]; + s->outputStream = [streams outputStream]; + s->writeBuffer = [CyclicalBuffer new]; + s->remoteEndPoint = remoteEndPoint; + s->futureOpenedSource = [FutureSource new]; + s->futureConnectedAndWritableSource = [FutureSource new]; + s->runLoop = [ThreadManager normalLatencyThreadRunLoop]; + [s->inputStream scheduleInRunLoop:s->runLoop forMode:NSDefaultRunLoopMode]; + [s->outputStream scheduleInRunLoop:s->runLoop forMode:NSDefaultRunLoopMode]; + + [s->futureConnectedAndWritableSource catchDo:^(id error) { + @synchronized(self) { + [s onNetworkFailure:error]; + } + }]; + + + [s->inputStream setDelegate:s]; + [s->outputStream setDelegate:s]; + + return s; +} + +-(Future*) asyncConnectionCompleted { return futureConnectedAndWritableSource; } +-(Future*) asyncTcpHandshakeCompleted { return futureOpenedSource; } + +-(void) terminate { + @synchronized(self) { + if (closedLocally) return; + closedLocally = true; + [futureConnectedAndWritableSource trySetResult:@NO]; // did not connect, no error + [futureOpenedSource trySetResult:@NO]; + [inputStream close]; + [outputStream close]; + } +} +-(void) send:(NSData*)data { + require(data != nil); + requireState(rawDataHandler != nil); + @synchronized(self) { + [writeBuffer enqueueData:data]; + [self tryWriteBufferedData]; + } +} +-(void) tryWriteBufferedData { + if (![futureConnectedAndWritableSource hasSucceeded]) return; + if (![[futureConnectedAndWritableSource forceGetResult] isEqual:@YES]) return; + NSStreamStatus status = [outputStream streamStatus]; + if (status < NSStreamStatusOpen) return; + if (status >= NSStreamStatusAtEnd) { + [rawDataHandler handleError:@"Wrote to ended/closed/errored stream." + relatedInfo:nil + causedTermination:false]; + return; + } + + while ([writeBuffer enqueuedLength] > 0 && [outputStream hasSpaceAvailable]) { + NSData* data = [writeBuffer peekVolatileHeadOfData]; + int d = [outputStream write:[data bytes] maxLength:[data length]]; + + // reached destination buffer capacity? + if (d == 0) break; + + // error? + if (d < 0) { + id error = [outputStream streamError]; + if (error == nil) error = @"Unknown error when writing to stream."; + [rawDataHandler handleError:error relatedInfo:nil causedTermination:false]; + return; + } + + // written, discard + [writeBuffer discard:(NSUInteger)d]; + } +} +-(void) startWithHandler:(PacketHandler*)handler { + require(handler != nil); + requireState(rawDataHandler == nil); + @synchronized(self) { + rawDataHandler = handler; + [self startProcessingStreamEventsEvenWithoutHandler]; + } +} +-(void) startProcessingStreamEventsEvenWithoutHandler { + @synchronized(self) { + if (started) return; + started = true; + + [inputStream open]; + [outputStream open]; + } +} + + +-(void) onNetworkFailure:(id)error { + @synchronized(self) { + [futureOpenedSource trySetFailure:error]; + [futureConnectedAndWritableSource trySetFailure:error]; + [rawDataHandler handleError:error relatedInfo:nil causedTermination:true]; + [self terminate]; + } +} + +-(void) onOpenCompleted { + if (![futureOpenedSource trySetResult:@YES]) return; + + @try { + [remoteEndPoint handleStreamsOpened:[StreamPair streamPairWithInput:inputStream + andOutput:outputStream]]; + } @catch (OperationFailed* ex) { + [self onNetworkFailure:ex]; + } +} + +-(void) onSpaceAvailableToWrite { + [self tryWriteBufferedData]; + + if ([futureConnectedAndWritableSource isCompletedOrWiredToComplete]) return; + + Future* checked = [remoteEndPoint asyncHandleStreamsConnected:[StreamPair streamPairWithInput:inputStream + andOutput:outputStream]]; + [futureConnectedAndWritableSource trySetResult:checked]; + [futureConnectedAndWritableSource thenDo:^(id result) { + @synchronized(self) { + [self onSpaceAvailableToWrite]; + } + }]; + [futureConnectedAndWritableSource catchDo:^(id error) { + @synchronized(self) { + [self onNetworkFailure:error]; + } + }]; +} + +-(void) onErrorOccurred:(id)fallbackError { + id error = [inputStream streamError]; + if (error == nil) error = [outputStream streamError]; + if (error == nil) error = fallbackError; + [self onNetworkFailure:error]; +} + +-(void) onBytesAvailableToRead { + if (rawDataHandler == nil) return; + if (![futureConnectedAndWritableSource hasSucceeded]) return; + if (![[futureConnectedAndWritableSource forceGetResult] isEqual:@YES]) return; + + while ([inputStream hasBytesAvailable]) { + int numRead = [inputStream read:[readBuffer mutableBytes] maxLength:[readBuffer length]]; + + if (numRead < 0) [self onErrorOccurred:@"Read Error"]; + if (numRead <= 0) break; + + NSData* readData = [readBuffer take:(NSUInteger)numRead]; + [rawDataHandler handlePacket:readData]; + } +} + +-(void) onEndOfStream { + [self onBytesAvailableToRead]; + if (!closedLocally) { + [self onErrorOccurred:@"Closed Remotely."]; + } + [self terminate]; +} + +-(void)stream:(NSStream*)aStream handleEvent:(NSStreamEvent)event { + requireState(aStream == inputStream || aStream == outputStream); + bool isInputStream = aStream == inputStream; + + @synchronized(self) { + switch(event) { + case NSStreamEventOpenCompleted: + [self onOpenCompleted]; + break; + + case NSStreamEventHasBytesAvailable: + [self onBytesAvailableToRead]; + break; + + case NSStreamEventHasSpaceAvailable: + [self onSpaceAvailableToWrite]; + break; + + case NSStreamEventErrorOccurred: + [self onErrorOccurred:[NSString stringWithFormat:@"Unknown %@ stream error.", + isInputStream ? @"input" : @"output"]]; + break; + + case NSStreamEventEndEncountered: + [self onEndOfStream]; + break; + + default: + [self onErrorOccurred:[NSString stringWithFormat:@"Unexpected %@ stream event: %d.", + isInputStream ? @"input" : @"output", + event]]; + } + } +} + +-(NSString *)description { + NSString* status = @"Not Started"; + if (started) status = @"Connecting"; + if ([futureOpenedSource hasSucceeded]) status = @"Connecting (TCP Handshake Completed)"; + if ([futureConnectedAndWritableSource hasSucceeded]) status = @"Connected"; + if (closedLocally) status = @"Closed"; + if ([futureConnectedAndWritableSource hasFailed]) status = @"Failed"; + + return [NSString stringWithFormat:@"Status: %@, RemoteEndPoint: %@", + status, + remoteEndPoint]; +} + +@end diff --git a/Signal/src/network/tcp/tls/SecureEndPoint.h b/Signal/src/network/tcp/tls/SecureEndPoint.h new file mode 100644 index 000000000..dfd980310 --- /dev/null +++ b/Signal/src/network/tcp/tls/SecureEndPoint.h @@ -0,0 +1,24 @@ +#import +#import "NetworkEndPoint.h" +#import "HostNameEndPoint.h" +#import "IpEndPoint.h" +#import "Certificate.h" + +/** + * + * SecureEndPoint combines a hostname end point with a verifiable cryptographic identity. + * + * SecureEndPoint is responsible for resolving and authenticating SSL connections. + * + **/ + +@interface SecureEndPoint : NSObject { +@private id optionalMoreSpecificEndPoint; +} +@property (nonatomic, readonly) Certificate* certificate; +@property (nonatomic, readonly) HostNameEndPoint* hostNameEndPoint; + ++(SecureEndPoint*) secureEndPointForHost:(HostNameEndPoint*)host + identifiedByCertificate:(Certificate*)certificate; + +@end diff --git a/Signal/src/network/tcp/tls/SecureEndPoint.m b/Signal/src/network/tcp/tls/SecureEndPoint.m new file mode 100644 index 000000000..18c7186cb --- /dev/null +++ b/Signal/src/network/tcp/tls/SecureEndPoint.m @@ -0,0 +1,115 @@ +#import "SecureEndPoint.h" +#import "Util.h" +#import "DnsManager.h" + +@implementation SecureEndPoint +@synthesize certificate, hostNameEndPoint; + ++(SecureEndPoint*) secureEndPointForHost:(HostNameEndPoint*)host + identifiedByCertificate:(Certificate*)certificate { + + require(host != nil); + require(certificate != nil); + + return [self secureEndPointForHost:host + identifiedByCertificate:certificate + withOptionalMoreSpecificEndPoint:nil]; +} + ++(SecureEndPoint*) secureEndPointForHost:(HostNameEndPoint*)host + identifiedByCertificate:(Certificate*)certificate + withOptionalMoreSpecificEndPoint:(id)optionalMoreSpecificEndPoint { + + require(host != nil); + require(certificate != nil); + + SecureEndPoint* s = [SecureEndPoint new]; + s->hostNameEndPoint = host; + s->certificate = certificate; + s->optionalMoreSpecificEndPoint = optionalMoreSpecificEndPoint; + return s; +} + +-(StreamPair *)createStreamPair { + if (optionalMoreSpecificEndPoint != nil) { + return [optionalMoreSpecificEndPoint createStreamPair]; + } + + return [hostNameEndPoint createStreamPair]; +} + +-(void) handleStreamsOpened:(StreamPair *)streamPair { + [[streamPair inputStream] setProperty:NSStreamSocketSecurityLevelNegotiatedSSL + forKey:NSStreamSocketSecurityLevelKey]; + + [[streamPair outputStream] setProperty:NSStreamSocketSecurityLevelNegotiatedSSL + forKey:NSStreamSocketSecurityLevelKey]; + + NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, + hostNameEndPoint.hostname, kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty((CFReadStreamRef)[streamPair inputStream], + kCFStreamPropertySSLSettings, + (CFTypeRef)settings); + + CFWriteStreamSetProperty((CFWriteStreamRef)[streamPair outputStream], + kCFStreamPropertySSLSettings, + (CFTypeRef)settings); +} + +-(void) authenticateSslStream:(StreamPair*)streamPair { + + SecTrustRef trust = (__bridge SecTrustRef)[[streamPair outputStream] propertyForKey:(__bridge NSString*)kCFStreamPropertySSLPeerTrust]; + + checkOperation(SecTrustGetCertificateCount(trust) > 0); + + [certificate setAsAnchorForTrust:trust]; + SecTrustResultType trustResult = kSecTrustResultInvalid; + OSStatus evalResult = SecTrustEvaluate(trust, &trustResult); + checkSecurityOperation(evalResult == 0, + ([NSString stringWithFormat:@"NetworkStream: SecTrustEvaluate failed with error code: %ld", evalResult])); + checkSecurityOperation(trustResult != kSecTrustResultProceed, + @"Unexpected: User approved certificate somehow? Failing safe."); + checkSecurityOperation(trustResult == kSecTrustResultUnspecified, + ([NSString stringWithFormat:@"NetworkStream: SecTrustEvaluate returned bad result: %u.", trustResult])); +} + +-(Future *)asyncHandleStreamsConnected:(StreamPair *)streamPair { + require(streamPair != nil); + + @try { + [self authenticateSslStream:streamPair]; + return [Future finished:@YES]; + } @catch (OperationFailed* ex) { + return [Future failed:ex]; + } +} + +-(Future*) asyncResolveToSpecificEndPointsUnlessCancelled:(id)unlessCancelledToken { + Future* futureResolvedLocations = [hostNameEndPoint asyncResolveToSpecificEndPointsUnlessCancelled:unlessCancelledToken]; + + return [futureResolvedLocations then:^(NSArray* specificEndPoints) { + return [specificEndPoints map:^(id specificEndPoint) { + return [SecureEndPoint secureEndPointForHost:hostNameEndPoint + identifiedByCertificate:certificate + withOptionalMoreSpecificEndPoint:specificEndPoint]; + }]; + }]; +} + +-(NSString*) description { + if (optionalMoreSpecificEndPoint == nil) { + return [NSString stringWithFormat:@"Host: %@, Certificate: %@)", + hostNameEndPoint, + certificate]; + } + + return [NSString stringWithFormat:@"Host: %@ (resolved to %@), Certificate: %@)", + hostNameEndPoint, + optionalMoreSpecificEndPoint, + certificate]; +} + +@end diff --git a/Signal/src/network/udp/UdpSocket.h b/Signal/src/network/udp/UdpSocket.h new file mode 100644 index 000000000..f75340dc4 --- /dev/null +++ b/Signal/src/network/udp/UdpSocket.h @@ -0,0 +1,43 @@ +#import +#import "IpEndPoint.h" +#import "PacketHandler.h" +#import "Terminable.h" + +/** + * + * Sends and receives raw data packets over UDP. + * +**/ + +@interface UdpSocket : NSObject { +@private CFSocketRef socket; +@public PacketHandler* currentHandler; +@private in_port_t specifiedLocalPort; +@private IpEndPoint* specifiedRemoteEndPoint; +@private bool hasSentData; + +@private in_port_t measuredLocalPort; +@private IpEndPoint* clientConnectedFromRemoteEndPoint; +} + ++(UdpSocket*) udpSocketToFirstSenderOnLocalPort:(in_port_t)localPort; + ++(UdpSocket*) udpSocketFromLocalPort:(in_port_t)localPort + toRemoteEndPoint:(IpEndPoint*)remoteEndPoint; + ++(UdpSocket*) udpSocketTo:(IpEndPoint*)remoteEndPoint; + +-(bool) isLocalPortKnown; + +-(in_port_t) localPort; + +-(bool) isRemoteEndPointKnown; + +-(IpEndPoint*) remoteEndPoint; + +-(void) send:(NSData*)packet; + +-(void) startWithHandler:(PacketHandler*)handler + untilCancelled:(id)untilCancelledToken; + +@end diff --git a/Signal/src/network/udp/UdpSocket.m b/Signal/src/network/udp/UdpSocket.m new file mode 100644 index 000000000..b54312e76 --- /dev/null +++ b/Signal/src/network/udp/UdpSocket.m @@ -0,0 +1,198 @@ +#import "Constraints.h" +#import "ThreadManager.h" +#import "UdpSocket.h" +#import "Util.h" + +@implementation UdpSocket + ++(UdpSocket*) udpSocketToFirstSenderOnLocalPort:(in_port_t)localPort { + require(localPort > 0); + UdpSocket* p = [UdpSocket new]; + p->specifiedLocalPort = localPort; + p->specifiedRemoteEndPoint = nil; + return p; +} + ++(UdpSocket*) udpSocketFromLocalPort:(in_port_t)localPort + toRemoteEndPoint:(IpEndPoint*)remoteEndPoint { + require(remoteEndPoint != nil); + require([remoteEndPoint port] > 0); + require(localPort > 0); + + UdpSocket* p = [UdpSocket new]; + p->specifiedLocalPort = localPort; + p->specifiedRemoteEndPoint = remoteEndPoint; + return p; +} ++(UdpSocket*) udpSocketTo:(IpEndPoint*)remoteEndPoint { + require(remoteEndPoint != nil); + require([remoteEndPoint port] > 0); + + UdpSocket* p = [UdpSocket new]; + p->specifiedLocalPort = 0; // passing port 0 to CFSocketAddress means 'pick one for me' + p->specifiedRemoteEndPoint = remoteEndPoint; + return p; +} + +-(void) dealloc { + if (socket != nil) { + CFSocketInvalidate(socket); + CFRelease(socket); + } +} + +-(void) send:(NSData*)packet { + @synchronized(self) { + require(packet != nil); + requireState(socket != nil); + requireState([self isRemoteEndPointKnown]); + + hasSentData = true; + CFTimeInterval t = 2.0; + CFSocketError result = CFSocketSendData(socket, NULL, (__bridge CFDataRef)packet, t); + + if (result != kCFSocketSuccess) { + [currentHandler handleError:[NSString stringWithFormat:@"Send failed with error code: %ld", result] + relatedInfo:packet + causedTermination:false]; + } + } +} + +-(bool) isRemoteEndPointKnown { + @synchronized(self) { + return specifiedRemoteEndPoint != nil || clientConnectedFromRemoteEndPoint != nil; + } +} + +-(IpEndPoint *)remoteEndPoint { + requireState([self isRemoteEndPointKnown]); + if (specifiedRemoteEndPoint != nil) return specifiedRemoteEndPoint; + return clientConnectedFromRemoteEndPoint; +} + + +-(bool) isLocalPortKnown { + @synchronized(self) { + return specifiedLocalPort != 0 || measuredLocalPort != 0; + } +} + +-(in_port_t) localPort { + requireState([self isLocalPortKnown]); + if (specifiedLocalPort != 0) return specifiedLocalPort; + return measuredLocalPort; +} + +void onReceivedData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info); +void onReceivedData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { + UdpSocket* udp = (__bridge UdpSocket*)info; + NSData* copyOfPacketData = [NSData dataWithBytes:CFDataGetBytePtr(data) + length:(NSUInteger)CFDataGetLength((CFDataRef)data)]; + [udp onReceivedData:copyOfPacketData + withEventType:type + from:address]; +} +-(void) onReceivedData:(NSData*)data + withEventType:(CFSocketCallBackType)type + from:(CFDataRef)addressData { + + @synchronized(self) { + @try { + checkOperation(type == kCFSocketDataCallBack); + + bool waitingForClient = ![self isRemoteEndPointKnown]; + bool packetHasContent = [data length] > 0; + bool haveNotSentPacketToBeBounced = !hasSentData; + checkOperationDescribe(packetHasContent || waitingForClient || haveNotSentPacketToBeBounced, + @"Received empty UDP packet. Probably indicates destination is unreachable."); + + if (waitingForClient) { + [self onConnectFrom:addressData]; + } + + [currentHandler handlePacket:data]; + } @catch (OperationFailed* ex) { + [currentHandler handleError:ex + relatedInfo:nil + causedTermination:true]; + CFSocketInvalidate(socket); + } + } +} +-(void) onConnectFrom:(CFDataRef)addressData { + CFSocketError connectResult = CFSocketConnectToAddress(socket, + addressData, + -1); + + checkOperationDescribe(connectResult == 0, + ([NSString stringWithFormat:@"CFSocketConnectToAddress failed with error code: %ld", connectResult])); + + clientConnectedFromRemoteEndPoint = [IpEndPoint ipEndPointFromSockaddrData:(__bridge NSData*)addressData]; +} + +-(void) setupLocalEndPoint { + IpEndPoint* specifiedLocalEndPoint = [IpEndPoint ipEndPointAtUnspecifiedAddressOnPort:specifiedLocalPort]; + + CFSocketError setAddressResult = CFSocketSetAddress(socket, (__bridge CFDataRef)[specifiedLocalEndPoint sockaddrData]); + checkOperationDescribe(setAddressResult == kCFSocketSuccess, + ([NSString stringWithFormat:@"CFSocketSetAddress failed with error code: %ld", setAddressResult])); + + IpEndPoint* measuredLocalEndPoint = [IpEndPoint ipEndPointFromSockaddrData:(__bridge_transfer NSData*)CFSocketCopyAddress(socket)]; + measuredLocalPort = [measuredLocalEndPoint port]; +} +-(void) setupRemoteEndPoint { + if (specifiedRemoteEndPoint == nil) return; + + CFSocketError connectResult = CFSocketConnectToAddress(socket, + (__bridge CFDataRef)[specifiedRemoteEndPoint sockaddrData], + -1); + + checkOperationDescribe(connectResult == kCFSocketSuccess, + ([NSString stringWithFormat:@"CFSocketConnectToAddress failed with error code: %ld", connectResult])); + +} + +-(void) startWithHandler:(PacketHandler*)handler + untilCancelled:(id)untilCancelledToken { + + require(handler != nil); + + @synchronized(self) { + bool isFirstTime = currentHandler == nil; + currentHandler = handler; + if (!isFirstTime) return; + } + + @try { + CFSocketContext socketContext = { 0, (__bridge void *)self, CFRetain, CFRelease, CFCopyDescription }; + + socket = CFSocketCreate(kCFAllocatorDefault, + PF_INET, + SOCK_DGRAM, + IPPROTO_UDP, + kCFSocketDataCallBack, + onReceivedData, + &socketContext); + checkOperationDescribe(socket != nil, + @"Failed to create socket."); + + [self setupLocalEndPoint]; + [self setupRemoteEndPoint]; + + NSRunLoop* runLoop = [ThreadManager lowLatencyThreadRunLoop]; + CFRunLoopAddSource([runLoop getCFRunLoop], CFSocketCreateRunLoopSource(NULL, socket, 0), kCFRunLoopCommonModes); + + [untilCancelledToken whenCancelled:^{ + @synchronized(self) { + currentHandler = nil; + CFSocketInvalidate(socket); + } + }]; + } @catch (OperationFailed* ex) { + [handler handleError:ex relatedInfo:nil causedTermination:true]; + CFSocketInvalidate(socket); + } +} + +@end diff --git a/Signal/src/phone/PhoneManager.h b/Signal/src/phone/PhoneManager.h new file mode 100644 index 000000000..d2ed4f0c6 --- /dev/null +++ b/Signal/src/phone/PhoneManager.h @@ -0,0 +1,39 @@ +#import +#import "PhoneNumber.h" +#import "InitiatorSessionDescriptor.h" +#import "CancelTokenSource.h" +#import "Environment.h" +#import "Terminable.h" +#import "Logging.h" +#import "ResponderSessionDescriptor.h" +#import "CallAudioManager.h" +#import "CallConnectUtil.h" +#import "CallController.h" +#import "Contact.h" + +/** + * + * PhoneManager is the highest level class, just below the UI layer. + * It is in charge of the state of the phone (calling, busy, etc). + * User actions like 'make a call' should roughly translate one-to-one with the exposed methods. + * + */ +@interface PhoneManager : NSObject { +@private ObservableValueController* currentCallControllerObservable; +@private ObservableValueController* currentCallStateObservable; +@private int64_t lastIncomingSessionId; +} + +@property (readonly,nonatomic,copy) ErrorHandlerBlock errorHandler; + +-(void) initiateOutgoingCallToRemoteNumber:(PhoneNumber*)remoteNumber; +-(void) initiateOutgoingCallToContact:(Contact*)contact atRemoteNumber:(PhoneNumber*)remoteNumber; +-(void) incomingCallWithSession:(ResponderSessionDescriptor*)session; +-(void) hangupOrDenyCall; +-(void) answerCall; +-(BOOL) toggleMute; +-(ObservableValue*) currentCallObservable; + ++(PhoneManager*) phoneManagerWithErrorHandler:(ErrorHandlerBlock)errorHandler; + +@end diff --git a/Signal/src/phone/PhoneManager.m b/Signal/src/phone/PhoneManager.m new file mode 100644 index 000000000..a63200d69 --- /dev/null +++ b/Signal/src/phone/PhoneManager.m @@ -0,0 +1,141 @@ +#import "CallAudioManager.h" +#import "PhoneManager.h" +#import "ThreadManager.h" +#import "CallTermination.h" +#import "CallFailedServerMessage.h" +#import "CallProgress.h" +#import "RecentCallManager.h" +#import "Util.h" + +@implementation PhoneManager + ++(PhoneManager*) phoneManagerWithErrorHandler:(ErrorHandlerBlock)errorHandler { + PhoneManager* m = [PhoneManager new]; + m->_errorHandler = errorHandler; + m->currentCallControllerObservable = [ObservableValueController observableValueControllerWithInitialValue:nil]; + m->currentCallStateObservable = [ObservableValueController observableValueControllerWithInitialValue:nil]; + + [m->currentCallControllerObservable watchLatestValue:^(CallController* latestValue) { + [m->currentCallStateObservable updateValue:[latestValue callState]]; + } onThread:[NSThread currentThread] untilCancelled:nil]; + + return m; +} + +-(ObservableValue*) currentCallObservable { + return currentCallStateObservable; +} + +-(CallController*) cancelExistingCallAndInitNewCallWork:(bool)initiatedLocally + remote:(PhoneNumber*)remoteNumber + optionalContact:(Contact*)contact { + CallController* old = [self curCallController]; + CallController* new = [CallController callControllerForCallInitiatedLocally:initiatedLocally + withRemoteNumber:remoteNumber + andOptionallySpecifiedContact:contact]; + [old terminateWithReason:CallTerminationType_ReplacedByNext + withFailureInfo:nil + andRelatedInfo:nil]; + [currentCallControllerObservable updateValue:new]; + return new; +} + +-(void) initiateOutgoingCallToContact:(Contact*)contact atRemoteNumber:(PhoneNumber*)remoteNumber { + require(remoteNumber != nil); + [self initiateOutgoingCallToRemoteNumber:remoteNumber withOptionallyKnownContact:contact]; +} + +-(void) initiateOutgoingCallToRemoteNumber:(PhoneNumber*)remoteNumber { + require(remoteNumber != nil); + [self initiateOutgoingCallToRemoteNumber:remoteNumber withOptionallyKnownContact:nil]; +} + +-(void) initiateOutgoingCallToRemoteNumber:(PhoneNumber*)remoteNumber withOptionallyKnownContact:(Contact*)contact { + require(remoteNumber != nil); + + CallController* callController = [self cancelExistingCallAndInitNewCallWork:true + remote:remoteNumber + optionalContact:contact]; + [callController acceptCall]; // initiator implicitly accepts call + id lifetime = [callController untilCancelledToken]; + + Future* futureConnected = [CallConnectUtil asyncInitiateCallToRemoteNumber:remoteNumber + andCallController:callController]; + + Future* futureCalling = [futureConnected then:^id(CallConnectResult* connectResult) { + [callController advanceCallProgressToConversingWithShortAuthenticationString:connectResult.shortAuthenticationString]; + CallAudioManager *cam = [CallAudioManager callAudioManagerStartedWithAudioSocket:connectResult.audioSocket + andErrorHandler:[callController errorHandler] + untilCancelled:lifetime]; + [callController setCallAudioManager:cam]; + return nil; + }]; + + [futureCalling catchDo:^(id error) { + [callController errorHandler](error, nil, true); + }]; +} + +-(void) incomingCallWithSession:(ResponderSessionDescriptor*)session { + require(session != nil); + + int64_t prevSession = lastIncomingSessionId; + lastIncomingSessionId = session.sessionId; + + if ([[[[currentCallControllerObservable currentValue] callState] futureTermination] isIncomplete]) { + if (session.sessionId == prevSession) { + [Environment errorNoter](@"Ignoring duplicate incoming call signal.", session, false); + return; + } + + // @todo: maybe a more general mechanism, so other things can watch? + [[[Environment getCurrent] recentCallManager] addMissedCallDueToBusy:session]; + + [[CallConnectUtil asyncSignalTooBusyToAnswerCallWithSessionDescriptor:session] catchDo:^(id error) { + [Environment errorNoter](error, @"Failed to signal busy.", false); + }]; + return; + } + + CallController* callController = [self cancelExistingCallAndInitNewCallWork:false + remote:session.initiatorNumber + optionalContact:nil]; + id lifetime = [callController untilCancelledToken]; + + Future* futureConnected = [CallConnectUtil asyncRespondToCallWithSessionDescriptor:session + andCallController:callController]; + + Future* futureStarted = [futureConnected then:^id(CallConnectResult* connectResult) { + [callController advanceCallProgressToConversingWithShortAuthenticationString:connectResult.shortAuthenticationString]; + CallAudioManager* cam = [CallAudioManager callAudioManagerStartedWithAudioSocket:connectResult.audioSocket + andErrorHandler:[callController errorHandler] + untilCancelled:lifetime]; + [callController setCallAudioManager:cam]; + return nil; + }]; + + [futureStarted catchDo:^(id error) { + [callController errorHandler](error, nil, true); + }]; +} +-(CallController*) curCallController { + return [currentCallControllerObservable currentValue]; +} +-(void) answerCall { + [[self curCallController] acceptCall]; +} +-(void) hangupOrDenyCall { + [[self curCallController] hangupOrDenyCall]; +} + +-(BOOL) toggleMute{ + return [self.curCallController toggleMute]; +} + +-(void)terminate{ + [[self curCallController] terminateWithReason:CallTerminationType_UncategorizedFailure + withFailureInfo:@"PhoneManager terminated" + andRelatedInfo:nil]; +} + +@end diff --git a/Signal/src/phone/PhoneNumber.h b/Signal/src/phone/PhoneNumber.h new file mode 100644 index 000000000..d9eb18c5b --- /dev/null +++ b/Signal/src/phone/PhoneNumber.h @@ -0,0 +1,39 @@ +#import +#import "NBPhoneNumberUtil.h" + +#define COUNTRY_CODE_PREFIX @"+" + +/** + * + * PhoneNumber is used to deal with the nitty details of parsing/canonicalizing phone numbers. + * Everything that expects a valid phone number should take a PhoneNumber, not a string, to avoid stringly typing. + * + */ +@interface PhoneNumber : NSObject { +@private NBPhoneNumber* phoneNumber; +@private NSString* e164; +} + ++(PhoneNumber*) phoneNumberFromText:(NSString*)text andRegion:(NSString*)regionCode; ++(PhoneNumber*) phoneNumberFromUserSpecifiedText:(NSString*)text; ++(PhoneNumber*) phoneNumberFromE164:(NSString*)text; + ++(PhoneNumber*) tryParsePhoneNumberFromText:(NSString*)text fromRegion:(NSString*)regionCode; ++(PhoneNumber*) tryParsePhoneNumberFromUserSpecifiedText:(NSString*)text; ++(PhoneNumber*) tryParsePhoneNumberFromE164:(NSString*)text; + + ++(NSString*) bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString*)input; ++(NSString*) bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString*)input + withSpecifiedCountryCodeString:(NSString*) countryCodeString; + ++(NSString*) regionCodeFromCountryCodeString:(NSString*) countryCodeString; + +-(NSURL*) toUrl; +-(NSString *)toE164; +-(NSString *)localizedDescriptionForUser; +-(NSNumber*)getCountryCode; +-(BOOL)isValid; +-(BOOL)resolvesInternationallyTo:(PhoneNumber*) otherPhoneNumber; + +@end diff --git a/Signal/src/phone/PhoneNumber.m b/Signal/src/phone/PhoneNumber.m new file mode 100644 index 000000000..ced994394 --- /dev/null +++ b/Signal/src/phone/PhoneNumber.m @@ -0,0 +1,174 @@ +#import "PhoneNumber.h" +#import "Constraints.h" +#import "Util.h" +#import "PreferencesUtil.h" +#import "Environment.h" +#import "NBPhoneNumber.h" +#import "NBAsYouTypeFormatter.h" + +static NSString *const RPDefaultsKeyPhoneNumberString = @"RPDefaultsKeyPhoneNumberString"; +static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneNumberCanonical"; + +@implementation PhoneNumber + ++(PhoneNumber*) phoneNumberFromText:(NSString*)text andRegion:(NSString*)regionCode { + require(text != nil); + require(regionCode != nil); + + NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance]; + + NSError* parseError = nil; + NBPhoneNumber *number = [phoneUtil parse:text + defaultRegion:regionCode + error:&parseError]; + checkOperationDescribe(parseError == nil, [parseError description]); + //checkOperation([phoneUtil isValidNumber:number]); + + NSError* toE164Error; + NSString* e164 = [phoneUtil format:number numberFormat:NBEPhoneNumberFormatE164 error:&toE164Error]; + checkOperationDescribe(toE164Error == nil, [e164 description]); + + PhoneNumber* phoneNumber = [PhoneNumber new]; + phoneNumber->phoneNumber = number; + phoneNumber->e164 = e164; + return phoneNumber; +} + ++(PhoneNumber*) phoneNumberFromUserSpecifiedText:(NSString*)text { + require(text != nil); + + return [PhoneNumber phoneNumberFromText:text + andRegion:[Environment currentRegionCodeForPhoneNumbers]]; +} + ++(PhoneNumber*) phoneNumberFromE164:(NSString*)text { + require(text != nil); + checkOperation([text hasPrefix:COUNTRY_CODE_PREFIX]); + + return [PhoneNumber phoneNumberFromText:text + andRegion:@"ZZ"]; +} + ++(NSString*) bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString*)input { + return [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input + withSpecifiedRegionCode:[Environment currentRegionCodeForPhoneNumbers]]; +} + ++(NSString*) bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString*)input withSpecifiedCountryCodeString:(NSString *)countryCodeString{ + return [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input + withSpecifiedRegionCode:[PhoneNumber regionCodeFromCountryCodeString:countryCodeString]]; +} + ++(NSString*) bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString*)input withSpecifiedRegionCode:(NSString *) regionCode{ + NBAsYouTypeFormatter* formatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:regionCode]; + + NSString* result = input; + for (NSUInteger i = 0; i < [input length]; i++) { + result = [formatter inputDigit:[input substringWithRange:NSMakeRange(i, 1)]]; + } + return result; +} + + ++(NSString*) regionCodeFromCountryCodeString:(NSString*) countryCodeString { + NBPhoneNumberUtil* phoneUtil = [NBPhoneNumberUtil sharedInstance]; + NSString* regionCode = [phoneUtil getRegionCodeForCountryCode:[NSNumber numberWithInteger:[[countryCodeString substringFromIndex:1] integerValue]]]; + return regionCode; +} + + ++(PhoneNumber*) tryParsePhoneNumberFromText:(NSString*)text fromRegion:(NSString*)regionCode { + require(text != nil); + require(regionCode != nil); + + @try { + return [self phoneNumberFromText:text andRegion:regionCode]; + } @catch (OperationFailed* ex) { + return nil; + } +} + ++(PhoneNumber*) tryParsePhoneNumberFromUserSpecifiedText:(NSString*)text { + require(text != nil); + + char s[[text length]+1]; + int xx = 0; + for (NSUInteger i = 0; i < [text length]; i++) { + // @todo: Don't be terrible + unichar x = [text characterAtIndex:i]; + if (x == '+' || (x >= '0' && x <= '9')) { + s[xx++] = (char)x; + } + } + + s[xx]=0; + text = [NSString stringWithUTF8String:(void*)s]; + + @try { + return [self phoneNumberFromUserSpecifiedText:text]; + } @catch (OperationFailed* ex) { + return nil; + } +} ++(PhoneNumber*) tryParsePhoneNumberFromE164:(NSString*)text { + require(text != nil); + + @try { + return [self phoneNumberFromE164:text]; + } @catch (OperationFailed* ex) { + return nil; + } +} + +-(NSURL*) toUrl { + // @todo: can we return a more user-friendly link (using original text) in some limited cases? + NSString* link = [NSString stringWithFormat:@"telprompt://%@", e164]; + return [NSURL URLWithString:link]; +} + +-(NSString *)toE164 { + return e164; +} + +- (NSNumber*)getCountryCode { + return phoneNumber.countryCode; +} + +-(BOOL)isValid { + return [[NBPhoneNumberUtil sharedInstance] isValidNumber:phoneNumber]; +} + +-(NSString *)localizedDescriptionForUser { + NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance]; + + NSError* formatError = nil; + NSString* pretty = [phoneUtil format:phoneNumber + numberFormat:NBEPhoneNumberFormatINTERNATIONAL + error:&formatError]; + + if (formatError != nil) return e164; + return pretty; +} + +-(BOOL)resolvesInternationallyTo:(PhoneNumber*) otherPhoneNumber { + return [self.toE164 isEqualToString:otherPhoneNumber.toE164]; +} + +-(NSString*) description { + return e164; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:phoneNumber forKey:RPDefaultsKeyPhoneNumberString]; + [encoder encodeObject:e164 forKey:RPDefaultsKeyPhoneNumberCanonical]; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if((self = [super init])) { + phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString]; + e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical]; + } + return self; +} + +@end diff --git a/Signal/src/phone/callstate/CallController.h b/Signal/src/phone/callstate/CallController.h new file mode 100644 index 000000000..6fe54a753 --- /dev/null +++ b/Signal/src/phone/callstate/CallController.h @@ -0,0 +1,55 @@ +#import +#import "CallAudioManager.h" +#import "CallState.h" +#import "CallProgress.h" +#import "CallTermination.h" +#import "CancelTokenSource.h" +#import "FutureSource.h" +#import "PacketHandler.h" + +/** + * + * CallController is where information about the progress and termination of a call is collected. + * It is responsible for distilling call events from various components into something usable by the UI. + * + * Components can indicate the call is progressing by calling advance with a progress type. + * Components can terminate the call by calling terminate with a reason. + * + * CallController takes care of ensuring progress never goes backwards, maintaining thread safety, etc. + * + */ +@interface CallController : NSObject { +@private ObservableValueController* progress; +@private FutureSource* termination; +@private FutureSource* shortAuthenticationString; +@private CancelTokenSource* canceller; +@private FutureSource* interactiveCallAcceptedOrDenied; +@private bool initiatedLocally; +@private PhoneNumber* remoteNumber; +@private CallState* exposedCallState; +@private Contact* potentiallySpecifiedContact; +@private CallAudioManager *callAudioManager; +} + ++(CallController*) callControllerForCallInitiatedLocally:(bool)initiatedLocally + withRemoteNumber:(PhoneNumber*)remoteNumber + andOptionallySpecifiedContact:(Contact*)contact; + +-(void)setCallAudioManager:(CallAudioManager*) callAudioManager; +-(void)advanceCallProgressTo:(enum CallProgressType)type; +-(void)hangupOrDenyCall; +-(void)acceptCall; +-(void)advanceCallProgressToConversingWithShortAuthenticationString:(NSString*)sas; +-(void)terminateWithReason:(enum CallTerminationType)reason + withFailureInfo:(id)failureInfo + andRelatedInfo:(id)relatedInfo; +-(void)terminateWithRejectionOrRemoteHangupAndFailureInfo:(id)failureInfo andRelatedInfo:(id)relatedInfo; +-(BOOL)toggleMute; +-(bool) isInitiator; +-(Future*)interactiveCallAccepted; +-(ErrorHandlerBlock)errorHandler; +-(id)untilCancelledToken; +-(CallState*)callState; + +@end + diff --git a/Signal/src/phone/callstate/CallController.m b/Signal/src/phone/callstate/CallController.m new file mode 100644 index 000000000..3a3102366 --- /dev/null +++ b/Signal/src/phone/callstate/CallController.m @@ -0,0 +1,155 @@ +#import "CallController.h" +#import "Util.h" +#import "Environment.h" +#import "SignalUtil.h" + +@implementation CallController { + UIBackgroundTaskIdentifier backgroundtask; +} + ++(CallController*) callControllerForCallInitiatedLocally:(bool)initiatedLocally + withRemoteNumber:(PhoneNumber*)remoteNumber + andOptionallySpecifiedContact:(Contact*)contact { + require(remoteNumber != nil); + + CallController* instance = [CallController new]; + CallProgress* initialProgress = [CallProgress callProgressWithType:CallProgressType_Connecting]; + instance->progress = [ObservableValueController observableValueControllerWithInitialValue:initialProgress]; + instance->termination = [FutureSource new]; + instance->shortAuthenticationString = [FutureSource new]; + instance->canceller = [CancelTokenSource cancelTokenSource]; + instance->interactiveCallAcceptedOrDenied = [FutureSource new]; + instance->initiatedLocally = initiatedLocally; + instance->remoteNumber = remoteNumber; + instance->potentiallySpecifiedContact = contact; + instance->exposedCallState = [CallState callStateWithObservableProgress:instance->progress + andFutureTermination:instance->termination + andFutureSas:instance->shortAuthenticationString + andRemoteNumber:instance->remoteNumber + andInitiatedLocally:instance->initiatedLocally + andPotentiallySpecifiedContact:instance->potentiallySpecifiedContact + andFutureAccepted:instance->interactiveCallAcceptedOrDenied]; + + return instance; +} + +-(void) setCallAudioManager:(CallAudioManager*) _callAudioManager { + callAudioManager = _callAudioManager; +} + +-(bool) isInitiator { + return initiatedLocally; +} + +-(ErrorHandlerBlock) errorHandler { + return ^(id error, id relatedInfo, bool causedTermination) { + if (causedTermination) { + if ([error isKindOfClass:[CallTermination class]]) { + CallTermination* t = error; + [self terminateWithReason:t.type + withFailureInfo:t.failure + andRelatedInfo:t.messageInfo]; + } else { + [self terminateWithReason:CallTerminationType_UncategorizedFailure + withFailureInfo:error + andRelatedInfo:relatedInfo]; + } + } + + [Environment errorNoter](error, relatedInfo, causedTermination); + }; +} +-(id) untilCancelledToken { + return [canceller getToken]; +} +-(Future *)interactiveCallAccepted { + return [interactiveCallAcceptedOrDenied then:^id(NSNumber* accepted) { + if ([accepted boolValue]) return accepted; + + return [Future failed:[CallTermination callTerminationOfType:CallTerminationType_RejectedLocal + withFailure:accepted + andMessageInfo:nil]]; + }]; +} +-(Future *)interactiveCallAcceptedOrDenied { + return interactiveCallAcceptedOrDenied; +} +-(CallState*) callState { + return exposedCallState; +} + +-(void)unrestrictedAdvanceCallProgressTo:(enum CallProgressType)type { + [progress adjustValue:^id(CallProgress* oldValue) { + if (type < [oldValue type]) return oldValue; + return [CallProgress callProgressWithType:type]; + }]; +} + +-(void)advanceCallProgressTo:(enum CallProgressType)type { + require(type < CallProgressType_Talking); + + [self unrestrictedAdvanceCallProgressTo:type]; +} +-(void)hangupOrDenyCall { + bool didDeny = [interactiveCallAcceptedOrDenied trySetResult:@NO]; + + enum CallTerminationType terminationType = didDeny + ? CallTerminationType_RejectedLocal + : CallTerminationType_HangupLocal; + [self terminateWithReason:terminationType + withFailureInfo:nil + andRelatedInfo:nil]; +} +-(void)acceptCall { + [interactiveCallAcceptedOrDenied trySetResult:@YES]; +} + +-(void)advanceCallProgressToConversingWithShortAuthenticationString:(NSString*)sas { + require(sas != nil); + [shortAuthenticationString trySetResult:sas]; + [self unrestrictedAdvanceCallProgressTo:CallProgressType_Talking]; +} + +-(void)terminateWithRejectionOrRemoteHangupAndFailureInfo:(id)failureInfo andRelatedInfo:(id)relatedInfo { + enum CallProgressType progressType = ((CallProgress*)[progress currentValue]).type; + bool hasAcceptedAlready = progressType > CallProgressType_Ringing; + enum CallTerminationType terminationType = hasAcceptedAlready + ? CallTerminationType_HangupRemote + : CallTerminationType_RejectedRemote; + + [self terminateWithReason:terminationType + withFailureInfo:failureInfo + andRelatedInfo:relatedInfo]; +} +-(void)terminateWithReason:(enum CallTerminationType)reason + withFailureInfo:(id)failureInfo + andRelatedInfo:(id)relatedInfo { + + CallTermination* t = [CallTermination callTerminationOfType:reason + withFailure:failureInfo + andMessageInfo:relatedInfo]; + + if (![termination trySetResult:t]) return; + [self unrestrictedAdvanceCallProgressTo:CallProgressType_Terminated]; + [interactiveCallAcceptedOrDenied trySetFailure:t]; + [canceller cancel]; + [shortAuthenticationString trySetFailure:t]; + [progress sealValue]; +} + +-(BOOL) toggleMute { + return [callAudioManager toggleMute]; +} + +-(void) enableBackground { + [progress watchLatestValueOnArbitraryThread:^(CallProgress* latestProgress) { + if( CallProgressType_Connecting == latestProgress.type) { + backgroundtask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + //todo: handle premature expiration + }]; + }else if(CallProgressType_Terminated == latestProgress.type){ + [[UIApplication sharedApplication] endBackgroundTask:backgroundtask]; + } + } untilCancelled:canceller.getToken]; +} +@end diff --git a/Signal/src/phone/callstate/CallFailedServerMessage.h b/Signal/src/phone/callstate/CallFailedServerMessage.h new file mode 100644 index 000000000..4946ee1a9 --- /dev/null +++ b/Signal/src/phone/callstate/CallFailedServerMessage.h @@ -0,0 +1,9 @@ +#import + +@interface CallFailedServerMessage : NSObject + +@property (readonly, nonatomic) NSString* text; + ++(CallFailedServerMessage*) callFailedServerMessageWithText:(NSString*)text; + +@end diff --git a/Signal/src/phone/callstate/CallFailedServerMessage.m b/Signal/src/phone/callstate/CallFailedServerMessage.m new file mode 100644 index 000000000..e95fc058f --- /dev/null +++ b/Signal/src/phone/callstate/CallFailedServerMessage.m @@ -0,0 +1,16 @@ +#import "CallFailedServerMessage.h" +#import "Util.h" + +@implementation CallFailedServerMessage + +@synthesize text; + ++(CallFailedServerMessage*) callFailedServerMessageWithText:(NSString*)text { + require(text != nil); + + CallFailedServerMessage* instance = [CallFailedServerMessage new]; + instance->text = text; + return instance; +} + +@end diff --git a/Signal/src/phone/callstate/CallProgress.h b/Signal/src/phone/callstate/CallProgress.h new file mode 100644 index 000000000..2f1a7e46b --- /dev/null +++ b/Signal/src/phone/callstate/CallProgress.h @@ -0,0 +1,49 @@ +#import + +enum CallProgressType { + /// Connecting covers: + /// - The initiator is establishing connection (over tls/tcp) to the default signaling server + /// - The initiator is requesting (using an http request) a call session to the to-be responder + /// - The initiator is contacting (over udp) the relay server described in the call descriptor it received + /// - The initiator has confirmed the session with the signaling server, but not yet received the 'Ringing' signal + /// - The responder is notified (via a device notification) of an incoming call + /// - The responder is contacting (over udp) the relay server described in the call descriptor it received + CallProgressType_Connecting, + + /// Ringing covers: + /// - The initiator has received a 'Ringing' signal from the signaling server it contacted + /// - The initiator has not yet received a zrtp 'Hello' from the responder via the relay + /// - The responder has confirmed the session with the signaling server + /// - The responder's user has not yet accepted the incoming call + CallProgressType_Ringing, + + /// Securing covers: + /// - The initiator has received a zrtp 'Hello' from the responder + /// - The initiator has not yet determined the handshake is over (by receiving a zrtp 'ConfAck' or authenticated audio data) + /// - The responder's user has accepted the call (causing the responder to send zrtp 'Hello') + /// - The responder has not yet determined the handshake is over (by receiving a zrtp 'Confirm2') + CallProgressType_Securing, + + /// Talking covers: + /// - Sending/Receiving encrypted and authenticated audio + CallProgressType_Talking, + + /// Terminated covers: + /// - Any of the call setup failed for whatever reason + /// - Either of the users decided to hang up + CallProgressType_Terminated +}; + +/** + * + * The CallProgress class is just an NSObject wrapper for the CallProgressType enum. + * + **/ +@interface CallProgress : NSObject + +@property (nonatomic, readonly) enum CallProgressType type; + ++(CallProgress*) callProgressWithType:(enum CallProgressType)type; +-(NSString*) localizedDescriptionForUser; + +@end diff --git a/Signal/src/phone/callstate/CallProgress.m b/Signal/src/phone/callstate/CallProgress.m new file mode 100644 index 000000000..467ba748e --- /dev/null +++ b/Signal/src/phone/callstate/CallProgress.m @@ -0,0 +1,31 @@ +#import "CallProgress.h" +#import "LocalizableText.h" + +@implementation CallProgress + +@synthesize type; + ++(CallProgress*) callProgressWithType:(enum CallProgressType)type { + CallProgress* instance = [CallProgress new]; + instance->type = type; + return instance; +} + +-(BOOL)isEqual:(id)object { + return [object isKindOfClass:[CallProgress class]] && ((CallProgress*)object).type == type; +} +-(NSUInteger)hash { + return type; +} +-(NSString *)description { + return makeCallProgressLocalizedTextDictionary()[self]; +} +-(NSString*) localizedDescriptionForUser { + return [self description]; +} + +-(id)copyWithZone:(NSZone *)zone { + return [CallProgress callProgressWithType:type]; +} + +@end diff --git a/Signal/src/phone/callstate/CallState.h b/Signal/src/phone/callstate/CallState.h new file mode 100644 index 000000000..d8d340d1f --- /dev/null +++ b/Signal/src/phone/callstate/CallState.h @@ -0,0 +1,37 @@ +#import +#import "ObservableValue.h" +#import "PhoneNumber.h" +#import "Contact.h" + +/** + * + * CallState exposes the state of a single call, in a simplified way intended to be consumed by the user interface code. + * + * The observable value observableProgress exposes values of type CallProgression that indicate the progression of the call, from connecting through teminated. + * The future futureTermination will eventually contain a CallTermination that indicates how the call terminated. + * The future futureShortAuthenticationString will eventually contain the sas to display, or else a failure if the call fails beforehand. + * The remoteNumber field is what it sounds like. + * The initiatedLocally field determines if we are the initiator of the call or the responder to a call. + * + * The futures exposed by this type are guaranteed to run callbacks (from then/catch/finally/etc) either inline or on the main thread. + * + */ +@interface CallState : NSObject + +@property (nonatomic, readonly) ObservableValue* observableProgress; +@property (nonatomic, readonly) Future* futureTermination; +@property (nonatomic, readonly) Future* futureShortAuthenticationString; +@property (nonatomic, readonly) PhoneNumber* remoteNumber; +@property (nonatomic, readonly) bool initiatedLocally; +@property (nonatomic, readonly) Contact* potentiallySpecifiedContact; +@property (nonatomic, readonly) Future* futureCallLocallyAcceptedOrRejected; + ++(CallState*) callStateWithObservableProgress:(ObservableValue*)observableProgress + andFutureTermination:(Future*)futureTermination + andFutureSas:(Future*)futureSas + andRemoteNumber:(PhoneNumber*)remoteNumber + andInitiatedLocally:(bool)initiatedLocally + andPotentiallySpecifiedContact:(Contact*)contact + andFutureAccepted:(Future*)futureCallLocallyAcceptedOrRejected; + +@end diff --git a/Signal/src/phone/callstate/CallState.m b/Signal/src/phone/callstate/CallState.m new file mode 100644 index 000000000..43e1b50b9 --- /dev/null +++ b/Signal/src/phone/callstate/CallState.m @@ -0,0 +1,39 @@ +#import "CallState.h" +#import "Util.h" + +@implementation CallState + +@synthesize observableProgress; +@synthesize futureTermination; +@synthesize remoteNumber; +@synthesize futureShortAuthenticationString; +@synthesize initiatedLocally; +@synthesize potentiallySpecifiedContact; +@synthesize futureCallLocallyAcceptedOrRejected; + ++(CallState*) callStateWithObservableProgress:(ObservableValue*)observableProgress + andFutureTermination:(Future*)futureTermination + andFutureSas:(Future*)futureSas + andRemoteNumber:(PhoneNumber*)remoteNumber + andInitiatedLocally:(bool)initiatedLocally + andPotentiallySpecifiedContact:(Contact*)contact + andFutureAccepted:(Future*)futureCallLocallyAcceptedOrRejected { + + require(observableProgress != nil); + require(futureTermination != nil); + require(futureSas != nil); + require(remoteNumber != nil); + require(futureCallLocallyAcceptedOrRejected != nil); + + CallState* call = [CallState new]; + call->observableProgress = observableProgress; + call->futureTermination = [futureTermination thenCompleteOnMainThread]; + call->futureShortAuthenticationString = [futureSas thenCompleteOnMainThread]; + call->remoteNumber = remoteNumber; + call->initiatedLocally = initiatedLocally; + call->potentiallySpecifiedContact = contact; + call->futureCallLocallyAcceptedOrRejected = futureCallLocallyAcceptedOrRejected; + return call; +} + +@end diff --git a/Signal/src/phone/callstate/CallTermination.h b/Signal/src/phone/callstate/CallTermination.h new file mode 100644 index 000000000..4f46f5880 --- /dev/null +++ b/Signal/src/phone/callstate/CallTermination.h @@ -0,0 +1,47 @@ +#import + +enum CallTerminationType { + // -- while connecting -- + CallTerminationType_LoginFailed, /// The signaling server said our authentication details were wrong. + CallTerminationType_NoSuchUser, /// The signaling server said there's red phone user with that number. + CallTerminationType_StaleSession, /// The signaling server said the call we're trying to respond to managed to end before we made contact. + CallTerminationType_ServerMessage, /// The signaling server said we should display a custom message (it's in the messageInfo property). + + // -- while ringing -- + CallTerminationType_ResponderIsBusy, /// The signaling server said the responder can't answer because they're busy. + CallTerminationType_RecipientUnavailable, /// The signaling server said the responder never contacted it about the incoming call. + CallTerminationType_RejectedLocal, /// We did not accept the call. + CallTerminationType_RejectedRemote, /// The signaling server said the other side hung up (before handshake). + + // -- while securing -- + CallTerminationType_HandshakeFailed, /// Something went wrong in the middle of the zrtp handshake. + CallTerminationType_InvalidRemotePublicKey, /// The publickey supplied from the remote participant was not valid + + // -- anytime -- + CallTerminationType_HangupLocal, /// We hung up. + CallTerminationType_HangupRemote, /// The signaling server said the other side hung up (after they accepted). + CallTerminationType_ReplacedByNext, /// We automatically hung up because we started another call. (e.g. incoming call cancelled by us dialing out) + + // -- uh oh -- + CallTerminationType_BadInteractionWithServer, /// The signaling or relay server did something we didn't expect or understand. + CallTerminationType_UncategorizedFailure, /// Something went wrong. We didn't handle it properly, so we don't know what exactly it was. +}; + +/** + * + * The CallTermination class is just an NSObject wrapper for the CallTerminationType enum. + * + **/ + +@interface CallTermination : NSObject + +@property (readonly, nonatomic) enum CallTerminationType type; +@property (readonly, nonatomic) id failure; +@property (readonly, nonatomic) id messageInfo; + ++(CallTermination*) callTerminationOfType:(enum CallTerminationType)type + withFailure:(id)failure + andMessageInfo:(id)messageInfo; +-(NSString*) localizedDescriptionForUser; + +@end diff --git a/Signal/src/phone/callstate/CallTermination.m b/Signal/src/phone/callstate/CallTermination.m new file mode 100644 index 000000000..9ef4befd1 --- /dev/null +++ b/Signal/src/phone/callstate/CallTermination.m @@ -0,0 +1,38 @@ +#import "CallTermination.h" +#import "LocalizableText.h" + +@implementation CallTermination + +@synthesize type, failure, messageInfo; + ++(CallTermination*) callTerminationOfType:(enum CallTerminationType)type + withFailure:(id)failure + andMessageInfo:(id)messageInfo { + + CallTermination* instance = [CallTermination new]; + instance->type = type; + instance->failure = failure; + instance->messageInfo = messageInfo; + return instance; +} + +-(BOOL)isEqual:(id)object { + return [object isKindOfClass:[CallTermination class]] && ((CallTermination*)object).type == type; +} +-(NSUInteger)hash { + return type; +} +-(NSString *)description { + return makeCallTerminationLocalizedTextDictionary()[self]; +} +-(NSString*) localizedDescriptionForUser { + return [self description]; +} + +-(id)copyWithZone:(NSZone *)zone { + return [CallTermination callTerminationOfType:type + withFailure:[failure copyWithZone:zone] + andMessageInfo:[messageInfo copyWithZone:zone]]; +} + +@end diff --git a/Signal/src/phone/signaling/CallConnectResult.h b/Signal/src/phone/signaling/CallConnectResult.h new file mode 100644 index 000000000..36018522a --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectResult.h @@ -0,0 +1,18 @@ +#import +#import "AudioSocket.h" + +/** + * + * A CallConnectResult is the eventual result of trying to initiate or respond to a call. + * It includes a secure communication channel and a short authentication string. + * + */ +@interface CallConnectResult : NSObject + +@property (nonatomic,readonly) NSString* shortAuthenticationString; +@property (nonatomic,readonly) AudioSocket* audioSocket; + ++(CallConnectResult*) callConnectResultWithShortAuthenticationString:(NSString*)shortAuthenticationString + andAudioSocket:(AudioSocket*)audioSocket; + +@end diff --git a/Signal/src/phone/signaling/CallConnectResult.m b/Signal/src/phone/signaling/CallConnectResult.m new file mode 100644 index 000000000..6dca43ece --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectResult.m @@ -0,0 +1,18 @@ +#import "CallConnectResult.h" + +@implementation CallConnectResult + +@synthesize audioSocket, shortAuthenticationString; + ++(CallConnectResult*) callConnectResultWithShortAuthenticationString:(NSString*)shortAuthenticationString + andAudioSocket:(AudioSocket*)audioSocket { + require(shortAuthenticationString != nil); + require(audioSocket != nil); + + CallConnectResult* result = [CallConnectResult new]; + result->shortAuthenticationString = shortAuthenticationString; + result->audioSocket = audioSocket; + return result; +} + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil.h b/Signal/src/phone/signaling/CallConnectUtil.h new file mode 100644 index 000000000..868c5b8d8 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil.h @@ -0,0 +1,29 @@ +#import +#import "CallConnectResult.h" +#import "CallController.h" +#import "ResponderSessionDescriptor.h" + +/** + * + * CallConnectUtil is a utility class containing methods related to connecting calls. + * + * Its implementation is actually split over more specific utility classes: + * - CallConnectUtil_Initiator + * - CallConnectUtil_Responder + * - CallConnectUtil_Server + * + **/ +@interface CallConnectUtil : NSObject + +/// Result has type Future(CallConnectResult) ++(Future*) asyncInitiateCallToRemoteNumber:(PhoneNumber*)remoteNumber + andCallController:(CallController*)callController; + +/// Result has type Future(CallConnectResult) ++(Future*) asyncRespondToCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor + andCallController:(CallController*)callController; + +/// Result has type Future(HttpResponse) ++(Future*) asyncSignalTooBusyToAnswerCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor; + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil.m b/Signal/src/phone/signaling/CallConnectUtil.m new file mode 100644 index 000000000..b056a6588 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil.m @@ -0,0 +1,30 @@ +#import "CallConnectUtil.h" +#import "CallConnectUtil_Initiator.h" +#import "CallConnectUtil_Responder.h" +#import "SignalUtil.h" +#import "Util.h" + +@implementation CallConnectUtil + ++(Future*) asyncInitiateCallToRemoteNumber:(PhoneNumber *)remoteNumber + andCallController:(CallController*)callController { + require(remoteNumber != nil); + require(callController != nil); + return [CallConnectUtil_Initiator asyncConnectCallToRemoteNumber:remoteNumber + withCallController:callController]; +} + ++(Future*) asyncRespondToCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor + andCallController:(CallController*)callController { + require(sessionDescriptor != nil); + require(callController != nil); + return [CallConnectUtil_Responder asyncConnectToIncomingCallWithSessionDescriptor:sessionDescriptor + andCallController:callController]; +} + ++(Future*) asyncSignalTooBusyToAnswerCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor { + require(sessionDescriptor != nil); + return [CallConnectUtil_Responder asyncSignalTooBusyToAnswerCallWithSessionDescriptor:sessionDescriptor]; +} + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil_Initiator.h b/Signal/src/phone/signaling/CallConnectUtil_Initiator.h new file mode 100644 index 000000000..e74da2932 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Initiator.h @@ -0,0 +1,24 @@ +#import +#import "CallController.h" +#import "HttpManager.h" +#import "InitiatorSessionDescriptor.h" + +/** + * + * CallConnectUtil_Initiator is a utility class that deals with the details of initiating a call: + * - Contacting the default signaling server + * - Asking for a session descriptor to call the other number + * - Forwarding later signals like 'ringing' and 'hangup' + * - Contacting the relay server from the descriptor + * - Starting the zrtp handshake + * - etc + * + **/ +@interface CallConnectUtil_Initiator : NSObject + +/// Result has type Future*(CallConnectResult) ++(Future*) asyncConnectCallToRemoteNumber:(PhoneNumber*)remoteNumber + withCallController:(CallController*)callController; + +@end + diff --git a/Signal/src/phone/signaling/CallConnectUtil_Initiator.m b/Signal/src/phone/signaling/CallConnectUtil_Initiator.m new file mode 100644 index 000000000..44aeda1fc --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Initiator.m @@ -0,0 +1,145 @@ +#import "CallConnectUtil_Initiator.h" + +#import "CallConnectUtil.h" +#import "CallConnectUtil_Server.h" +#import "IgnoredPacketFailure.h" +#import "SignalUtil.h" +#import "UnrecognizedRequestFailure.h" +#import "Util.h" +#import "ZrtpManager.h" + +@implementation CallConnectUtil_Initiator + ++(Future*) asyncConnectCallToRemoteNumber:(PhoneNumber*)remoteNumber + withCallController:(CallController*)callController { + + require(remoteNumber != nil); + require(callController != nil); + require([callController isInitiator]); + + Future* futureInitiatorSessionDescriptor = [self asyncConnectToSignalServerAndGetInitiatorSessionDescriptorWithCallController:callController]; + + return [futureInitiatorSessionDescriptor then:^(InitiatorSessionDescriptor* session) { + return [CallConnectUtil_Server asyncConnectCallOverRelayDescribedInInitiatorSessionDescriptor:session + withCallController:callController + andInteropOptions:@[]]; + }]; +} + ++(Future*) asyncConnectToSignalServerAndGetInitiatorSessionDescriptorWithCallController:(CallController*)callController { + require(callController != nil); + + Future* futureSignalConnection = [CallConnectUtil_Server asyncConnectToDefaultSignalingServerUntilCancelled:[callController untilCancelledToken]]; + + return [futureSignalConnection then:^(HttpManager* httpManager) { + requireState([httpManager isKindOfClass:[HttpManager class]]); + + FutureSource* predeclaredFutureSession = [FutureSource new]; + + HttpResponse*(^serverRequestHandler)(HttpRequest*) = ^(HttpRequest* remoteRequest) { + return [self respondToServerRequest:remoteRequest + usingEventualDescriptor:predeclaredFutureSession + andCallController:callController]; + }; + + [httpManager startWithRequestHandler:serverRequestHandler + andErrorHandler:[callController errorHandler] + untilCancelled:[callController untilCancelledToken]]; + + HttpRequest* initiateRequest = [HttpRequest httpRequestToInitiateToRemoteNumber:[callController callState].remoteNumber]; + Future* futureResponseToInitiate = [httpManager asyncOkResponseForRequest:initiateRequest + unlessCancelled:[callController untilCancelledToken]]; + Future* futureResponseToInitiateWithInterpretedFailures = [futureResponseToInitiate catch:^(id error) { + if ([error isKindOfClass:[HttpResponse class]]) { + HttpResponse* badResponse = error; + return [Future failed:[self callTerminationForBadResponse:badResponse + toInitiateRequest:initiateRequest]]; + } + + return [Future failed:error]; + }]; + + Future* futureSession = [futureResponseToInitiateWithInterpretedFailures then:^(HttpResponse* response) { + return [InitiatorSessionDescriptor initiatorSessionDescriptorFromJson:[response getOptionalBodyText]]; + }]; + [predeclaredFutureSession trySetResult:futureSession]; + + return futureSession; + }]; +} + ++(CallTermination*) callTerminationForBadResponse:(HttpResponse*)badResponse + toInitiateRequest:(HttpRequest*)initiateRequest { + require(badResponse != nil); + require(initiateRequest != nil); + + switch ([badResponse getStatusCode]) { + case SIGNAL_STATUS_CODE_NO_SUCH_USER: + return [CallTermination callTerminationOfType:CallTerminationType_NoSuchUser + withFailure:badResponse + andMessageInfo:initiateRequest]; + case SIGNAL_STATUS_CODE_SERVER_MESSAGE: + return [CallTermination callTerminationOfType:CallTerminationType_ServerMessage + withFailure:badResponse + andMessageInfo:[badResponse getOptionalBodyText]]; + case SIGNAL_STATUS_CODE_LOGIN_FAILED: + return [CallTermination callTerminationOfType:CallTerminationType_LoginFailed + withFailure:badResponse + andMessageInfo:initiateRequest]; + default: + return [CallTermination callTerminationOfType:CallTerminationType_BadInteractionWithServer + withFailure:badResponse + andMessageInfo:initiateRequest]; + } +} + ++(HttpResponse*) respondToServerRequest:(HttpRequest*)request + usingEventualDescriptor:(Future*)futureInitiatorSessionDescriptor + andCallController:(CallController*)callController { + require(request != nil); + require(futureInitiatorSessionDescriptor != nil); + require(callController != nil); + + // heart beat? + if ([request isKeepAlive]) { + return [HttpResponse httpResponse200Ok]; + } + + // too soon? + if (![futureInitiatorSessionDescriptor hasSucceeded]) { + [callController terminateWithReason:CallTerminationType_BadInteractionWithServer + withFailureInfo:[IgnoredPacketFailure new:@"Didn't receive session id from signaling server. Not able to understand request."] + andRelatedInfo:request]; + return [HttpResponse httpResponse500InternalServerError]; + } + int64_t sessionId = [[futureInitiatorSessionDescriptor forceGetResult] sessionId]; + + // hangup? + if ([request isHangupForSession:sessionId]) { + [callController terminateWithRejectionOrRemoteHangupAndFailureInfo:nil + andRelatedInfo:request]; + return [HttpResponse httpResponse200Ok]; + } + + // ringing? + if ([request isRingingForSession:sessionId]) { + [callController advanceCallProgressTo:CallProgressType_Ringing]; + return [HttpResponse httpResponse200Ok]; + } + + // busy signal? + if ([request isBusyForSession:sessionId]) { + [callController terminateWithReason:CallTerminationType_ResponderIsBusy + withFailureInfo:nil + andRelatedInfo:request]; + return [HttpResponse httpResponse200Ok]; + } + + // errr..... + [callController terminateWithReason:CallTerminationType_BadInteractionWithServer + withFailureInfo:[UnrecognizedRequestFailure new:@"Didn't understand signaling server."] + andRelatedInfo:request]; + return [HttpResponse httpResponse501NotImplemented]; +} + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil_Responder.h b/Signal/src/phone/signaling/CallConnectUtil_Responder.h new file mode 100644 index 000000000..96c2861c4 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Responder.h @@ -0,0 +1,26 @@ +#import +#import "HttpManager.h" +#import "ResponderSessionDescriptor.h" +#import "CallController.h" + +/** + * + * CallConnectUtil_Responder is a utility class that deals with the details of responding to a call: + * - Contacting the described signaling server + * - Signalling busy or ringing + * - Forwarding later signals like 'hangup' + * - Contacting the described relay server + * - Starting the zrtp handshake + * - etc + * + **/ +@interface CallConnectUtil_Responder : NSObject + +/// Result has type Future(CallConnectResult) ++(Future*) asyncConnectToIncomingCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor + andCallController:(CallController*)callController; + +/// Result has type Future(HttpResponse) ++(Future*) asyncSignalTooBusyToAnswerCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor; + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil_Responder.m b/Signal/src/phone/signaling/CallConnectUtil_Responder.m new file mode 100644 index 000000000..590e805d0 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Responder.m @@ -0,0 +1,154 @@ +#import "CallConnectUtil_Responder.h" + +#import "CallConnectUtil.h" +#import "CallConnectUtil_Server.h" +#import "SignalUtil.h" +#import "UnrecognizedRequestFailure.h" +#import "Util.h" +#import "ZrtpManager.h" + +@implementation CallConnectUtil_Responder + ++(Future*) asyncConnectToIncomingCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor + andCallController:(CallController*)callController { + + require(sessionDescriptor != nil); + require(callController != nil); + require(![callController isInitiator]); + + Future* futureSignalsAreGo = [self asyncConnectToSignalServerDescribedBy:sessionDescriptor + withCallController:callController]; + + Future* futureSignalsAreGoAndCallAccepted = [futureSignalsAreGo then:^(id _) { + [callController advanceCallProgressTo:CallProgressType_Ringing]; + + return [callController interactiveCallAccepted]; + }]; + + return [futureSignalsAreGoAndCallAccepted then:^(id _) { + return [CallConnectUtil_Server asyncConnectCallOverRelayDescribedInResponderSessionDescriptor:sessionDescriptor + withCallController:callController]; + }]; +} + ++(Future*) asyncConnectToSignalServerDescribedBy:(ResponderSessionDescriptor*)sessionDescriptor + withCallController:(CallController*)callController { + require(sessionDescriptor != nil); + require(callController != nil); + + Future* futureSignalConnection = [CallConnectUtil_Server asyncConnectToSignalingServerNamed:sessionDescriptor.relayServerName + untilCancelled:[callController untilCancelledToken]]; + + return [futureSignalConnection then:^id(HttpManager* httpManager) { + require([httpManager isKindOfClass:[httpManager class]]); + + HttpResponse*(^serverRequestHandler)(HttpRequest*) = ^(HttpRequest* remoteRequest) { + return [self respondToServerRequest:remoteRequest + usingDescriptor:sessionDescriptor + andCallController:callController]; + }; + + [httpManager startWithRequestHandler:serverRequestHandler + andErrorHandler:[Environment errorNoter] + untilCancelled:[callController untilCancelledToken]]; + + HttpRequest* ringRequest = [HttpRequest httpRequestToRingWithSessionId:sessionDescriptor.sessionId]; + Future* futureResponseToRing = [httpManager asyncOkResponseForRequest:ringRequest + unlessCancelled:[callController untilCancelledToken]]; + Future* futureResponseToRingWithInterpretedFailures = [futureResponseToRing catch:^(id error) { + if ([error isKindOfClass:[HttpResponse class]]) { + HttpResponse* badResponse = error; + return [Future failed:[self callTerminationForBadResponse:badResponse + toRingRequest:ringRequest]]; + } + + return [Future failed:error]; + }]; + + return [futureResponseToRingWithInterpretedFailures then:^(id _) { + return @YES; + }]; + }]; +} + ++(CallTermination*) callTerminationForBadResponse:(HttpResponse*)badResponse + toRingRequest:(HttpRequest*)ringRequest { + require(badResponse != nil); + require(ringRequest != nil); + + switch ([badResponse getStatusCode]) { + case SIGNAL_STATUS_CODE_STALE_SESSION: + return [CallTermination callTerminationOfType:CallTerminationType_StaleSession + withFailure:badResponse + andMessageInfo:ringRequest]; + case SIGNAL_STATUS_CODE_LOGIN_FAILED: + return [CallTermination callTerminationOfType:CallTerminationType_LoginFailed + withFailure:badResponse + andMessageInfo:ringRequest]; + default: + return [CallTermination callTerminationOfType:CallTerminationType_BadInteractionWithServer + withFailure:badResponse + andMessageInfo:ringRequest]; + } +} ++(HttpResponse*) respondToServerRequest:(HttpRequest*)request + usingDescriptor:(ResponderSessionDescriptor*)responderSessionDescriptor + andCallController:(CallController*)callController { + require(request != nil); + require(responderSessionDescriptor != nil); + require(callController != nil); + + // heart beat? + if ([request isKeepAlive]) { + return [HttpResponse httpResponse200Ok]; + } + + // hangup? + if ([request isHangupForSession:responderSessionDescriptor.sessionId]) { + [callController terminateWithReason:CallTerminationType_HangupRemote + withFailureInfo:nil + andRelatedInfo:request]; + return [HttpResponse httpResponse200Ok]; + } + + // errr...... + [callController terminateWithReason:CallTerminationType_BadInteractionWithServer + withFailureInfo:[UnrecognizedRequestFailure new:@"Didn't understand signaling server."] + andRelatedInfo:request]; + return [HttpResponse httpResponse501NotImplemented]; +} + ++(Future*) asyncSignalTooBusyToAnswerCallWithSessionDescriptor:(ResponderSessionDescriptor*)sessionDescriptor { + require(sessionDescriptor != nil); + + HttpRequest* busyRequest = [HttpRequest httpRequestToSignalBusyWithSessionId:sessionDescriptor.sessionId]; + + return [self asyncOkResponseFor:busyRequest + fromSignalingServerNamed:sessionDescriptor.relayServerName + unlessCancelled:nil + andErrorHandler:[Environment errorNoter]]; +} + ++(Future*) asyncOkResponseFor:(HttpRequest*)request + fromSignalingServerNamed:(NSString*)name + unlessCancelled:(id)unlessCancelledToken + andErrorHandler:(ErrorHandlerBlock)errorHandler { + require(request != nil); + require(errorHandler != nil); + require(name != nil); + + HttpManager* manager = [HttpManager startWithEndPoint:[Environment getSecureEndPointToSignalingServerNamed:name] + untilCancelled:unlessCancelledToken]; + + [manager startWithRejectingRequestHandlerAndErrorHandler:errorHandler + untilCancelled:nil]; + + Future* result = [manager asyncOkResponseForRequest:request + unlessCancelled:unlessCancelledToken]; + + [manager terminateWhenDoneCurrentWork]; + + return result; +} + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil_Server.h b/Signal/src/phone/signaling/CallConnectUtil_Server.h new file mode 100644 index 000000000..f277af6bb --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Server.h @@ -0,0 +1,29 @@ +#import +#import "CallController.h" +#import "InitiatorSessionDescriptor.h" +#import "ResponderSessionDescriptor.h" + +/** + * + * CallConnectUtil_Server is a utility class exposing methods related to connecting to relay/signaling servers. + * + **/ +@interface CallConnectUtil_Server : NSObject + +/// Result has type Future(HttpManager) ++(Future*) asyncConnectToDefaultSignalingServerUntilCancelled:(id)untilCancelledToken; + +/// Result has type Future(HttpManager) ++(Future*) asyncConnectToSignalingServerNamed:(NSString*)name + untilCancelled:(id)untilCancelledToken; + +/// Result has type Future(CallConnectResult) ++(Future*) asyncConnectCallOverRelayDescribedInResponderSessionDescriptor:(ResponderSessionDescriptor*)session + withCallController:(CallController*)callController; + +/// Result has type Future(CallConnectResult) ++(Future*) asyncConnectCallOverRelayDescribedInInitiatorSessionDescriptor:(InitiatorSessionDescriptor*)session + withCallController:(CallController*)callController + andInteropOptions:(NSArray*)interopOptions; + +@end diff --git a/Signal/src/phone/signaling/CallConnectUtil_Server.m b/Signal/src/phone/signaling/CallConnectUtil_Server.m new file mode 100644 index 000000000..3f6b0f695 --- /dev/null +++ b/Signal/src/phone/signaling/CallConnectUtil_Server.m @@ -0,0 +1,203 @@ +#import "CallConnectUtil_Server.h" + +#import "AudioSocket.h" +#import "CallConnectResult.h" +#import "DnsManager.h" +#import "IgnoredPacketFailure.h" +#import "LowLatencyConnector.h" +#import "SignalUtil.h" +#import "UdpSocket.h" +#import "Util.h" +#import "ZrtpManager.h" + +#define BASE_TIMEOUT_SECONDS 1 +#define RETRY_TIMEOUT_FACTOR 2 +#define MAX_TRY_COUNT 5 + +@implementation CallConnectUtil_Server + ++(Future*) asyncConnectToDefaultSignalingServerUntilCancelled:(id)untilCancelledToken { + return [self asyncConnectToSignalingServerAt:[Environment getSecureEndPointToDefaultRelayServer] + untilCancelled:untilCancelledToken]; +} + ++(Future*) asyncConnectToSignalingServerNamed:(NSString*)name + untilCancelled:(id)untilCancelledToken { + require(name != nil); + return [self asyncConnectToSignalingServerAt:[Environment getSecureEndPointToSignalingServerNamed:name] + untilCancelled:untilCancelledToken]; +} + ++(Future*) asyncConnectToSignalingServerAt:(SecureEndPoint*)location + untilCancelled:(id)untilCancelledToken { + require(location != nil); + + Future* futureConnection = [LowLatencyConnector asyncLowLatencyConnectToEndPoint:location + untilCancelled:untilCancelledToken]; + + return [futureConnection then:^(LowLatencyCandidate* result) { + HttpSocket* httpSocket = [HttpSocket httpSocketOver:[result networkStream]]; + return [HttpManager httpManagerFor:httpSocket + untilCancelled:untilCancelledToken]; + }]; +} + + ++(Future*) asyncConnectCallOverRelayDescribedInResponderSessionDescriptor:(ResponderSessionDescriptor*)session + withCallController:(CallController*)callController { + require(session != nil); + require(callController != nil); + + InitiatorSessionDescriptor* equivalentSession = [InitiatorSessionDescriptor initiatorSessionDescriptorWithSessionId:session.sessionId + andRelayServerName:session.relayServerName + andRelayPort:session.relayUdpPort]; + + NSArray* interopOptions = session.interopVersion == 0 + ? @[ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER] + : @[]; + if (session.interopVersion > 1) [Environment errorNoter](@"Newer-than-code interop version specified.", session, false); + + return [self asyncConnectCallOverRelayDescribedInInitiatorSessionDescriptor:equivalentSession + withCallController:callController + andInteropOptions:interopOptions]; +} + ++(Future*) asyncConnectCallOverRelayDescribedInInitiatorSessionDescriptor:(InitiatorSessionDescriptor*)session + withCallController:(CallController*)callController + andInteropOptions:(NSArray*)interopOptions { + require(session != nil); + require(callController != nil); + + Future* futureUdpSocket = [self asyncRepeatedlyAttemptConnectToUdpRelayDescribedBy:session + withCallController:callController]; + + Future* futureZrtpHandshakeResult = [futureUdpSocket then:^(UdpSocket* udpSocket) { + return [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:udpSocket interopOptions:interopOptions] + andCallController:callController]; + }]; + + return [futureZrtpHandshakeResult then:^(ZrtpHandshakeResult* zrtpResult) { + AudioSocket* audioSocket = [AudioSocket audioSocketOver:[zrtpResult secureRtpSocket]]; + + NSString* sas = [[zrtpResult masterSecret] shortAuthenticationString]; + + return [CallConnectResult callConnectResultWithShortAuthenticationString:sas + andAudioSocket:audioSocket]; + }]; +} + ++(Future*) asyncRepeatedlyAttemptConnectToUdpRelayDescribedBy:(InitiatorSessionDescriptor*)sessionDescriptor + withCallController:(CallController*)callController { + + require(sessionDescriptor != nil); + require(callController != nil); + + CancellableOperationStarter operation = ^(id internalUntilCancelledToken) { + return [self asyncAttemptResolveThenConnectToUdpRelayDescribedBy:sessionDescriptor + untilCancelled:internalUntilCancelledToken + withErrorHandler:[callController errorHandler]]; + }; + + Future* futureRelayedUdpSocket = [AsyncUtil asyncTry:operation + upToNTimes:MAX_TRY_COUNT + withBaseTimeout:BASE_TIMEOUT_SECONDS + andRetryFactor:RETRY_TIMEOUT_FACTOR + untilCancelled:[callController untilCancelledToken]]; + + return [futureRelayedUdpSocket catch:^(id error) { + return [Future failed:[CallTermination callTerminationOfType:CallTerminationType_BadInteractionWithServer + withFailure:error + andMessageInfo:@"Timed out on all attempts to contact relay."]]; + }]; +} + ++(Future*) asyncAttemptResolveThenConnectToUdpRelayDescribedBy:(InitiatorSessionDescriptor*)sessionDescriptor + untilCancelled:(id)untilCancelledToken + withErrorHandler:(ErrorHandlerBlock)errorHandler { + + require(sessionDescriptor != nil); + require(errorHandler != nil); + + NSString* domain = [Environment relayServerNameToHostName:[sessionDescriptor relayServerName]]; + + Future* futureDnsResult = [DnsManager asyncQueryAddressesForDomainName:domain + unlessCancelled:untilCancelledToken]; + + Future* futureEndPoint = [futureDnsResult then:^(NSArray* ipAddresses) { + require([ipAddresses count] > 0); + + // @todo: is this the correct way to pick an address (random)? What about low latency? Different each time? dns beforehand? + IpAddress* address = [ipAddresses objectAtIndex:arc4random_uniform([ipAddresses count])]; + return [IpEndPoint ipEndPointAtAddress:address + onPort:sessionDescriptor.relayUdpPort]; + }]; + + return [futureEndPoint then:^(IpEndPoint* remote) { + return [self asyncAttemptConnectToUdpRelayDescribedBy:remote + withSessionId:sessionDescriptor.sessionId + untilCancelled:untilCancelledToken + withErrorHandler:errorHandler]; + }]; +} + ++(Future*) asyncAttemptConnectToUdpRelayDescribedBy:(IpEndPoint*)remoteEndPoint + withSessionId:(int64_t)sessionId + untilCancelled:(id)untilCancelledToken + withErrorHandler:(ErrorHandlerBlock)errorHandler { + + require(remoteEndPoint != nil); + require(errorHandler != nil); + + UdpSocket* udpSocket = [UdpSocket udpSocketTo:remoteEndPoint]; + + id logger = [[Environment logging] getOccurrenceLoggerForSender:self withKey:@"relay setup"]; + + Future* futureFirstResponseData = [self asyncFirstPacketReceivedAfterStartingSocket:udpSocket + untilCancelled:untilCancelledToken + withErrorHandler:errorHandler]; + + Future* futureRelaySocket = [futureFirstResponseData then:^id(NSData* openPortResponseData) { + HttpResponse* openPortResponse = [HttpResponse httpResponseFromData:openPortResponseData]; + [logger markOccurrence:openPortResponse]; + if (![openPortResponse isOkResponse]) return [Future failed:openPortResponse]; + + return udpSocket; + }]; + + HttpRequest* openPortRequest = [HttpRequest httpRequestToOpenPortWithSessionId:sessionId]; + [logger markOccurrence:openPortRequest]; + [udpSocket send:[openPortRequest serialize]]; + + return futureRelaySocket; +} + ++(Future*) asyncFirstPacketReceivedAfterStartingSocket:(UdpSocket*)udpSocket + untilCancelled:(id)untilCancelledToken + withErrorHandler:(ErrorHandlerBlock)errorHandler { + + require(udpSocket != nil); + require(errorHandler != nil); + + FutureSource* futureResultSource = [FutureSource new]; + + [untilCancelledToken whenCancelledTryCancel:futureResultSource]; + + PacketHandlerBlock packetHandler = ^(id packet) { + if (![futureResultSource trySetResult:packet]) {; + errorHandler([IgnoredPacketFailure new:@"Received another packet before relay socket events redirected to new handler."], packet, false); + } + }; + + ErrorHandlerBlock socketErrorHandler = ^(id error, id relatedInfo, bool causedTermination) { + if (causedTermination) [futureResultSource trySetFailure:error]; + errorHandler(error, relatedInfo, causedTermination); + }; + + [udpSocket startWithHandler:[PacketHandler packetHandler:packetHandler + withErrorHandler:socketErrorHandler] + untilCancelled:untilCancelledToken]; + + return futureResultSource; +} + +@end diff --git a/Signal/src/phone/signaling/InitiateSignal.pb.h b/Signal/src/phone/signaling/InitiateSignal.pb.h new file mode 100644 index 000000000..44e0acb82 --- /dev/null +++ b/Signal/src/phone/signaling/InitiateSignal.pb.h @@ -0,0 +1,97 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! + +#import "ProtocolBuffers.h" + +@class InitiateSignal; +@class InitiateSignal_Builder; + +@interface InitiateSignalRoot : NSObject { +} ++ (PBExtensionRegistry*) extensionRegistry; ++ (void) registerAllExtensions:(PBMutableExtensionRegistry*) registry; +@end + +@interface InitiateSignal : PBGeneratedMessage { +@private + BOOL hasSessionId_:1; + BOOL hasInitiator_:1; + BOOL hasServerName_:1; + BOOL hasPort_:1; + BOOL hasVersion_:1; + int64_t sessionId; + NSString* initiator; + NSString* serverName; + int32_t port; + int32_t version; +} +- (BOOL) hasInitiator; +- (BOOL) hasSessionId; +- (BOOL) hasPort; +- (BOOL) hasServerName; +- (BOOL) hasVersion; +@property (readonly, retain) NSString* initiator; +@property (readonly) int64_t sessionId; +@property (readonly) int32_t port; +@property (readonly, retain) NSString* serverName; +@property (readonly) int32_t version; + ++ (InitiateSignal*) defaultInstance; +- (InitiateSignal*) defaultInstance; + +- (BOOL) isInitialized; +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output; +- (InitiateSignal_Builder*) builder; ++ (InitiateSignal_Builder*) builder; ++ (InitiateSignal_Builder*) builderWithPrototype:(InitiateSignal*) prototype; + ++ (InitiateSignal*) parseFromData:(NSData*) data; ++ (InitiateSignal*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (InitiateSignal*) parseFromInputStream:(NSInputStream*) input; ++ (InitiateSignal*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; ++ (InitiateSignal*) parseFromCodedInputStream:(PBCodedInputStream*) input; ++ (InitiateSignal*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; +@end + +@interface InitiateSignal_Builder : PBGeneratedMessage_Builder { +@private + InitiateSignal* result; +} + +- (InitiateSignal*) defaultInstance; + +- (InitiateSignal_Builder*) clear; +- (InitiateSignal_Builder*) clone; + +- (InitiateSignal*) build; +- (InitiateSignal*) buildPartial; + +- (InitiateSignal_Builder*) mergeFrom:(InitiateSignal*) other; +- (InitiateSignal_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input; +- (InitiateSignal_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry; + +- (BOOL) hasInitiator; +- (NSString*) initiator; +- (InitiateSignal_Builder*) setInitiator:(NSString*) value; +- (InitiateSignal_Builder*) clearInitiator; + +- (BOOL) hasSessionId; +- (int64_t) sessionId; +- (InitiateSignal_Builder*) setSessionId:(int64_t) value; +- (InitiateSignal_Builder*) clearSessionId; + +- (BOOL) hasPort; +- (int32_t) port; +- (InitiateSignal_Builder*) setPort:(int32_t) value; +- (InitiateSignal_Builder*) clearPort; + +- (BOOL) hasServerName; +- (NSString*) serverName; +- (InitiateSignal_Builder*) setServerName:(NSString*) value; +- (InitiateSignal_Builder*) clearServerName; + +- (BOOL) hasVersion; +- (int32_t) version; +- (InitiateSignal_Builder*) setVersion:(int32_t) value; +- (InitiateSignal_Builder*) clearVersion; +@end + diff --git a/Signal/src/phone/signaling/InitiateSignal.pb.m b/Signal/src/phone/signaling/InitiateSignal.pb.m new file mode 100644 index 000000000..e1bf35881 --- /dev/null +++ b/Signal/src/phone/signaling/InitiateSignal.pb.m @@ -0,0 +1,350 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! + +#import "InitiateSignal.pb.h" + +@implementation InitiateSignalRoot +static PBExtensionRegistry* extensionRegistry = nil; ++ (PBExtensionRegistry*) extensionRegistry { + return extensionRegistry; +} + ++ (void) initialize { + if (self == [InitiateSignalRoot class]) { + PBMutableExtensionRegistry* registry = [PBMutableExtensionRegistry registry]; + [self registerAllExtensions:registry]; + extensionRegistry = registry; + } +} ++ (void) registerAllExtensions:(PBMutableExtensionRegistry*) registry { +} +@end + +@interface InitiateSignal () +@property (retain) NSString* initiator; +@property int64_t sessionId; +@property int32_t port; +@property (retain) NSString* serverName; +@property int32_t version; +@end + +@implementation InitiateSignal + +- (BOOL) hasInitiator { + return !!hasInitiator_; +} +- (void) setHasInitiator:(BOOL) value { + hasInitiator_ = !!value; +} +@synthesize initiator; +- (BOOL) hasSessionId { + return !!hasSessionId_; +} +- (void) setHasSessionId:(BOOL) value { + hasSessionId_ = !!value; +} +@synthesize sessionId; +- (BOOL) hasPort { + return !!hasPort_; +} +- (void) setHasPort:(BOOL) value { + hasPort_ = !!value; +} +@synthesize port; +- (BOOL) hasServerName { + return !!hasServerName_; +} +- (void) setHasServerName:(BOOL) value { + hasServerName_ = !!value; +} +@synthesize serverName; +- (BOOL) hasVersion { + return !!hasVersion_; +} +- (void) setHasVersion:(BOOL) value { + hasVersion_ = !!value; +} +@synthesize version; +- (void) dealloc { + self.initiator = nil; + self.serverName = nil; +} +- (id) init { + if ((self = [super init])) { + self.initiator = @""; + self.sessionId = 0L; + self.port = 0; + self.serverName = @""; + self.version = 0; + } + return self; +} +static InitiateSignal* defaultInitiateSignalInstance = nil; ++ (void) initialize { + if (self == [InitiateSignal class]) { + defaultInitiateSignalInstance = [[InitiateSignal alloc] init]; + } +} ++ (InitiateSignal*) defaultInstance { + return defaultInitiateSignalInstance; +} +- (InitiateSignal*) defaultInstance { + return defaultInitiateSignalInstance; +} +- (BOOL) isInitialized { + return YES; +} +- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output { + if (self.hasInitiator) { + [output writeString:1 value:self.initiator]; + } + if (self.hasSessionId) { + [output writeUInt64:2 value:self.sessionId]; + } + if (self.hasPort) { + [output writeUInt32:3 value:self.port]; + } + if (self.hasServerName) { + [output writeString:4 value:self.serverName]; + } + if (self.hasVersion) { + [output writeUInt32:5 value:self.version]; + } + [self.unknownFields writeToCodedOutputStream:output]; +} +- (int32_t) serializedSize { + int32_t size = memoizedSerializedSize; + if (size != -1) { + return size; + } + + size = 0; + if (self.hasInitiator) { + size += computeStringSize(1, self.initiator); + } + if (self.hasSessionId) { + size += computeUInt64Size(2, self.sessionId); + } + if (self.hasPort) { + size += computeUInt32Size(3, self.port); + } + if (self.hasServerName) { + size += computeStringSize(4, self.serverName); + } + if (self.hasVersion) { + size += computeUInt32Size(5, self.version); + } + size += self.unknownFields.serializedSize; + memoizedSerializedSize = size; + return size; +} ++ (InitiateSignal*) parseFromData:(NSData*) data { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromData:data] build]; +} ++ (InitiateSignal*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromData:data extensionRegistry:extensionRegistry] build]; +} ++ (InitiateSignal*) parseFromInputStream:(NSInputStream*) input { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromInputStream:input] build]; +} ++ (InitiateSignal*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (InitiateSignal*) parseFromCodedInputStream:(PBCodedInputStream*) input { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromCodedInputStream:input] build]; +} ++ (InitiateSignal*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + return (InitiateSignal*)[[[InitiateSignal builder] mergeFromCodedInputStream:input extensionRegistry:extensionRegistry] build]; +} ++ (InitiateSignal_Builder*) builder { + return [[InitiateSignal_Builder alloc] init]; +} ++ (InitiateSignal_Builder*) builderWithPrototype:(InitiateSignal*) prototype { + return [[InitiateSignal builder] mergeFrom:prototype]; +} +- (InitiateSignal_Builder*) builder { + return [InitiateSignal builder]; +} +@end + +@interface InitiateSignal_Builder() +@property (retain) InitiateSignal* result; +@end + +@implementation InitiateSignal_Builder +@synthesize result; +- (void) dealloc { + self.result = nil; +} +- (id) init { + if ((self = [super init])) { + self.result = [[InitiateSignal alloc] init]; + } + return self; +} +- (PBGeneratedMessage*) internalGetResult { + return result; +} +- (InitiateSignal_Builder*) clear { + self.result = [[InitiateSignal alloc] init]; + return self; +} +- (InitiateSignal_Builder*) clone { + return [InitiateSignal builderWithPrototype:result]; +} +- (InitiateSignal*) defaultInstance { + return [InitiateSignal defaultInstance]; +} +- (InitiateSignal*) build { + [self checkInitialized]; + return [self buildPartial]; +} +- (InitiateSignal*) buildPartial { + InitiateSignal* returnMe = result; + self.result = nil; + return returnMe; +} +- (InitiateSignal_Builder*) mergeFrom:(InitiateSignal*) other { + if (other == [InitiateSignal defaultInstance]) { + return self; + } + if (other.hasInitiator) { + [self setInitiator:other.initiator]; + } + if (other.hasSessionId) { + [self setSessionId:other.sessionId]; + } + if (other.hasPort) { + [self setPort:other.port]; + } + if (other.hasServerName) { + [self setServerName:other.serverName]; + } + if (other.hasVersion) { + [self setVersion:other.version]; + } + [self mergeUnknownFields:other.unknownFields]; + return self; +} +- (InitiateSignal_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input { + return [self mergeFromCodedInputStream:input extensionRegistry:[PBExtensionRegistry emptyRegistry]]; +} +- (InitiateSignal_Builder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry { + PBUnknownFieldSet_Builder* unknownFields = [PBUnknownFieldSet builderWithUnknownFields:self.unknownFields]; + while (YES) { + int32_t tag = [input readTag]; + switch (tag) { + case 0: + [self setUnknownFields:[unknownFields build]]; + return self; + default: { + if (![self parseUnknownField:input unknownFields:unknownFields extensionRegistry:extensionRegistry tag:tag]) { + [self setUnknownFields:[unknownFields build]]; + return self; + } + break; + } + case 10: { + [self setInitiator:[input readString]]; + break; + } + case 16: { + [self setSessionId:[input readUInt64]]; + break; + } + case 24: { + [self setPort:[input readUInt32]]; + break; + } + case 34: { + [self setServerName:[input readString]]; + break; + } + case 40: { + [self setVersion:[input readUInt32]]; + break; + } + } + } +} +- (BOOL) hasInitiator { + return result.hasInitiator; +} +- (NSString*) initiator { + return result.initiator; +} +- (InitiateSignal_Builder*) setInitiator:(NSString*) value { + result.hasInitiator = YES; + result.initiator = value; + return self; +} +- (InitiateSignal_Builder*) clearInitiator { + result.hasInitiator = NO; + result.initiator = @""; + return self; +} +- (BOOL) hasSessionId { + return result.hasSessionId; +} +- (int64_t) sessionId { + return result.sessionId; +} +- (InitiateSignal_Builder*) setSessionId:(int64_t) value { + result.hasSessionId = YES; + result.sessionId = value; + return self; +} +- (InitiateSignal_Builder*) clearSessionId { + result.hasSessionId = NO; + result.sessionId = 0L; + return self; +} +- (BOOL) hasPort { + return result.hasPort; +} +- (int32_t) port { + return result.port; +} +- (InitiateSignal_Builder*) setPort:(int32_t) value { + result.hasPort = YES; + result.port = value; + return self; +} +- (InitiateSignal_Builder*) clearPort { + result.hasPort = NO; + result.port = 0; + return self; +} +- (BOOL) hasServerName { + return result.hasServerName; +} +- (NSString*) serverName { + return result.serverName; +} +- (InitiateSignal_Builder*) setServerName:(NSString*) value { + result.hasServerName = YES; + result.serverName = value; + return self; +} +- (InitiateSignal_Builder*) clearServerName { + result.hasServerName = NO; + result.serverName = @""; + return self; +} +- (BOOL) hasVersion { + return result.hasVersion; +} +- (int32_t) version { + return result.version; +} +- (InitiateSignal_Builder*) setVersion:(int32_t) value { + result.hasVersion = YES; + result.version = value; + return self; +} +- (InitiateSignal_Builder*) clearVersion { + result.hasVersion = NO; + result.version = 0; + return self; +} +@end + diff --git a/Signal/src/phone/signaling/InitiateSignal.proto b/Signal/src/phone/signaling/InitiateSignal.proto new file mode 100644 index 000000000..6d189dec0 --- /dev/null +++ b/Signal/src/phone/signaling/InitiateSignal.proto @@ -0,0 +1,9 @@ +package redphone; + +message InitiateSignal { + optional string initiator = 1; + optional uint64 sessionId = 2; + optional uint32 port = 3; + optional string serverName = 4; + optional uint32 version = 5; +} diff --git a/Signal/src/phone/signaling/InitiatorSessionDescriptor.h b/Signal/src/phone/signaling/InitiatorSessionDescriptor.h new file mode 100644 index 000000000..83e055576 --- /dev/null +++ b/Signal/src/phone/signaling/InitiatorSessionDescriptor.h @@ -0,0 +1,24 @@ +#import +#import "Environment.h" + +/** + * + * The InitiatorSessionDescriptor class stores the information returned by the signaling server when initiating a call. + * It describes which relay server to connect to and what to tell the relay server. + * + */ +@interface InitiatorSessionDescriptor : NSObject + +@property (nonatomic, readonly) in_port_t relayUdpPort; +@property (nonatomic, readonly) int64_t sessionId; +@property (nonatomic, readonly) NSString* relayServerName; + ++(InitiatorSessionDescriptor*) initiatorSessionDescriptorWithSessionId:(int64_t)sessionId + andRelayServerName:(NSString*)relayServerName + andRelayPort:(in_port_t)relayUdpPort; + ++(InitiatorSessionDescriptor*) initiatorSessionDescriptorFromJson:(NSString*)json; + +-(NSString*) toJson; + +@end diff --git a/Signal/src/phone/signaling/InitiatorSessionDescriptor.m b/Signal/src/phone/signaling/InitiatorSessionDescriptor.m new file mode 100644 index 000000000..605645aae --- /dev/null +++ b/Signal/src/phone/signaling/InitiatorSessionDescriptor.m @@ -0,0 +1,57 @@ +#import "InitiatorSessionDescriptor.h" + +#import "Constraints.h" +#import "Util.h" + +#define SessionIdKey @"sessionId" +#define RelayPortKey @"relayPort" +#define RelayHostKey @"serverName" + +@implementation InitiatorSessionDescriptor + +@synthesize relayUdpPort, relayServerName, sessionId; + ++(InitiatorSessionDescriptor*) initiatorSessionDescriptorWithSessionId:(int64_t)sessionId andRelayServerName:(NSString*)relayServerName andRelayPort:(in_port_t)relayUdpPort { + require(relayServerName != nil); + require(relayUdpPort > 0); + InitiatorSessionDescriptor* d = [InitiatorSessionDescriptor new]; + d->sessionId = sessionId; + d->relayServerName = relayServerName; + d->relayUdpPort = relayUdpPort; + return d; +} + ++(InitiatorSessionDescriptor*) initiatorSessionDescriptorFromJson:(NSString*)json { + checkOperation(json != nil); + + NSDictionary* fields = [json decodedAsJsonIntoDictionary]; + id jsonSessionId = [fields objectForKey:SessionIdKey]; + id jsonRelayPort = [fields objectForKey:RelayPortKey]; + id jsonRelayName = [fields objectForKey:RelayHostKey]; + checkOperationDescribe([jsonSessionId isKindOfClass:[NSNumber class]], @"Unexpected json data"); + checkOperationDescribe([jsonRelayPort isKindOfClass:[NSNumber class]], @"Unexpected json data"); + checkOperationDescribe([jsonRelayName isKindOfClass:[NSString class]], @"Unexpected json data"); + checkOperationDescribe([jsonRelayPort unsignedShortValue] > 0, @"Unexpected json data"); + + int64_t sessionId = [[jsonSessionId description] longLongValue]; // workaround: asking for longLongValue directly causes rounding-through-double + in_port_t relayUdpPort = [jsonRelayPort unsignedShortValue]; + return [InitiatorSessionDescriptor initiatorSessionDescriptorWithSessionId:sessionId + andRelayServerName:jsonRelayName + andRelayPort:relayUdpPort]; +} + +-(NSString*) toJson { + return [@{SessionIdKey : [NSNumber numberWithLongLong:sessionId], + RelayPortKey : [NSNumber numberWithUnsignedShort:relayUdpPort], + RelayHostKey : relayServerName + } encodedAsJson]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"relay name: %@, relay port: %d, session id: %llud", + relayServerName, + relayUdpPort, + sessionId]; +} + +@end diff --git a/Signal/src/phone/signaling/ResponderSessionDescriptor.h b/Signal/src/phone/signaling/ResponderSessionDescriptor.h new file mode 100644 index 000000000..35a496b72 --- /dev/null +++ b/Signal/src/phone/signaling/ResponderSessionDescriptor.h @@ -0,0 +1,28 @@ +#import +#import "Environment.h" +#import "InitiatorSessionDescriptor.h" +#import "PhoneNumber.h" + +/** + * + * The ResponderSessionDescriptor class stores the information included in device notifications indicating an incoming call. + * It describes who is calling, which relay server to connect to, and what to tell the relay server. + * + */ +@interface ResponderSessionDescriptor : NSObject + +@property (nonatomic,readonly) int32_t interopVersion; +@property (nonatomic,readonly) in_port_t relayUdpPort; +@property (nonatomic,readonly) int64_t sessionId; +@property (nonatomic,readonly) NSString* relayServerName; +@property (nonatomic,readonly) PhoneNumber* initiatorNumber; + ++(ResponderSessionDescriptor*)responderSessionDescriptorWithInteropVersion:(int32_t)interopVersion + andRelayUdpPort:(in_port_t)relayUdpPort + andSessionId:(int64_t)sessionId + andRelayServerName:(NSString*)relayServerName + andInitiatorNumber:(PhoneNumber*)initiatorNumber; + ++(ResponderSessionDescriptor*)responderSessionDescriptorFromEncryptedRemoteNotification:(NSDictionary*)remoteNotif; + +@end diff --git a/Signal/src/phone/signaling/ResponderSessionDescriptor.m b/Signal/src/phone/signaling/ResponderSessionDescriptor.m new file mode 100644 index 000000000..65dde6f2e --- /dev/null +++ b/Signal/src/phone/signaling/ResponderSessionDescriptor.m @@ -0,0 +1,114 @@ +#import "ResponderSessionDescriptor.h" + +#import "Constraints.h" +#import "CryptoTools.h" +#import "KeyChainStorage.h" +#import "PreferencesUtil.h" +#import "Util.h" +#import "InitiateSignal.pb.h" + +#define MessagePropertyKey @"m" +#define RelayPortKey @"p" +#define SessionIdKey @"s" +#define RelayHostKey @"n" +#define InitiatorNumberKey @"i" + +#define VERSION_SIZE 1 +#define IV_SIZE 16 +#define HMAC_TRUNCATED_SIZE 10 + +#define EXPECTED_REMOTE_NOTIF_FORMAT_VERSION 0 +#define MAX_SUPPORTED_INTEROP_VERSION 1 + +@implementation ResponderSessionDescriptor + +@synthesize relayUdpPort; +@synthesize sessionId; +@synthesize relayServerName; +@synthesize initiatorNumber; +@synthesize interopVersion; + ++(ResponderSessionDescriptor*)responderSessionDescriptorWithInteropVersion:(int32_t)interopVersion + andRelayUdpPort:(in_port_t)relayUdpPort + andSessionId:(int64_t)sessionId + andRelayServerName:(NSString*)relayServerName + andInitiatorNumber:(PhoneNumber*)initiatorNumber { + require(relayUdpPort > 0); + require(relayServerName != nil); + require(initiatorNumber != nil); + + ResponderSessionDescriptor* rsd = [ResponderSessionDescriptor new]; + rsd->interopVersion = interopVersion; + rsd->relayUdpPort = relayUdpPort; + rsd->sessionId = sessionId; + rsd->relayServerName = relayServerName; + rsd->initiatorNumber = initiatorNumber; + + return rsd; +} + ++(ResponderSessionDescriptor*)responderSessionDescriptorFromEncryptedRemoteNotification:(NSDictionary*)remoteNotif { + require(remoteNotif != nil); + + NSString* message = [remoteNotif objectForKey:MessagePropertyKey]; + checkOperation(message != nil); + NSData* authenticatedPayload = [message decodedAsBase64Data]; + + checkOperation([authenticatedPayload length] > 0); + uint8_t includedRemoteNotificationFormatVersion = [authenticatedPayload uint8At:0]; + checkOperation(includedRemoteNotificationFormatVersion == EXPECTED_REMOTE_NOTIF_FORMAT_VERSION); + + NSData* encryptedPayload = [self verifyAndRemoveMacFromRemoteNotifcationData:authenticatedPayload]; + NSData* payload = [self decryptRemoteNotificationData:encryptedPayload]; + InitiateSignal* parsedPayload = [InitiateSignal parseFromData:payload]; + + in_port_t maxPort = (in_port_t)-1; + assert(maxPort > 0); + checkOperation(parsedPayload.version >= 0); + checkOperation(parsedPayload.version <= MAX_SUPPORTED_INTEROP_VERSION); + checkOperation(parsedPayload.sessionId >= 0); + checkOperation(parsedPayload.port > 0 && parsedPayload.port <= maxPort); + checkOperation(parsedPayload.initiator != nil); + checkOperation(parsedPayload.serverName != nil); + + int32_t interopVersion = parsedPayload.version; + int64_t sessionId = parsedPayload.sessionId; + in_port_t relayUdpPort = (in_port_t)parsedPayload.port; + NSString* relayServerName = parsedPayload.serverName; + PhoneNumber* phoneNumber = [PhoneNumber phoneNumberFromE164:parsedPayload.initiator]; + + return [ResponderSessionDescriptor responderSessionDescriptorWithInteropVersion:interopVersion + andRelayUdpPort:relayUdpPort + andSessionId:sessionId + andRelayServerName:relayServerName + andInitiatorNumber:phoneNumber]; +} ++(NSData*) verifyAndRemoveMacFromRemoteNotifcationData:(NSData*)data { + require(data != nil); + checkOperation([data length] >= HMAC_TRUNCATED_SIZE); + NSData* includedMac = [data takeLast:HMAC_TRUNCATED_SIZE]; + NSData* payload = [data skipLast:HMAC_TRUNCATED_SIZE]; + NSData* signalingMacKey = [KeyChainStorage getOrGenerateSignalingMacKey]; + NSData* computedMac = [[payload hmacWithSha1WithKey:signalingMacKey] takeLast:HMAC_TRUNCATED_SIZE]; + checkOperation([includedMac isEqualToData_TimingSafe:computedMac]); + return payload; +} ++(NSData*) decryptRemoteNotificationData:(NSData*)data { + require(data != nil); + checkOperation([data length] >= VERSION_SIZE + IV_SIZE); + NSData* cipherKey = [KeyChainStorage getOrGenerateSignalingCipherKey]; + NSData* iv = [data subdataWithRange:NSMakeRange(VERSION_SIZE, IV_SIZE)]; + NSData* cipherText = [data skip:VERSION_SIZE+IV_SIZE]; + return [cipherText decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:cipherKey andIv:iv]; +} + +-(NSString*) description { + return [NSString stringWithFormat:@"relay name: %@, relay port: %d, session id: %llud, initiator phone number: %@, interop version: %d", + relayServerName, + relayUdpPort, + sessionId, + initiatorNumber, + interopVersion]; +} + +@end diff --git a/Signal/src/phone/signaling/SignalUtil.h b/Signal/src/phone/signaling/SignalUtil.h new file mode 100644 index 000000000..6712955e7 --- /dev/null +++ b/Signal/src/phone/signaling/SignalUtil.h @@ -0,0 +1,39 @@ +#import +#import "HttpManager.h" +#import "HttpRequestUtil.h" +#import "PhoneNumberDirectoryFilter.h" + +#define SIGNAL_STATUS_CODE_STALE_SESSION 404 +#define SIGNAL_STATUS_CODE_NO_SUCH_USER 404 +#define SIGNAL_STATUS_CODE_SERVER_MESSAGE 402 +#define SIGNAL_STATUS_CODE_LOGIN_FAILED 401 + +@interface HttpRequest(SignalUtil) + +-(bool) isKeepAlive; + +-(bool) isRingingForSession:(int64_t)targetSessionId; + +-(bool) isHangupForSession:(int64_t)targetSessionId; + +-(bool) isBusyForSession:(int64_t)targetSessionId; + ++(HttpRequest*) httpRequestToOpenPortWithSessionId:(int64_t)sessionId; + ++(HttpRequest*) httpRequestToInitiateToRemoteNumber:(PhoneNumber*)remoteNumber; + ++(HttpRequest*) httpRequestToRingWithSessionId:(int64_t)sessionId; + ++(HttpRequest*) httpRequestToSignalBusyWithSessionId:(int64_t)sessionId; + ++(HttpRequest*) httpRequestToStartRegistrationOfPhoneNumber; + ++(HttpRequest*) httpRequestToStartRegistrationOfPhoneNumberWithVoice; + ++(HttpRequest*) httpRequestToVerifyAccessToPhoneNumberWithChallenge:(NSString*)challenge; + ++(HttpRequest*) httpRequestToRegisterForApnSignalingWithDeviceToken:(NSData*)deviceToken; + ++(HttpRequest*) httpRequestForPhoneNumberDirectoryFilter; + +@end diff --git a/Signal/src/phone/signaling/SignalUtil.m b/Signal/src/phone/signaling/SignalUtil.m new file mode 100644 index 000000000..d21b8e507 --- /dev/null +++ b/Signal/src/phone/signaling/SignalUtil.m @@ -0,0 +1,110 @@ +#import "SignalUtil.h" + +#import "Constraints.h" +#import "Environment.h" +#import "KeyChainStorage.h" +#import "PreferencesUtil.h" +#import "Util.h" + +#define CLAIMED_INTEROP_VERSION_IN_INITIATE_SIGNAL 1 + +/** + * + * Augments HttpRequest with utility methods related to interacting with signaling servers. + * + */ +@implementation HttpRequest(SignalUtil) + +-(NSNumber*) tryGetSessionId { + if (![self.location hasPrefix:@"/session/"]) return nil; + + NSString* sessionIdText = [self.location substringFromIndex:[@"/session/" length]]; + sessionIdText = [sessionIdText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSNumber* sessionIdNumber = [sessionIdText tryParseAsDecimalNumber]; + + if ([sessionIdNumber hasLongLongValue]) return sessionIdNumber; + + return nil; +} + +-(bool) isKeepAlive { + return [self.method isEqualToString:@"GET"] && [self.location hasPrefix:@"/keepalive"]; +} + +-(bool) isRingingForSession:(int64_t)targetSessionId { + return [self.method isEqualToString:@"RING"] && [[NSNumber numberWithLongLong:targetSessionId] isEqualToNumber:[self tryGetSessionId]]; +} + +-(bool) isHangupForSession:(int64_t)targetSessionId { + return [self.method isEqualToString:@"DELETE"] && [[NSNumber numberWithLongLong:targetSessionId] isEqualToNumber:[self tryGetSessionId]]; +} + +-(bool) isBusyForSession:(int64_t)targetSessionId { + return [self.method isEqualToString:@"BUSY"] && [[NSNumber numberWithLongLong:targetSessionId] isEqualToNumber:[self tryGetSessionId]]; +} + ++(HttpRequest*) httpRequestToOpenPortWithSessionId:(int64_t)sessionId { + return [HttpRequest httpRequestUnauthenticatedWithMethod:@"GET" + andLocation:[NSString stringWithFormat:@"/open/%lld", sessionId]]; +} ++(HttpRequest*) httpRequestToRingWithSessionId:(int64_t)sessionId { + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:@"RING" + andLocation:[NSString stringWithFormat:@"/session/%lld", sessionId]]; +} ++(HttpRequest*) httpRequestToSignalBusyWithSessionId:(int64_t)sessionId { + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:@"BUSY" + andLocation:[NSString stringWithFormat:@"/session/%lld", sessionId]]; +} ++(HttpRequest*) httpRequestToInitiateToRemoteNumber:(PhoneNumber*)remoteNumber { + require(remoteNumber != nil); + + NSString* formattedRemoteNumber = [remoteNumber toE164]; + NSString* interopVersionInsert = CLAIMED_INTEROP_VERSION_IN_INITIATE_SIGNAL == 0 + ? @"" + : [NSString stringWithFormat:@"/%d", CLAIMED_INTEROP_VERSION_IN_INITIATE_SIGNAL]; + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:@"GET" + andLocation:[NSString stringWithFormat:@"/session%@/%@", + interopVersionInsert, + formattedRemoteNumber]]; +} ++(HttpRequest*) httpRequestToStartRegistrationOfPhoneNumber { + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:@"GET" + andLocation:@"/users/verification"]; +} + ++(HttpRequest*) httpRequestToStartRegistrationOfPhoneNumberWithVoice { + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:@"GET" + andLocation:@"/users/verification/voice"]; +} + ++(HttpRequest*) httpRequestToVerifyAccessToPhoneNumberWithChallenge:(NSString*)challenge { + require(challenge != nil); + + PhoneNumber* localPhoneNumber = [KeyChainStorage forceGetLocalNumber]; + NSString* query = [NSString stringWithFormat:@"/users/verification/%@", [localPhoneNumber toE164]]; + + NSData* signalingCipherKey = [KeyChainStorage getOrGenerateSignalingMacKey]; + NSData* signalingMacKey = [KeyChainStorage getOrGenerateSignalingMacKey]; + NSData* signalingExtraKeyData = [KeyChainStorage getOrGenerateSignalingExtraKey]; + NSString* encodedSignalingKey = [[@[signalingCipherKey, signalingMacKey, signalingExtraKeyData] concatDatas] encodedAsBase64]; + NSString* body = [@{@"key" : encodedSignalingKey, @"challenge" : challenge} encodedAsJson]; + + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:@"PUT" + andLocation:query + andOptionalBody:body]; +} ++(HttpRequest*) httpRequestToRegisterForApnSignalingWithDeviceToken:(NSData*)deviceToken { + require(deviceToken != nil); + + NSString* query = [NSString stringWithFormat:@"/apn/%@", [deviceToken encodedAsHexString]]; + + return [HttpRequest httpRequestWithBasicAuthenticationAndMethod:@"PUT" + andLocation:query]; +} + ++(HttpRequest*) httpRequestForPhoneNumberDirectoryFilter { + return [HttpRequest httpRequestWithOtpAuthenticationAndMethod:@"GET" + andLocation:@"/users/directory"]; +} + +@end diff --git a/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.h b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.h new file mode 100644 index 000000000..5205a9274 --- /dev/null +++ b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.h @@ -0,0 +1,26 @@ +#import +#import "BloomFilter.h" +#import "PhoneNumber.h" +#import "HttpResponse.h" + +/** + * + * PhoneNumberDirectoryFilter matches numbers-to-be-called against a bloom filter that determines if those numbers are red phone compatible. + * The bloom filter expires periodically, and must be updated from the whispersystem servers. + * + */ +@interface PhoneNumberDirectoryFilter : NSObject { +@private NSDate* expirationDate; +} + +@property (nonatomic,readonly) BloomFilter* bloomFilter; + ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterDefault; ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterWithBloomFilter:(BloomFilter*)bloomFilter + andExpirationDate:(NSDate*)expirationDate; ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterFromHttpResponse:(HttpResponse*)response; + +-(bool) containsPhoneNumber:(PhoneNumber*)phoneNumber; +-(NSDate*) getExpirationDate; + +@end diff --git a/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.m b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.m new file mode 100644 index 000000000..4da3528d2 --- /dev/null +++ b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilter.m @@ -0,0 +1,65 @@ +#import "PhoneNumberDirectoryFilter.h" +#import "Environment.h" +#import "Constraints.h" +#import "PreferencesUtil.h" + +#define HASH_COUNT_HEADER_KEY @"X-Hash-Count" +#define MIN_NEW_EXPIRATION_SECONDS (12 * 60 * 60) +#define MAX_EXPIRATION_SECONDS (24 * 60 * 60) + +@implementation PhoneNumberDirectoryFilter + +@synthesize bloomFilter; + ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterDefault { + return [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterWithBloomFilter:[BloomFilter bloomFilterWithNothing] + andExpirationDate:[NSDate date]]; +} ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterWithBloomFilter:(BloomFilter*)bloomFilter + andExpirationDate:(NSDate*)expirationDate { + require(bloomFilter != nil); + require(expirationDate != nil); + PhoneNumberDirectoryFilter* newInstance = [PhoneNumberDirectoryFilter new]; + newInstance->bloomFilter = bloomFilter; + newInstance->expirationDate = expirationDate; + return newInstance; +} + +-(NSDate*) getExpirationDate { + NSDate* currentDate = [NSDate date]; + NSDate* maxExpiryDate = [NSDate dateWithTimeInterval:MAX_EXPIRATION_SECONDS sinceDate:currentDate]; + expirationDate = [expirationDate earlierDate:maxExpiryDate]; + return expirationDate; +} + ++(PhoneNumberDirectoryFilter*) phoneNumberDirectoryFilterFromHttpResponse:(HttpResponse*)response { + require(response != nil); + + checkOperation([response isOkResponse]); + + NSString* hashCountHeader = [[response getHeaders] objectForKey:HASH_COUNT_HEADER_KEY]; + checkOperation(hashCountHeader != nil); + + int hashCountValue = [hashCountHeader integerValue]; + checkOperation(hashCountValue > 0); + + NSData* responseBody = [response getOptionalBodyData]; + checkOperation([responseBody length] > 0); + + BloomFilter* bloomFilter = [BloomFilter bloomFilterWithHashCount:(NSUInteger)hashCountValue + andData:responseBody]; + + NSTimeInterval expirationDuration = MIN_NEW_EXPIRATION_SECONDS + + arc4random_uniform(MAX_EXPIRATION_SECONDS - MIN_NEW_EXPIRATION_SECONDS); + NSDate* expirationDate = [NSDate dateWithTimeInterval:expirationDuration sinceDate:[NSDate date]]; + + return [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterWithBloomFilter:bloomFilter + andExpirationDate:expirationDate]; +} + +-(bool) containsPhoneNumber:(PhoneNumber*)phoneNumber { + if (phoneNumber == nil) return false; + return [bloomFilter contains:[phoneNumber toE164]]; +} + +@end diff --git a/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.h b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.h new file mode 100644 index 000000000..fdf3d47a0 --- /dev/null +++ b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.h @@ -0,0 +1,20 @@ +#import +#import "SignalUtil.h" +#import "CancelToken.h" + +/** + * + * PhoneNumberDirectoryFilterManager is responsible for periodically downloading the latest + * bloom filter containing phone numbers considered to have RedPhone support. + * + */ +@interface PhoneNumberDirectoryFilterManager : NSObject { +@private PhoneNumberDirectoryFilter* phoneNumberDirectoryFilter; +@private id lifetimeToken; +} + +-(void) forceUpdate; +-(void) startUntilCancelled:(id)cancelToken; +-(PhoneNumberDirectoryFilter*) getCurrentFilter; + +@end diff --git a/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.m b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.m new file mode 100644 index 000000000..2024ae1dd --- /dev/null +++ b/Signal/src/phone/signaling/number directory/PhoneNumberDirectoryFilterManager.m @@ -0,0 +1,112 @@ +#import "PhoneNumberDirectoryFilterManager.h" +#import "Environment.h" +#import "PreferencesUtil.h" +#import "ThreadManager.h" +#import "Util.h" +#import "NotificationManifest.h" + +#define MINUTE (60.0) +#define HOUR (MINUTE*60.0) + +#define DIRECTORY_UPDATE_TIMEOUT_PERIOD (1.0*MINUTE) +#define DIRECTORY_UPDATE_RETRY_PERIOD (1.0*HOUR) + +@implementation PhoneNumberDirectoryFilterManager { +@private CancelTokenSource* currentUpdateLifetime; +} + +-(id) init { + if (self = [super init]) { + phoneNumberDirectoryFilter = [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterDefault]; + } + return self; +} +-(void) startUntilCancelled:(id)cancelToken { + lifetimeToken = cancelToken; + + phoneNumberDirectoryFilter = [[[Environment getCurrent] preferences] tryGetSavedPhoneNumberDirectory]; + if (phoneNumberDirectoryFilter == nil) { + phoneNumberDirectoryFilter = [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterDefault]; + } + + [self scheduleUpdate]; +} + +-(PhoneNumberDirectoryFilter*) getCurrentFilter { + @synchronized(self) { + return phoneNumberDirectoryFilter; + } +} +-(void)forceUpdate { + [self scheduleUpdateAt:NSDate.date]; +} +-(void) scheduleUpdate { + return [self scheduleUpdateAt:self.getCurrentFilter.getExpirationDate]; +} +-(void) scheduleUpdateAt:(NSDate*)date { + void(^doUpdate)(void) = ^{ + [self update]; + }; + + [currentUpdateLifetime cancel]; + currentUpdateLifetime = [CancelTokenSource cancelTokenSource]; + [lifetimeToken whenCancelled:^{ [currentUpdateLifetime cancel]; }]; + [TimeUtil scheduleRun:doUpdate + at:date + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:currentUpdateLifetime.getToken]; +} + +-(Future*) asyncQueryCurrentDirectory { + CancellableOperationStarter startAwaitDirectoryOperation = ^(id untilCancelledToken) { + HttpRequest* directoryRequest = [HttpRequest httpRequestForPhoneNumberDirectoryFilter]; + + Future* futureDirectoryResponse = [HttpManager asyncOkResponseFromMasterServer:directoryRequest + unlessCancelled:untilCancelledToken + andErrorHandler:[Environment errorNoter]]; + + return [futureDirectoryResponse then:^(HttpResponse* response) { + return [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterFromHttpResponse:response]; + }]; + }; + + return [AsyncUtil raceCancellableOperation:startAwaitDirectoryOperation + againstTimeout:DIRECTORY_UPDATE_TIMEOUT_PERIOD + untilCancelled:lifetimeToken]; +} + +-(PhoneNumberDirectoryFilter*) sameDirectoryWithRetryTimeout { + BloomFilter* filter = [phoneNumberDirectoryFilter bloomFilter]; + NSDate* retryDate = [NSDate dateWithTimeInterval:DIRECTORY_UPDATE_RETRY_PERIOD + sinceDate:[NSDate date]]; + return [PhoneNumberDirectoryFilter phoneNumberDirectoryFilterWithBloomFilter:filter + andExpirationDate:retryDate]; +} +-(void) signalDirectoryQueryFailed:(id)failure { + NSString* desc = [NSString stringWithFormat:@"Failed to retrieve directory. Retrying in %f hours.", + DIRECTORY_UPDATE_RETRY_PERIOD/HOUR]; + [Environment errorNoter](desc, failure, false); +} +-(Future*) asyncQueryCurrentDirectoryWithDefaultOnFail { + Future* futureDirectory = [self asyncQueryCurrentDirectory]; + + return [futureDirectory catch:^PhoneNumberDirectoryFilter*(id error) { + [self signalDirectoryQueryFailed:error]; + return [self sameDirectoryWithRetryTimeout]; + }]; +} + +-(void) update { + Future* eventualDirectory = [self asyncQueryCurrentDirectoryWithDefaultOnFail]; + + [eventualDirectory thenDo:^(PhoneNumberDirectoryFilter* directory) { + @synchronized(self) { + phoneNumberDirectoryFilter = directory; + } + [[Environment preferences] setSavedPhoneNumberDirectory:directory]; + [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_DIRECTORY_WAS_UPDATED object:nil]; + [self scheduleUpdate]; + }]; +} + +@end diff --git a/Signal/src/profiling/CategorizingLogger.h b/Signal/src/profiling/CategorizingLogger.h new file mode 100644 index 000000000..030ff487b --- /dev/null +++ b/Signal/src/profiling/CategorizingLogger.h @@ -0,0 +1,13 @@ +#import +#import "JitterQueue.h" +#import "Logging.h" + +@interface CategorizingLogger : NSObject { +@private NSMutableArray* callbacks; +@private NSMutableDictionary* indexDic; +} + ++(CategorizingLogger*) categorizingLogger; +-(void) addLoggingCallback:(void(^)(NSString* category, id details, NSUInteger index))callback; + +@end diff --git a/Signal/src/profiling/CategorizingLogger.m b/Signal/src/profiling/CategorizingLogger.m new file mode 100644 index 000000000..860b60fe8 --- /dev/null +++ b/Signal/src/profiling/CategorizingLogger.m @@ -0,0 +1,78 @@ +#import "CategorizingLogger.h" +#import "AnonymousOccurrenceLogger.h" +#import "AnonymousConditionLogger.h" +#import "AnonymousValueLogger.h" +#import "LoggingUtil.h" + +@implementation CategorizingLogger + ++(CategorizingLogger*) categorizingLogger { + CategorizingLogger* c = [CategorizingLogger new]; + c->callbacks = [NSMutableArray array]; + c->indexDic = [NSMutableDictionary dictionary]; + return c; +} +-(void) addLoggingCallback:(void(^)(NSString* category, id details, NSUInteger index))callback { + [callbacks addObject:[callback copy]]; +} + +-(void) log:(NSString*)category details:(id)details { + NSNumber* index = [indexDic objectForKey:category]; + if (index == nil) { + index = [NSNumber numberWithUnsignedInteger:[indexDic count]]; + [indexDic setObject:index forKey:category]; + } + NSUInteger x = [index unsignedIntegerValue]; + for (void (^callback)(NSString* category, id details, NSUInteger index) in callbacks) { + callback(category, details, x); + } +} + +-(id) getValueLoggerForValue:(id)valueIdentity from:(id)sender { + id r = [AnonymousValueLogger anonymousValueLogger:^(double value) { + [self log:[NSString stringWithFormat:@"Value %@ from %@", valueIdentity, sender] details:[NSNumber numberWithDouble:value]]; + }]; + return [LoggingUtil throttleValueLogger:r discardingAfterEventForDuration:0.5]; +} +-(id) getOccurrenceLoggerForSender:(id)sender withKey:(NSString*)key { + id r = [AnonymousOccurrenceLogger anonymousOccurencyLoggerWithMarker:^(id details){ + [self log:[NSString stringWithFormat:@"Mark %@ from %@", key, sender] details:details]; + }]; + return [LoggingUtil throttleOccurrenceLogger:r discardingAfterEventForDuration:0.5]; +} +-(id) getConditionLoggerForSender:(id)sender { + return [AnonymousConditionLogger anonymousConditionLoggerWithLogNotice:^(NSString *text) { + [self log:[NSString stringWithFormat:@"Notice from %@", sender] details:text]; + } andLogWarning:^(NSString *text) { + [self log:[NSString stringWithFormat:@"Warning from %@", sender] details:text]; + } andLogError:^(NSString *text) { + [self log:[NSString stringWithFormat:@"Error from %@", sender] details:text]; + }]; +} +-(id) jitterQueueNotificationReceiver { + return self; +} + +-(void) notifyCreated { + [self log:@"JitterQueue created" details:nil]; +} +-(void) notifyArrival:(uint16_t)sequenceNumber { + [self log:@"JitterQueue arrival" details:[NSString stringWithFormat:@"sequence: %d", sequenceNumber]]; +} +-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount { + [self log:@"JitterQueue dequeue" details:[NSString stringWithFormat:@"sequence: %d, remaining: %d", sequenceNumber, remainingCount]]; +} +-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType { + [self log:@"JitterQueue bad arrival" details:[NSString stringWithFormat:@"sequence: %d, arrival type: %d", sequenceNumber, arrivalType]]; +} +-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type { + [self log:@"JitterQueue bad dequeue" details:[NSString stringWithFormat:@"type: %d", type]]; +} +-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { + [self log:@"JitterQueue resync" details:[NSString stringWithFormat:@"from: %d, to: %d", oldReadHeadSequenceNumber, newReadHeadSequenceNumber]]; +} +-(void) notifyDiscardOverflow:(uint16_t)discardedSequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { + [self log:@"JitterQueue discard overflow" details:[NSString stringWithFormat:@"discarded: %d, from: %d, to: %d", discardedSequenceNumber, oldReadHeadSequenceNumber, newReadHeadSequenceNumber]]; +} + +@end diff --git a/Signal/src/profiling/DecayingSampleEstimator.h b/Signal/src/profiling/DecayingSampleEstimator.h new file mode 100644 index 000000000..a78323964 --- /dev/null +++ b/Signal/src/profiling/DecayingSampleEstimator.h @@ -0,0 +1,21 @@ +#import + +/// A sample estimate based on an exponential weighting of observed samples, favoring the latest samples. +@interface DecayingSampleEstimator : NSObject { +@private double estimate; +@private double decayPerUnitSample; +} + ++(DecayingSampleEstimator*) decayingSampleEstimatorWithInitialEstimate:(double)initialEstimate andDecayPerUnitSample:(double)decayPerUnitSample; ++(DecayingSampleEstimator*) decayingSampleEstimatorWithInitialEstimate:(double)initialEstimate andDecayFactor:(double)decayFactor perNSamples:(double)decayPeriod; + +/// Decays the current estimate towards the given sample value, assuming a unit weighting. +-(void) updateWithNextSample:(double)sampleValue; +/// Decays the current estimate towards the given sample value, with a given weighting. +-(void) updateWithNextSample:(double)sampleValue withSampleWeight:(double)weight; + +-(double) currentEstimate; +-(double) decayRatePerUnitSample; +-(void) forceEstimateTo:(double)newEstimate; + +@end diff --git a/Signal/src/profiling/DecayingSampleEstimator.m b/Signal/src/profiling/DecayingSampleEstimator.m new file mode 100644 index 000000000..c5d054929 --- /dev/null +++ b/Signal/src/profiling/DecayingSampleEstimator.m @@ -0,0 +1,44 @@ +#import "DecayingSampleEstimator.h" +#import "Constraints.h" + +@implementation DecayingSampleEstimator + ++(DecayingSampleEstimator*) decayingSampleEstimatorWithInitialEstimate:(double)initialEstimate andDecayPerUnitSample:(double)decayPerUnitSample { + require(decayPerUnitSample >= 0); + require(decayPerUnitSample <= 1); + DecayingSampleEstimator* d = [DecayingSampleEstimator new]; + d->estimate = initialEstimate; + d->decayPerUnitSample = decayPerUnitSample; + return d; +} ++(DecayingSampleEstimator*) decayingSampleEstimatorWithInitialEstimate:(double)initialEstimate andDecayFactor:(double)decayFactor perNSamples:(double)decayPeriod { + require(decayFactor >= 0); + require(decayFactor <= 1); + require(decayPeriod > 0); + double decayPerUnitSample = 1 - pow(1 - decayFactor, 1/decayPeriod); + return [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:initialEstimate andDecayPerUnitSample:decayPerUnitSample]; +} + +-(void) updateWithNextSample:(double)sampleValue { + estimate *= 1 - decayPerUnitSample; + estimate += sampleValue * decayPerUnitSample; +} +-(void) updateWithNextSample:(double)sampleValue withSampleWeight:(double)weight { + require(weight >= 0); + if (weight == 0) return; + + double decayPerWeightedSample = 1 - pow(1 - decayPerUnitSample, weight); + estimate *= 1 - decayPerWeightedSample; + estimate += sampleValue * decayPerWeightedSample; +} +-(double) currentEstimate { + return estimate; +} +-(void) forceEstimateTo:(double)newEstimate { + estimate = newEstimate; +} +-(double) decayRatePerUnitSample { + return decayPerUnitSample; +} + +@end diff --git a/Signal/src/profiling/EventWindow.h b/Signal/src/profiling/EventWindow.h new file mode 100644 index 000000000..05046b06c --- /dev/null +++ b/Signal/src/profiling/EventWindow.h @@ -0,0 +1,14 @@ +#import +#import "PriorityQueue.h" + +@interface EventWindow : NSObject { +@private NSTimeInterval windowDuration; +@private PriorityQueue* events; +@private NSTimeInterval lastWindowEnding; +} + ++(EventWindow*) eventWindowWithWindowDuration:(NSTimeInterval)windowDuration; +-(void) addEventAtTime:(NSTimeInterval)eventTime; +-(NSUInteger) countAfterRemovingEventsBeforeWindowEndingAt:(NSTimeInterval)endOfWindowTime; + +@end diff --git a/Signal/src/profiling/EventWindow.m b/Signal/src/profiling/EventWindow.m new file mode 100644 index 000000000..1b3a32032 --- /dev/null +++ b/Signal/src/profiling/EventWindow.m @@ -0,0 +1,36 @@ +#import "EventWindow.h" +#import "Util.h" + +@implementation EventWindow + ++(EventWindow*) eventWindowWithWindowDuration:(NSTimeInterval)windowDuration { + require(windowDuration >= 0); + + EventWindow* w = [EventWindow new]; + w->windowDuration = windowDuration; + w->events = [PriorityQueue priorityQueueAscendingWithComparator:^NSComparisonResult(id obj1, id obj2) { + return [(NSNumber*)obj1 compare:(NSNumber*)obj2]; + }]; + w->lastWindowEnding = -INFINITY; + return w; +} + +-(void) addEventAtTime:(NSTimeInterval)eventTime { + [events enqueue:[NSNumber numberWithDouble:eventTime]]; +} + +-(NSUInteger) countAfterRemovingEventsBeforeWindowEndingAt:(NSTimeInterval)endOfWindowTime { + // because values are removed, going backwards will give misleading results. + // checking for this case so callers don't get silent bad results + // includes a small leeway in case of non-monotonic time source or extended precision lose + requireState(endOfWindowTime >= lastWindowEnding-0.03); + lastWindowEnding = endOfWindowTime; + + NSTimeInterval startOfWindowTime = endOfWindowTime - windowDuration; + while([events count] > 0 && [[events peek] doubleValue] < startOfWindowTime) { + [events dequeue]; + } + return [events count]; +} + +@end diff --git a/Signal/src/profiling/LoggingUtil.h b/Signal/src/profiling/LoggingUtil.h new file mode 100644 index 000000000..d0050dd28 --- /dev/null +++ b/Signal/src/profiling/LoggingUtil.h @@ -0,0 +1,15 @@ +#import +#import "Logging.h" +#import "DecayingSampleEstimator.h" + +@interface LoggingUtil : NSObject + ++(id) throttleValueLogger:(id)valueLogger discardingAfterEventForDuration:(NSTimeInterval)duration; ++(id) throttleOccurrenceLogger:(id)occurrenceLogger discardingAfterEventForDuration:(NSTimeInterval)duration; ++(id) getAccumulatingValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender; ++(id) getDifferenceValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender; ++(id) getAveragingValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender; ++(id) getValueEstimateLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender withEstimator:(DecayingSampleEstimator*)estimator; ++(id) getMagnitudeDecayingToZeroValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender withDecayFactor:(double)decayFactorPerSample; + +@end diff --git a/Signal/src/profiling/LoggingUtil.m b/Signal/src/profiling/LoggingUtil.m new file mode 100644 index 000000000..17f1ae8b8 --- /dev/null +++ b/Signal/src/profiling/LoggingUtil.m @@ -0,0 +1,81 @@ +#import "LoggingUtil.h" +#import "AnonymousValueLogger.h" +#import "AnonymousOccurrenceLogger.h" +#import "Constraints.h" +#import "TimeUtil.h" + +@implementation LoggingUtil + ++(id) throttleValueLogger:(id)valueLogger discardingAfterEventForDuration:(NSTimeInterval)duration { + __block NSTimeInterval t = [TimeUtil time] - duration; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + double t2 = [TimeUtil time]; + if (t2 - duration < t) return; + t = t2; + + [valueLogger logValue:value]; + }]; +} ++(id) throttleOccurrenceLogger:(id)occurrenceLogger discardingAfterEventForDuration:(NSTimeInterval)duration { + __block NSTimeInterval t = [TimeUtil time] - duration; + return [AnonymousOccurrenceLogger anonymousOccurencyLoggerWithMarker:^(id details) { + double t2 = [TimeUtil time]; + if (t2 - duration < t) return; + t = t2; + + [occurrenceLogger markOccurrence:details]; + }]; +} + ++(id) getAccumulatingValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender { + __block double total = 0.0; + id norm = [logging getValueLoggerForValue:valueIdentity from:sender]; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + total += value; + [norm logValue:total]; + }]; +} ++(id) getDifferenceValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender { + __block double previous = 0.0; + __block bool hasPrevious = false; + id norm = [logging getValueLoggerForValue:valueIdentity from:sender]; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + double d = value - previous; + previous = value; + if (hasPrevious) { + [norm logValue:d]; + } + hasPrevious = true; + }]; +} ++(id) getAveragingValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender { + __block double total = 0.0; + __block NSUInteger count = 0; + id norm = [logging getValueLoggerForValue:valueIdentity from:sender]; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + total += value; + count += 1; + [norm logValue:total/count]; + }]; +} ++(id) getValueEstimateLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender withEstimator:(DecayingSampleEstimator*)estimator { + require(estimator != nil); + id norm = [logging getValueLoggerForValue:valueIdentity from:sender]; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + [estimator updateWithNextSample:value]; + [norm logValue:[estimator currentEstimate]]; + }]; +} ++(id) getMagnitudeDecayingToZeroValueLoggerTo:(id)logging named:(id)valueIdentity from:(id)sender withDecayFactor:(double)decayFactorPerSample { + require(decayFactorPerSample <= 1); + require(decayFactorPerSample >= 0); + __block double decayingEstimate = 0.0; + id norm = [logging getValueLoggerForValue:valueIdentity from:sender]; + return [AnonymousValueLogger anonymousValueLogger:^(double value) { + value = ABS(value); + decayingEstimate = MAX(value, decayingEstimate*decayFactorPerSample); + [norm logValue:decayingEstimate]; + }]; +} + +@end diff --git a/Signal/src/profiling/protocols/ConditionLogger.h b/Signal/src/profiling/protocols/ConditionLogger.h new file mode 100644 index 000000000..3c2424cea --- /dev/null +++ b/Signal/src/profiling/protocols/ConditionLogger.h @@ -0,0 +1,7 @@ +#import + +@protocol ConditionLogger +-(void) logNotice:(id)details; +-(void) logWarning:(id)details; +-(void) logError:(id)details; +@end diff --git a/Signal/src/profiling/protocols/Logging.h b/Signal/src/profiling/protocols/Logging.h new file mode 100644 index 000000000..9fc52713c --- /dev/null +++ b/Signal/src/profiling/protocols/Logging.h @@ -0,0 +1,20 @@ +#import +#import "OccurrenceLogger.h" +#import "ConditionLogger.h" +#import "ValueLogger.h" + +@protocol JitterQueueNotificationReceiver; + +@protocol Logging + +/// Note: the logger MUST NOT store a reference to the given sender. Calling this method, or storing its result, must not create a reference cycle. +-(id) getOccurrenceLoggerForSender:(id)sender withKey:(NSString*)key; + +/// Note: the logger MUST NOT store a reference to the given sender. Calling this method, or storing its result, must not create a reference cycle. +-(id) getConditionLoggerForSender:(id)sender; + +/// Note: the logger MUST NOT store a reference to the given sender. Calling this method, or storing its result, must not create a reference cycle. +-(id) getValueLoggerForValue:(id)valueIdentity from:(id)sender; + +-(id) jitterQueueNotificationReceiver; +@end diff --git a/Signal/src/profiling/protocols/OccurrenceLogger.h b/Signal/src/profiling/protocols/OccurrenceLogger.h new file mode 100644 index 000000000..ad3a17557 --- /dev/null +++ b/Signal/src/profiling/protocols/OccurrenceLogger.h @@ -0,0 +1,5 @@ +#import + +@protocol OccurrenceLogger +-(void) markOccurrence:(id)details; +@end diff --git a/Signal/src/profiling/protocols/ValueLogger.h b/Signal/src/profiling/protocols/ValueLogger.h new file mode 100644 index 000000000..c2fda5fd1 --- /dev/null +++ b/Signal/src/profiling/protocols/ValueLogger.h @@ -0,0 +1,7 @@ +#import + +@protocol ValueLogger + +-(void) logValue:(double)value; + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.h b/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.h new file mode 100644 index 000000000..40409f791 --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.h @@ -0,0 +1,12 @@ +#import +#import "ConditionLogger.h" + +@interface AnonymousConditionLogger : NSObject + +@property (nonatomic,readonly,copy) void (^logNoticeBlock)(id details); +@property (nonatomic,readonly,copy) void (^logWarningBlock)(id details); +@property (nonatomic,readonly,copy) void (^logErrorBlock)(id details); + ++(AnonymousConditionLogger*) anonymousConditionLoggerWithLogNotice:(void(^)(id details))logNotice andLogWarning:(void(^)(id details))logWarning andLogError:(void(^)(id details))logError; + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.m b/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.m new file mode 100644 index 000000000..ae6bfefd0 --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousConditionLogger.m @@ -0,0 +1,28 @@ +#import "AnonymousConditionLogger.h" +#import "Constraints.h" + +@implementation AnonymousConditionLogger + ++(AnonymousConditionLogger*) anonymousConditionLoggerWithLogNotice:(void(^)(id details))logNotice andLogWarning:(void(^)(id details))logWarning andLogError:(void(^)(id details))logError { + require(logNotice != nil); + require(logWarning != nil); + require(logError != nil); + + AnonymousConditionLogger* a = [AnonymousConditionLogger new]; + a->_logErrorBlock = logError; + a->_logWarningBlock = logWarning; + a->_logNoticeBlock = logNotice; + return a; +} + +-(void) logError:(id)details { + _logErrorBlock(details); +} +-(void) logWarning:(id)details { + _logWarningBlock(details); +} +-(void) logNotice:(id)details { + _logNoticeBlock(details); +} + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.h b/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.h new file mode 100644 index 000000000..43092e17c --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.h @@ -0,0 +1,10 @@ +#import +#import "OccurrenceLogger.h" + +@interface AnonymousOccurrenceLogger : NSObject + +@property (readonly,nonatomic,copy) void (^marker)(id details); + ++(AnonymousOccurrenceLogger*) anonymousOccurencyLoggerWithMarker:(void(^)(id details))marker; + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.m b/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.m new file mode 100644 index 000000000..8d1fbf644 --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousOccurrenceLogger.m @@ -0,0 +1,17 @@ +#import "AnonymousOccurrenceLogger.h" +#import "Constraints.h" + +@implementation AnonymousOccurrenceLogger + ++(AnonymousOccurrenceLogger*) anonymousOccurencyLoggerWithMarker:(void(^)(id details))marker { + require(marker != nil); + AnonymousOccurrenceLogger* a = [AnonymousOccurrenceLogger new]; + a->_marker = marker; + return a; +} + +-(void) markOccurrence:(id)details { + _marker(details); +} + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.h b/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.h new file mode 100644 index 000000000..8127a078e --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.h @@ -0,0 +1,10 @@ +#import +#import "ValueLogger.h" + +@interface AnonymousValueLogger : NSObject + +@property (nonatomic,readonly,copy) void (^logValueBlock)(double value); + ++(AnonymousValueLogger*) anonymousValueLogger:(void(^)(double value))logValue; + +@end diff --git a/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.m b/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.m new file mode 100644 index 000000000..4f8e90a0b --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/AnonymousValueLogger.m @@ -0,0 +1,17 @@ +#import "AnonymousValueLogger.h" +#import "Constraints.h" + +@implementation AnonymousValueLogger + ++(AnonymousValueLogger*) anonymousValueLogger:(void(^)(double value))logValue { + require(logValue != nil); + AnonymousValueLogger* a = [AnonymousValueLogger new]; + a->_logValueBlock = logValue; + return a; +} + +-(void) logValue:(double)value { + _logValueBlock(value); +} + +@end diff --git a/Signal/src/profiling/protocols/utilities/DiscardingLog.h b/Signal/src/profiling/protocols/utilities/DiscardingLog.h new file mode 100644 index 000000000..4e7b90015 --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/DiscardingLog.h @@ -0,0 +1,8 @@ +#import +#import "Logging.h" +#import "ConditionLogger.h" +#import "JitterQueue.h" + +@interface DiscardingLog : NSObject ++(DiscardingLog*) discardingLog; +@end diff --git a/Signal/src/profiling/protocols/utilities/DiscardingLog.m b/Signal/src/profiling/protocols/utilities/DiscardingLog.m new file mode 100644 index 000000000..175eeedcb --- /dev/null +++ b/Signal/src/profiling/protocols/utilities/DiscardingLog.m @@ -0,0 +1,44 @@ +#import "DiscardingLog.h" + +@implementation DiscardingLog ++(DiscardingLog*) discardingLog { + return [DiscardingLog new]; +} + +-(id) getOccurrenceLoggerForSender:(id)sender withKey:(NSString *)key { + return self; +} +-(id) getConditionLoggerForSender:(id)sender { + return self; +} +-(id) jitterQueueNotificationReceiver { + return self; +} +-(id) getValueLoggerForValue:(id)valueIdentity from:(id)sender { + return self; +} + +-(void) logValue:(double)value { +} +-(void) markOccurrence:(id)details { +} +-(void) logError:(NSString *)text { +} +-(void) logNotice:(NSString *)text { +} +-(void) logWarning:(NSString *)text { +} +-(void) notifyArrival:(uint16_t)sequenceNumber { +} +-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount { +} +-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType { +} +-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type { +} +-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { +} +-(void) notifyDiscardOverflow:(uint16_t)discardedSequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber { +} + +@end diff --git a/Signal/src/storage/KeychainWrapper.h b/Signal/src/storage/KeychainWrapper.h new file mode 100644 index 000000000..2d5cb1c02 --- /dev/null +++ b/Signal/src/storage/KeychainWrapper.h @@ -0,0 +1,24 @@ +#import +#import +#import + +@interface KeychainWrapper : NSObject + +// Generic exposed method to search the keychain for a given value. Limit one result per search. ++ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier; + +// Calls searchKeychainCopyMatchingIdentifier: and converts to a string value. ++ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier; + +// Default initializer to store a value in the keychain. +// Associated properties are handled for you - setting Data Protection Access, Company Identifer (to uniquely identify string, etc). ++ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier; + +// Updates a value in the keychain. If you try to set the value with createKeychainValue: and it already exists, +// this method is called instead to update the value in place. ++ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier; + +// Delete a value in the keychain. ++ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier; + +@end diff --git a/Signal/src/storage/KeychainWrapper.m b/Signal/src/storage/KeychainWrapper.m new file mode 100644 index 000000000..9ed18be09 --- /dev/null +++ b/Signal/src/storage/KeychainWrapper.m @@ -0,0 +1,100 @@ +#import "KeychainWrapper.h" + +@implementation KeychainWrapper + ++ (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier { + NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init]; + [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; + NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; + NSString* appName = [infoDict objectForKey:@"CFBundleDisplayName"]; + [searchDictionary setObject:appName forKey:(__bridge id)kSecAttrService]; + + NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding]; + [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric]; + [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount]; + + return searchDictionary; +} + ++ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier { + + NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier]; + [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; + + [searchDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData]; + + NSData *result = nil; + CFTypeRef foundDict = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &foundDict); + + if (status == noErr) { + result = (__bridge_transfer NSData *)foundDict; + } else { + result = nil; + } + + return result; +} + ++ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier { + NSData *valueData = [self searchKeychainCopyMatchingIdentifier:identifier]; + if (valueData) { + NSString *value = [[NSString alloc] initWithData:valueData + encoding:NSUTF8StringEncoding]; + return value; + } + else { + return nil; + } +} + + ++ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier { + + NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier]; + NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; + [dictionary setObject:valueData forKey:(__bridge id)kSecValueData]; + + // Protect the keychain entry so it's only valid when the device is unlocked. + [dictionary setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible]; + + // Add. + OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL); + + // If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO). + if (status == errSecSuccess) { + return YES; + } + else if (status == errSecDuplicateItem){ + return [self updateKeychainValue:value forIdentifier:identifier]; + } + else { + return NO; + } +} + ++ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier { + + NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier]; + NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init]; + NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; + [updateDictionary setObject:valueData forKey:(__bridge id)kSecValueData]; + + OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary, + (__bridge CFDictionaryRef)updateDictionary); + + if (status == errSecSuccess) { + return YES; + } + else { + return NO; + } +} + ++ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier { + NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier]; + CFDictionaryRef dictionary = (__bridge CFDictionaryRef)searchDictionary; + SecItemDelete(dictionary); +} + +@end diff --git a/Signal/src/util/ArrayUtil.h b/Signal/src/util/ArrayUtil.h new file mode 100644 index 000000000..81ef47054 --- /dev/null +++ b/Signal/src/util/ArrayUtil.h @@ -0,0 +1,7 @@ +#import + +@interface NSArray (Util) +-(NSData*) toUint8Data; +-(NSData*) concatDatas; +-(NSArray*) concatArrays; +@end diff --git a/Signal/src/util/ArrayUtil.m b/Signal/src/util/ArrayUtil.m new file mode 100644 index 000000000..16c7b3203 --- /dev/null +++ b/Signal/src/util/ArrayUtil.m @@ -0,0 +1,38 @@ +#import "ArrayUtil.h" +#import "Constraints.h" + +@implementation NSArray (Util) + +-(NSData*) toUint8Data { + NSUInteger n = [self count]; + uint8_t x[n]; + for (NSUInteger i = 0; i < n; i++) { + x[i] = [(NSNumber*)[self objectAtIndex:i] unsignedCharValue]; + } + return [NSData dataWithBytes:x length:n]; +} +-(NSData*) concatDatas { + NSUInteger t = 0; + for (id d in self) { + require([d isKindOfClass:[NSData class]]); + t += [(NSData*)d length]; + } + + NSMutableData* result = [NSMutableData dataWithLength:t]; + uint8_t* dst = [result mutableBytes]; + for (NSData* d in self) { + memcpy(dst, [d bytes], [d length]); + dst += [d length]; + } + return result; +} +-(NSArray*) concatArrays { + NSMutableArray* r = [NSMutableArray array]; + for (id e in self) { + require([e isKindOfClass:[NSArray class]]); + [r addObjectsFromArray:e]; + } + return r; +} + +@end diff --git a/Signal/src/util/BloomFilter.h b/Signal/src/util/BloomFilter.h new file mode 100644 index 000000000..04e57e15e --- /dev/null +++ b/Signal/src/util/BloomFilter.h @@ -0,0 +1,25 @@ +#import + +/** + * + * A bloom filter allows a set of items to be represented compactly, at the cost of false-positives when checking membership. + * When contains returns true, the given item may be in the set. + * When contains returns false, the given item is definitely not in the set. + * + * Bloom filters are used to opportunistically avoid starting an expensive operation that always fails for items not in a set. + * In the specific case of RedPhone, it is used to determine if a phone number can be called (i.e. is in the RedPhone directory). + * + */ +@interface BloomFilter : NSObject + +@property (nonatomic,readonly) NSUInteger hashCount; +@property (nonatomic,readonly) NSData* data; + ++(BloomFilter*) bloomFilterWithHashCount:(NSUInteger)hashCount + andData:(NSData*)data; ++(BloomFilter*) bloomFilterWithNothing; ++(BloomFilter*) bloomFilterWithEverything; + +-(bool) contains:(NSString*)entity; + +@end diff --git a/Signal/src/util/BloomFilter.m b/Signal/src/util/BloomFilter.m new file mode 100644 index 000000000..052c374de --- /dev/null +++ b/Signal/src/util/BloomFilter.m @@ -0,0 +1,66 @@ +#import "BloomFilter.h" +#import "Constraints.h" +#import "Util.h" +#import "CryptoTools.h" +#import "Conversions.h" + +@implementation BloomFilter + +@synthesize hashCount, data; + ++(BloomFilter*) bloomFilterWithNothing { + return [BloomFilter bloomFilterWithHashCount:1 andData:[NSMutableData dataWithLength:1]]; +} + ++(BloomFilter*) bloomFilterWithEverything { + NSMutableData* data = [NSMutableData dataWithLength:1]; + [data setUint8At:0 to:0xFF]; + return [BloomFilter bloomFilterWithHashCount:1 andData:data]; +} + ++(BloomFilter*) bloomFilterWithHashCount:(NSUInteger)hashCount + andData:(NSData*)data { + require(hashCount > 0); + require(data != nil); + + BloomFilter* result = [BloomFilter new]; + result->hashCount = hashCount; + result->data = data; + return result; +} + +-(uint32_t) hash:(NSData*)value index:(NSUInteger)index { + NSData* key = [[[NSNumber numberWithUnsignedInteger:index] stringValue] encodedAsAscii]; + NSData* hash = [value hmacWithSha1WithKey:key]; + return [hash bigEndianUInt32At:0] % ([data length] * 8); +} + +-(bool) isBitSetAt:(uint32_t)bitIndex { + uint32_t byteIndex = bitIndex / 8; + uint8_t bitMask = (uint8_t)(1 << (bitIndex % 8)); + return ([data uint8At:byteIndex] & bitMask) != 0; +} + +-(bool) contains:(NSString*)entity { + require(entity != nil); + NSData* value = [entity encodedAsUtf8]; + for (NSUInteger i = 0; i < hashCount; i++) { + uint32_t bitIndex = [self hash:value index:i]; + if (![self isBitSetAt:bitIndex]) { + return false; + } + } + return true; +} + +-(NSString*) description { + if (data.length == 1 && [data uint8At:0] == 0xFF) { + return @"Everything (degenerate bloom filter)"; + } + if (data.length == 1 && [data uint8At:0] == 0) { + return @"Nothing (degenerate bloom filter)"; + } + return [super description]; +} + +@end diff --git a/Signal/src/util/Conversions.h b/Signal/src/util/Conversions.h new file mode 100644 index 000000000..65c786beb --- /dev/null +++ b/Signal/src/util/Conversions.h @@ -0,0 +1,12 @@ +#import +#import +#import "Constraints.h" +#import "CryptoTools.h" + +@interface NSData (Conversions) +-(uint16_t) bigEndianUInt16At:(NSUInteger)offset; +-(uint32_t) bigEndianUInt32At:(NSUInteger)offset; ++(NSData*) dataWithBigEndianBytesOfUInt16:(uint16_t)value; ++(NSData*) dataWithBigEndianBytesOfUInt32:(uint32_t)value; ++(NSData*) switchEndiannessOfData:(NSData*)data; +@end diff --git a/Signal/src/util/Conversions.m b/Signal/src/util/Conversions.m new file mode 100644 index 000000000..0097bb0ad --- /dev/null +++ b/Signal/src/util/Conversions.m @@ -0,0 +1,44 @@ +#import "Conversions.h" +#import "Util.h" +#import "CryptoTools.h" + +@implementation NSData (Conversions) + +-(uint16_t) bigEndianUInt16At:(NSUInteger)offset { + require(offset <= [self length]-sizeof(uint16_t)); + return (uint16_t)[self uint8At:1+offset] + | (uint16_t)((uint16_t)[self uint8At:0+offset] << 8); +} +-(uint32_t) bigEndianUInt32At:(NSUInteger)offset { + require(offset <= [self length]-sizeof(uint32_t)); + return ((uint32_t)[self uint8At:3+offset] << 0) + | ((uint32_t)[self uint8At:2+offset] << 8) + | ((uint32_t)[self uint8At:1+offset] << 16) + | ((uint32_t)[self uint8At:0+offset] << 24); +} + ++(NSData*) dataWithBigEndianBytesOfUInt16:(uint16_t)value { + uint8_t d[sizeof(uint16_t)]; + d[1] = (uint8_t)((value >> 0) & 0xFF); + d[0] = (uint8_t)((value >> 8) & 0xFF); + return [NSData dataWithBytes:d length:sizeof(uint16_t)]; +} ++(NSData*) dataWithBigEndianBytesOfUInt32:(uint32_t)value { + uint8_t d[sizeof(uint32_t)]; + d[3] = (uint8_t)((value >> 0) & 0xFF); + d[2] = (uint8_t)((value >> 8) & 0xFF); + d[1] = (uint8_t)((value >> 16) & 0xFF); + d[0] = (uint8_t)((value >> 24) & 0xFF); + return [NSData dataWithBytes:d length:sizeof(uint32_t)]; +} ++(NSData*) switchEndiannessOfData:(NSData*)data{ + const void* bytes = [data bytes]; + NSMutableData* switchedEndianData = [NSMutableData new]; + for (NSUInteger i = [data length]; i > 0; --i){ + uint8_t byte = *(((uint8_t*)(bytes))+((i-1)*sizeof(uint8_t))); + [switchedEndianData appendData:[NSData dataWithBytes:&byte length:sizeof(byte)]]; + } + return switchedEndianData; +} + +@end diff --git a/Signal/src/util/Crc32.h b/Signal/src/util/Crc32.h new file mode 100644 index 000000000..cecc6f6e3 --- /dev/null +++ b/Signal/src/util/Crc32.h @@ -0,0 +1,7 @@ +#import + +@interface NSData (CRC) + +-(uint32_t) crc32; + +@end diff --git a/Signal/src/util/Crc32.m b/Signal/src/util/Crc32.m new file mode 100644 index 000000000..a4d8cedb4 --- /dev/null +++ b/Signal/src/util/Crc32.m @@ -0,0 +1,44 @@ +#import "Crc32.h" + +#define DEFAULT_POLYNOMIAL 0xEDB88320L +#define DEFAULT_SEED 0xFFFFFFFFL + +void generateCRC32Table(uint32_t *pTable, uint32_t poly); + +@implementation NSData (CRC) + +void generateCRC32Table(uint32_t *pTable, uint32_t poly) { + for (uint32_t i = 0; i <= 255; i++) { + uint32_t crc = i; + + for (uint32_t j = 8; j > 0; j--) { + if ((crc & 1) == 1) + crc = (crc >> 1) ^ poly; + else + crc >>= 1; + } + pTable[i] = crc; + } +} + +-(uint32_t)crc32 { + return [self crc32WithSeed:DEFAULT_SEED usingPolynomial:DEFAULT_POLYNOMIAL]; +} + +-(uint32_t)crc32WithSeed:(uint32_t)seed usingPolynomial:(uint32_t)poly { + uint32_t *pTable = malloc(sizeof(uint32_t) * 256); + generateCRC32Table(pTable, poly); + + uint32_t crc = seed; + uint8_t *pBytes = (uint8_t *)[self bytes]; + uint32_t length = [self length]; + + while (length--) { + crc = (crc>>8) ^ pTable[(crc & 0xFF) ^ *pBytes++]; + } + + free(pTable); + return crc ^ 0xFFFFFFFFL; +} + +@end diff --git a/Signal/src/util/DataUtil.h b/Signal/src/util/DataUtil.h new file mode 100644 index 000000000..b0e0ca3be --- /dev/null +++ b/Signal/src/util/DataUtil.h @@ -0,0 +1,77 @@ +#import + +@interface NSData (Util) + +-(NSString*) encodedAsHexString; + +-(const void*) bytesNotNull; + ++(NSData*) dataWithLength:(NSUInteger)length; + ++(NSData*) dataWithSingleByte:(uint8_t)value; + +/// Decodes the data as a utf-8 string. +-(NSString*) decodedAsUtf8; + +/// Decodes the data as an ascii string. +/// Throws when the data contains non-ascii character data (bytes larger than 127). +-(NSString*) decodedAsAscii; + +/// Decodes the data as an ascii string. +/// Replaces any bad or non-printable characters with dots. +-(NSString*) decodedAsAsciiReplacingErrorsWithDots; + +/// Finds the first index where the given sub data is present. +/// Returns nil if there is no such index. +-(NSNumber*) tryFindIndexOf:(NSData*)subData; + +-(NSData*) skip:(NSUInteger)offset; + +-(NSData*) take:(NSUInteger)takeCount; + +-(NSData*) skipLast:(NSUInteger)skipLastCount; + +-(NSData*) takeLast:(NSUInteger)takeLastCount; + +/// Returns an NSData referencing a subrange of another NSData. +/// Modifying the original NSData will modify the result. +/// If the original is dealloced before the result, bad things happen to you. +-(NSData*) subdataVolatileWithRange:(NSRange)range; + +/// Returns an NSData referencing the end of another NSData. +/// Modifying the original NSData will modify the result. +/// If the original is dealloced before the result, bad things happen to you. +-(NSData*) skipVolatile:(NSUInteger)offset; + +/// Returns an NSData referencing the start of another NSData. +/// Modifying the original NSData will modify the result. +/// If the original is dealloced before the result, bad things happen to you. +-(NSData*) takeVolatile:(NSUInteger)takeCount; + +/// Returns an NSData referencing the start of another NSData. +/// Modifying the original NSData will modify the result. +/// If the original is dealloced before the result, bad things happen to you. +-(NSData*) skipLastVolatile:(NSUInteger)skipLastCount; + +/// Returns an NSData referencing the end of another NSData. +/// Modifying the original NSData will modify the result. +/// If the original is dealloced before the result, bad things happen to you. +-(NSData*) takeLastVolatile:(NSUInteger)takeLastCount; + +-(uint8_t) uint8At:(NSUInteger)offset; + +-(uint8_t) highUint4AtByteOffset:(NSUInteger)offset; + +-(uint8_t) lowUint4AtByteOffset:(NSUInteger)offset; + +-(NSString*) encodedAsBase64; + +@end + +@interface NSMutableData (Util) + +-(void) replaceBytesStartingAt:(NSUInteger)offset withData:(NSData*)data; + +-(void) setUint8At:(NSUInteger)offset to:(uint8_t)newValue; + +@end diff --git a/Signal/src/util/DataUtil.m b/Signal/src/util/DataUtil.m new file mode 100644 index 000000000..751154396 --- /dev/null +++ b/Signal/src/util/DataUtil.m @@ -0,0 +1,191 @@ +#import "DataUtil.h" +#import "Constraints.h" + +@implementation NSData (Util) +-(const void*) bytesNotNull { + // note: this storage location is static, not auto, so its lifetime does not end + // (also, by virtue of being const, there are no threading/entrancy issues) + static const int SafeNonNullPointerToStaticStorageLocation[1]; + + if ([self length] == 0) { + return SafeNonNullPointerToStaticStorageLocation; + } else { + require([self bytes] != nil); + return [self bytes]; + } +} ++(NSData*) dataWithLength:(NSUInteger)length { + return [NSMutableData dataWithLength:length]; +} + ++(NSData*) dataWithSingleByte:(uint8_t)value{ + return [NSData dataWithBytes:&value length:sizeof(value)]; +} +-(NSNumber*) tryFindIndexOf:(NSData*)subData { + require(subData != nil); + if ([subData length] > [self length]) return nil; + + NSUInteger subDataLength = [subData length]; + NSUInteger excessLength = [self length] - subDataLength; + + const uint8_t* selfBytes = [self bytes]; + const uint8_t* subDataBytes = [subData bytes]; + for (NSUInteger i = 0; i <= excessLength; i++) { + if (memcmp(selfBytes+i, subDataBytes, subDataLength) == 0) { + return [NSNumber numberWithUnsignedInteger:i]; + } + } + return nil; +} +-(NSString*) encodedAsHexString { + if (![self bytes]) return @""; + + NSMutableString* result = [NSMutableString string]; + for (NSUInteger i = 0; i < [self length]; ++i) + [result appendString:[NSString stringWithFormat:@"%02x", [self uint8At:i]]]; + + return result; +} +-(NSString*) decodedAsUtf8 { + // workaround for empty data having nil bytes + if ([self length] == 0) return @""; + + [NSString stringWithUTF8String:[self bytes]]; + NSString* result = [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding]; + checkOperationDescribe(result != nil, @"Invalid UTF8 data."); + return result; +} +-(NSString*) decodedAsAscii { + // workaround for empty data having nil bytes + if ([self length] == 0) return @""; + // workaround for initWithData not enforcing the fact that NSASCIIStringEncoding means strict 7-bit + for (NSUInteger i = 0; i < [self length]; i++) { + checkOperationDescribe(([self uint8At:i] & 0x80) == 0, @"Invalid ascii data."); + } + + NSString* result = [[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding]; + checkOperationDescribe(result != nil, @"Invalid ascii data."); + return result; +} +-(NSString*) decodedAsAsciiReplacingErrorsWithDots { + const int MinPrintableChar = ' '; + const int MaxPrintableChar = '~'; + + NSMutableData* d = [NSMutableData dataWithLength:[self length]]; + for (NSUInteger i = 0; i < [self length]; i++) { + uint8_t v = [self uint8At:i]; + if (v < MinPrintableChar || v > MaxPrintableChar) v = '.'; + [d setUint8At:i to:v]; + } + return [d decodedAsAscii]; +} + +-(NSData*) skip:(NSUInteger)offset { + require(offset <= [self length]); + return [self subdataWithRange:NSMakeRange(offset, [self length] - offset)]; +} +-(NSData*) take:(NSUInteger)takeCount { + require(takeCount <= [self length]); + return [self subdataWithRange:NSMakeRange(0, takeCount)]; +} +-(NSData*) skipLast:(NSUInteger)skipLastCount { + require(skipLastCount <= [self length]); + return [self subdataWithRange:NSMakeRange(0, [self length] - skipLastCount)]; +} +-(NSData*) takeLast:(NSUInteger)takeLastCount { + require(takeLastCount <= [self length]); + return [self subdataWithRange:NSMakeRange([self length] - takeLastCount, takeLastCount)]; +} + +-(NSData*) subdataVolatileWithRange:(NSRange)range { + NSUInteger length = [self length]; + require(range.location <= length); + require(range.length <= length); + require(range.location + range.length <= length); + + return [NSData dataWithBytesNoCopy:(uint8_t*)[self bytes] + range.location length:range.length freeWhenDone:NO]; +} +-(NSData*) skipVolatile:(NSUInteger)offset { + require(offset <= [self length]); + return [self subdataVolatileWithRange:NSMakeRange(offset, [self length] - offset)]; +} +-(NSData*) takeVolatile:(NSUInteger)takeCount { + require(takeCount <= [self length]); + return [self subdataVolatileWithRange:NSMakeRange(0, takeCount)]; +} +-(NSData*) skipLastVolatile:(NSUInteger)skipLastCount { + require(skipLastCount <= [self length]); + return [self subdataVolatileWithRange:NSMakeRange(0, [self length] - skipLastCount)]; +} +-(NSData*) takeLastVolatile:(NSUInteger)takeLastCount { + require(takeLastCount <= [self length]); + return [self subdataVolatileWithRange:NSMakeRange([self length] - takeLastCount, takeLastCount)]; +} + +-(uint8_t) highUint4AtByteOffset:(NSUInteger)offset { + return [self uint8At:offset] >> 4; +} +-(uint8_t) lowUint4AtByteOffset:(NSUInteger)offset { + return [self uint8At:offset] & 0xF; +} +-(uint8_t) uint8At:(NSUInteger)offset { + require(offset < [self length]); + return ((const uint8_t*)[self bytes])[offset]; +} +-(const uint8_t*) constPtrToUint8At:(NSUInteger)offset { + return ((uint8_t*)[self bytes]) + offset; +} +-(NSString*) encodedAsBase64 { + const NSUInteger BitsPerBase64Word = 6; + const NSUInteger BitsPerByte = 8; + const uint8_t Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + NSUInteger byteCount = [self length]; + NSUInteger bitCount = byteCount*BitsPerByte; + NSUInteger base64WordCount = bitCount / BitsPerBase64Word; + if (base64WordCount * BitsPerBase64Word < bitCount) base64WordCount += 1; + + // base 256 to to base 2 + bool bits[bitCount]; + for (NSUInteger i = 0; i < byteCount; i++) { + for (NSUInteger j = 0; j < BitsPerByte; j++) { + bits[i*BitsPerByte + BitsPerByte - 1 - j] = (([self uint8At:i] >> j) & 1) != 0; + } + } + + // base 2 to base 64 + uint8_t base64Words[base64WordCount]; + for (NSUInteger i = 0; i < base64WordCount; i++) { + base64Words[i] = 0; + for (NSUInteger j = 0; j < BitsPerBase64Word; j++) { + NSUInteger offset = i*BitsPerBase64Word + BitsPerBase64Word - 1 - j; + if (offset >= bitCount) continue; // default to 0 + if (bits[offset]) base64Words[i] |= 1 << j; + } + } + + // base 64 to ASCII data + NSUInteger paddingCount = bitCount % 3; + NSMutableData* asciiData = [NSMutableData dataWithLength:base64WordCount+paddingCount]; + for (NSUInteger i = 0; i < base64WordCount; i++) { + [asciiData setUint8At:i to:Base64Chars[base64Words[i]]]; + } + for (NSUInteger i = 0; i < paddingCount; i++) { + [asciiData setUint8At:i+base64WordCount to:'=']; + } + + return [asciiData decodedAsAscii]; +} +@end + +@implementation NSMutableData (Util) +-(void) setUint8At:(NSUInteger)offset to:(uint8_t)newValue { + require(offset < [self length]); + ((uint8_t*)[self mutableBytes])[offset] = newValue; +} +-(void) replaceBytesStartingAt:(NSUInteger)offset withData:(NSData*)data { + require(data != nil); + require(offset + [data length] <= [self length]); + [self replaceBytesInRange:NSMakeRange(offset, [data length]) withBytes:[data bytes]]; +} +@end diff --git a/Signal/src/util/DateUtil.h b/Signal/src/util/DateUtil.h new file mode 100644 index 000000000..72526ebb6 --- /dev/null +++ b/Signal/src/util/DateUtil.h @@ -0,0 +1,11 @@ +#import + +@interface DateUtil : NSObject + ++ (NSDateFormatter *)dateFormatter; ++ (NSDateFormatter *)weekdayFormatter; ++ (NSDateFormatter *)timeFormatter; ++ (BOOL)dateIsOlderThanOneDay:(NSDate *)date; ++ (BOOL)dateIsOlderThanOneWeek:(NSDate *)date; + +@end diff --git a/Signal/src/util/DateUtil.m b/Signal/src/util/DateUtil.m new file mode 100644 index 000000000..be77b7438 --- /dev/null +++ b/Signal/src/util/DateUtil.m @@ -0,0 +1,39 @@ +#import "DateUtil.h" +#import "Environment.h" +#import "PreferencesUtil.h" + +#define ONE_DAY_TIME_INTERVAL (double)60*60*24 +#define ONE_WEEK_TIME_INTERVAL (double)60*60*24*7 + +static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE"; +static NSString *const DATE_FORMAT_HOUR_MINUTE = @"h:mm a "; + +@implementation DateUtil + ++ (NSDateFormatter *)dateFormatter { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:[[[Environment getCurrent] preferences] getDateFormat]]; +return formatter; +} + ++ (NSDateFormatter *)weekdayFormatter { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:DATE_FORMAT_WEEKDAY]; +return formatter; +} + ++ (NSDateFormatter *)timeFormatter { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:DATE_FORMAT_HOUR_MINUTE]; +return formatter; +} + ++ (BOOL)dateIsOlderThanOneDay:(NSDate *)date { + return [[NSDate date] timeIntervalSinceDate:date] > ONE_DAY_TIME_INTERVAL; +} + ++ (BOOL)dateIsOlderThanOneWeek:(NSDate *)date { + return [[NSDate date] timeIntervalSinceDate:date] > ONE_WEEK_TIME_INTERVAL; +} + +@end diff --git a/Signal/src/util/DictionaryUtil.h b/Signal/src/util/DictionaryUtil.h new file mode 100644 index 000000000..c23bfd058 --- /dev/null +++ b/Signal/src/util/DictionaryUtil.h @@ -0,0 +1,7 @@ +#import + +@interface NSDictionary (Util) + +-(NSString*) encodedAsJson; + +@end diff --git a/Signal/src/util/DictionaryUtil.m b/Signal/src/util/DictionaryUtil.m new file mode 100644 index 000000000..f46d9c841 --- /dev/null +++ b/Signal/src/util/DictionaryUtil.m @@ -0,0 +1,16 @@ +#import "DataUtil.h" +#import "DictionaryUtil.h" +#import "Constraints.h" + +@implementation NSDictionary (Util) + +-(NSString*) encodedAsJson { + NSError* jsonSerializeError = nil; + NSData* data = [NSJSONSerialization dataWithJSONObject:self + options:0 + error:&jsonSerializeError]; + checkOperation(jsonSerializeError == nil); + return [data decodedAsUtf8]; +} + +@end diff --git a/Signal/src/util/FunctionalUtil.h b/Signal/src/util/FunctionalUtil.h new file mode 100644 index 000000000..dacb91864 --- /dev/null +++ b/Signal/src/util/FunctionalUtil.h @@ -0,0 +1,32 @@ +#import + +@interface NSArray (FunctionalUtil) + +/// Returns true when any of the items in this array match the given predicate. +-(bool) any:(int (^)(id item))predicate; + +/// Returns true when all of the items in this array match the given predicate. +-(bool) all:(int (^)(id item))predicate; + +/// Returns the first item in this array that matches the given predicate, or else returns nil if none match it. +-(id) firstMatchingElseNil:(int (^)(id item))predicate; + +/// Returns an array of all the results of passing items from this array through the given projection function. +-(NSArray*) map:(id (^)(id item))projection; + +/// Returns an array of all the results of passing items from this array through the given projection function. +-(NSArray*) filter:(int (^)(id item))predicate; + +/// Returns the sum of the doubles in this array of doubles. +-(double) sumDouble; + +/// Returns the sum of the unsigned integers in this array of unsigned integers. +-(NSUInteger) sumNSUInteger; + +/// Returns the sum of the integers in this array of integers. +-(NSInteger) sumNSInteger; + +-(NSDictionary *)keyedBy:(id(^)(id))keySelector; +-(NSDictionary *)groupBy:(id(^)(id value))keySelector; + +@end diff --git a/Signal/src/util/FunctionalUtil.m b/Signal/src/util/FunctionalUtil.m new file mode 100644 index 000000000..1eabd7c7e --- /dev/null +++ b/Signal/src/util/FunctionalUtil.m @@ -0,0 +1,103 @@ +#import "FunctionalUtil.h" +#import "Constraints.h" + +@implementation NSArray (FunctionalUtil) +-(bool) any:(int (^)(id item))predicate { + require(predicate != nil); + for (id e in self) { + if (predicate(e)) { + return true; + } + } + return false; +} +-(bool) all:(int (^)(id item))predicate { + require(predicate != nil); + for (id e in self) { + if (!predicate(e)) { + return false; + } + } + return true; +} +-(id) firstMatchingElseNil:(int (^)(id item))predicate { + require(predicate != nil); + for (id e in self) { + if (predicate(e)) { + return e; + } + } + return nil; +} +-(NSArray*) map:(id (^)(id item))projection { + require(projection != nil); + + NSMutableArray* r = [NSMutableArray arrayWithCapacity:[self count]]; + for (id e in self) { + [r addObject:projection(e)]; + } + return r; +} +-(NSArray*) filter:(int (^)(id item))predicate { + require(predicate != nil); + + NSMutableArray* r = [NSMutableArray array]; + for (id e in self) { + if (predicate(e)) { + [r addObject:e]; + } + } + return r; +} +-(double) sumDouble { + double s = 0.0; + for (NSNumber* e in self) { + s += [e doubleValue]; + } + return s; +} +-(NSUInteger) sumNSUInteger { + NSUInteger s = 0; + for (NSNumber* e in self) { + s += [e unsignedIntegerValue]; + } + return s; +} +-(NSInteger) sumNSInteger { + NSInteger s = 0; + for (NSNumber* e in self) { + s += [e integerValue]; + } + return s; +} +-(NSDictionary *)keyedBy:(id(^)(id value))keySelector { + require(keySelector != nil); + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + for (id value in self) { + result[keySelector(value)] = value; + } + checkOperation([result count] == [self count]); + + return result; +} +-(NSDictionary *)groupBy:(id(^)(id value))keySelector { + require(keySelector != nil); + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + for (id item in self) { + id key = keySelector(item); + + NSMutableArray* group = result[key]; + if (group == nil) { + group = [NSMutableArray array]; + result[key] = group; + } + [group addObject:item]; + } + + return result; +} +@end diff --git a/Signal/src/util/NumberUtil.h b/Signal/src/util/NumberUtil.h new file mode 100644 index 000000000..829137f55 --- /dev/null +++ b/Signal/src/util/NumberUtil.h @@ -0,0 +1,43 @@ +#import +#import "DataUtil.h" +#import "StringUtil.h" +#import "DictionaryUtil.h" +#import "ArrayUtil.h" + +@interface NumberUtil : NSObject + ++(int16_t) congruentDifferenceMod2ToThe16From:(uint16_t)s1 to:(uint16_t)s2; + ++(int8_t) signOfInt32:(int32_t)value; + ++(int8_t) signOfDouble:(double)value; + ++(NSUInteger) largestIntegerThatIsAtMost:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple; + ++(NSUInteger) smallestIntegerThatIsAtLeast:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple; + ++(double) clamp:(double)value toMin:(double)min andMax:(double)max; + ++(NSUInteger) from:(NSUInteger)value subtractWithSaturationAtZero:(NSUInteger)minusDelta; + ++(uint8_t) uint8FromLowUInt4:(uint8_t)low4UInt4 andHighUInt4:(uint8_t)highUInt4; + ++(uint8_t) lowUInt4OfUint8:(uint8_t)value; + ++(uint8_t) highUInt4OfUint8:(uint8_t)value; + ++(NSUInteger) assertConvertIntToNSUInteger:(int) value; + ++(NSInteger) assertConvertUnsignedIntToNSInteger:(unsigned int) value; + ++(int) assertConvertNSUIntegerToInt:(NSUInteger) value; + +@end + +@interface NSNumber (NumberUtil) + +-(bool) hasUnsignedIntegerValue; +-(bool) hasUnsignedLongLongValue; +-(bool) hasLongLongValue; + +@end diff --git a/Signal/src/util/NumberUtil.m b/Signal/src/util/NumberUtil.m new file mode 100644 index 000000000..ea25cf39d --- /dev/null +++ b/Signal/src/util/NumberUtil.m @@ -0,0 +1,95 @@ +#import "NumberUtil.h" +#import "Constraints.h" + +@implementation NumberUtil + ++(int16_t) congruentDifferenceMod2ToThe16From:(uint16_t)s1 to:(uint16_t)s2 { + int32_t d = (int32_t)(s2 - s1); + if (d > INT16_MAX) d -= 1 << 16; + return (int16_t)d; +} + ++(int8_t) signOfInt32:(int32_t)value { + if (value < 0) return -1; + if (value > 0) return +1; + return 0; +} + ++(int8_t) signOfDouble:(double)value { + if (value < 0) return -1; + if (value > 0) return +1; + return 0; +} + ++(NSUInteger) largestIntegerThatIsAtMost:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple { + require(multiple != 0); + NSUInteger d = value / multiple; + d *= multiple; + if (d > value) d -= multiple; + return d; +} + ++(NSUInteger) smallestIntegerThatIsAtLeast:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple { + require(multiple != 0); + NSUInteger d = value / multiple; + d *= multiple; + if (d < value) d += multiple; + return d; +} + ++(double) clamp:(double)value toMin:(double)min andMax:(double)max { + require(min <= max); + if (value < min) return min; + if (value > max) return max; + return value; +} + ++(NSUInteger) from:(NSUInteger)value subtractWithSaturationAtZero:(NSUInteger)minusDelta { + return value - MIN(value, minusDelta); +} + ++(uint8_t) uint8FromLowUInt4:(uint8_t)low4UInt4 andHighUInt4:(uint8_t)highUInt4 { + require(low4UInt4 < 0x10); + require(highUInt4 < 0x10); + return low4UInt4 | (uint8_t)(highUInt4 << 4); +} + ++(uint8_t) lowUInt4OfUint8:(uint8_t)value { + return value & 0xF; +} + ++(uint8_t) highUInt4OfUint8:(uint8_t)value { + return value >> 4; +} + ++(NSUInteger) assertConvertIntToNSUInteger:(int) value { + assert(0 <= value); + return (NSUInteger)value; +} + ++(NSInteger) assertConvertUnsignedIntToNSInteger:(unsigned int) value { + // uint is a subset of NSInteger(long) bounds check is always true + return (NSInteger)value; +} + ++(int) assertConvertNSUIntegerToInt:(NSUInteger) value { + assert(value <= INT32_MAX); + return (int)value; +} + + +@end + +@implementation NSNumber (NumberUtil) + +-(bool) hasUnsignedIntegerValue { + return [self isEqual:[NSNumber numberWithUnsignedInteger:[self unsignedIntegerValue]]]; +} +-(bool) hasUnsignedLongLongValue { + return [self isEqual:[NSNumber numberWithUnsignedLongLong:[self unsignedLongLongValue]]]; +} +-(bool) hasLongLongValue { + return [self isEqual:[NSNumber numberWithLongLong:[self longLongValue]]]; +} + +@end diff --git a/Signal/src/util/Operation.h b/Signal/src/util/Operation.h new file mode 100644 index 000000000..1ecba4f72 --- /dev/null +++ b/Signal/src/util/Operation.h @@ -0,0 +1,40 @@ +#import +#import "DataUtil.h" +#import "StringUtil.h" +#import "DictionaryUtil.h" +#import "ArrayUtil.h" +#import "Future.h" + +typedef void(^Action)(void); +typedef id(^Function)(void); + +@interface Operation : NSObject + +@property (nonatomic,readonly,copy) Action callback; + ++(Operation*) operation:(Action)block; + ++(void) asyncRun:(Action)action + onThread:(NSThread*)thread; + ++(void) asyncRunAndWaitUntilDone:(Action)action + onThread:(NSThread*)thread; + ++(Future*) asyncEvaluate:(Function)function + onThread:(NSThread*)thread; + ++(Future*) asyncEvaluateOnNewThread:(Function)function; + ++(void) asyncRunOnNewThread:(Action)action; + +-(void)run; + +-(SEL) selectorToRun; + +-(void) performOnNewThread; + +-(void) performOnThread:(NSThread*)thread; + +-(void) performOnThreadAndWaitUntilDone:(NSThread*)thread; + +@end diff --git a/Signal/src/util/Operation.m b/Signal/src/util/Operation.m new file mode 100644 index 000000000..df4585f3f --- /dev/null +++ b/Signal/src/util/Operation.m @@ -0,0 +1,85 @@ +#import "Util.h" +#import "Constraints.h" +#import "FutureSource.h" + +@implementation Operation + ++(Operation*) operation:(Action)block { + require(block != NULL); + Operation* a = [Operation new]; + a->_callback = block; + return a; +} + ++(void) asyncRun:(Action)action + onThread:(NSThread*)thread { + + require(action != nil); + require(thread != nil); + + [[Operation operation:action] performOnThread:thread]; +} + ++(Future*) asyncEvaluate:(Function)function + onThread:(NSThread*)thread { + + require(function != nil); + require(thread != nil); + + FutureSource* result = [FutureSource new]; + Action evaler = ^() { + [result trySetResult:function()]; + }; + [[Operation operation:evaler] performOnThread:thread]; + return result; +} + ++(void) asyncRunAndWaitUntilDone:(Action)action + onThread:(NSThread*)thread { + + require(action != nil); + require(thread != nil); + + [[Operation operation:action] performOnThreadAndWaitUntilDone:thread]; +} + ++(void) asyncRunOnNewThread:(Action)action { + require(action != nil); + [[Operation operation:action] performOnNewThread]; +} + ++(Future*) asyncEvaluateOnNewThread:(Function)function { + + require(function != nil); + + FutureSource* result = [FutureSource new]; + Action evaler = ^() { + [result trySetResult:function()]; + }; + [[Operation operation:evaler] performOnNewThread]; + return result; +} + +-(SEL) selectorToRun { + return @selector(run); +} + +-(void) performOnThread:(NSThread*)thread { + require(thread != nil); + [self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:thread == [NSThread currentThread]]; +} + +-(void) performOnThreadAndWaitUntilDone:(NSThread*)thread { + require(thread != nil); + [self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:true]; +} + +-(void) performOnNewThread { + [NSThread detachNewThreadSelector:[self selectorToRun] toTarget:self withObject:nil]; +} + +-(void) run { + _callback(); +} + +@end diff --git a/Signal/src/util/PhoneNumberUtil.h b/Signal/src/util/PhoneNumberUtil.h new file mode 100644 index 000000000..a420a6d64 --- /dev/null +++ b/Signal/src/util/PhoneNumberUtil.h @@ -0,0 +1,9 @@ +#import +#import "PhoneNumber.h" + +@interface PhoneNumberUtil : NSObject ++ (NSString *)callingCodeFromCountryCode:(NSString *)code; ++ (NSString *)countryNameFromCountryCode:(NSString *)code; ++ (NSArray *)countryCodesForSearchTerm:(NSString *)searchTerm; ++ (NSString*) normalizePhoneNumber:(NSString *) number; +@end diff --git a/Signal/src/util/PhoneNumberUtil.m b/Signal/src/util/PhoneNumberUtil.m new file mode 100644 index 000000000..d11c903a8 --- /dev/null +++ b/Signal/src/util/PhoneNumberUtil.m @@ -0,0 +1,40 @@ +#import "PhoneNumberUtil.h" +#import "ContactsManager.h" +#import "FunctionalUtil.h" +#import "NBPhoneNumberUtil.h" + +@implementation PhoneNumberUtil + + ++ (NSString *)countryNameFromCountryCode:(NSString *)code { + NSDictionary *countryCodeComponent = [NSDictionary dictionaryWithObject:code + forKey:NSLocaleCountryCode]; + NSString *identifier = [NSLocale localeIdentifierFromComponents:countryCodeComponent]; + NSString *country = [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier + value:identifier]; + return country; +} + ++ (NSString *)callingCodeFromCountryCode:(NSString *)code { + NSNumber *callingCode = [[NBPhoneNumberUtil sharedInstance] getCountryCodeForRegion:code]; + return [NSString stringWithFormat:@"%@%@", COUNTRY_CODE_PREFIX, callingCode]; +} + ++ (NSArray *)countryCodesForSearchTerm:(NSString *)searchTerm { + + NSArray *countryCodes = [NSLocale ISOCountryCodes]; + + if (searchTerm) { + countryCodes = [countryCodes filter:^int(NSString *code) { + NSString *countryName = [self countryNameFromCountryCode:code]; + return [ContactsManager name:countryName matchesQuery:searchTerm]; + }]; + } + return countryCodes; +} + ++ (NSString*) normalizePhoneNumber:(NSString *) number { + return [[NBPhoneNumberUtil sharedInstance] normalizePhoneNumber:number]; +} + +@end diff --git a/Signal/src/util/SmsInvite.h b/Signal/src/util/SmsInvite.h new file mode 100644 index 000000000..4a77b4089 --- /dev/null +++ b/Signal/src/util/SmsInvite.h @@ -0,0 +1,13 @@ +#import +#import +#import "PhoneNumber.h" + +@interface SmsInvite : NSObject { + UIViewController* parent; +} + ++(SmsInvite*) smsInviteWithParent:(UIViewController*) parent; + +-(void)sendSMSInviteToNumber:(PhoneNumber *)number; + +@end diff --git a/Signal/src/util/SmsInvite.m b/Signal/src/util/SmsInvite.m new file mode 100644 index 000000000..fd040d52b --- /dev/null +++ b/Signal/src/util/SmsInvite.m @@ -0,0 +1,36 @@ +#import "SmsInvite.h" + +#import "LocalizableText.h" + +@implementation SmsInvite + ++ (SmsInvite*) smsInviteWithParent:(UIViewController *)parent { + SmsInvite* invite = [SmsInvite new]; + invite->parent = parent; + return invite; +} + +- (void)sendSMSInviteToNumber:(PhoneNumber *)number{ + if ([MFMessageComposeViewController canSendText]) { + + MFMessageComposeViewController *messageController = [MFMessageComposeViewController new]; + + NSString *inviteMessage = INVITE_USERS_MESSAGE; + + messageController.body = [inviteMessage stringByAppendingString:@" http://appstore.com/signalprivatemessenger"]; + messageController.recipients = @[[number toE164]]; + messageController.messageComposeDelegate = self; + + [parent presentViewController:messageController + animated:YES + completion:nil]; + } +} + +- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { + [controller dismissViewControllerAnimated:YES + completion:nil]; +} + + +@end diff --git a/Signal/src/util/StringUtil.h b/Signal/src/util/StringUtil.h new file mode 100644 index 000000000..518d2b0b5 --- /dev/null +++ b/Signal/src/util/StringUtil.h @@ -0,0 +1,21 @@ +#import + +@interface NSString (Util) +/// The utf-8 encoding of the string's text. +-(NSData*) encodedAsUtf8; +/// The ascii encoding of the string's text. +/// Throws when the string contains non-ascii characters. +-(NSData*) encodedAsAscii; +-(NSRegularExpression*) toRegularExpression; +-(NSString*) withMatchesAgainst:(NSRegularExpression*)regex replacedBy:(NSString*)replacement; +-(bool) containsAnyMatches:(NSRegularExpression*)regex; +-(NSString*) withPrefixRemovedElseNull:(NSString*)prefix; +-(NSData*) decodedAsJsonIntoData; +-(NSDictionary*) decodedAsJsonIntoDictionary; +-(NSData*) decodedAsHexString; +-(NSData*) decodedAsSpaceSeparatedHexString; +-(NSData*) decodedAsBase64Data; +-(NSNumber*) tryParseAsDecimalNumber; +-(NSNumber*) tryParseAsUnsignedInteger; + +@end diff --git a/Signal/src/util/StringUtil.m b/Signal/src/util/StringUtil.m new file mode 100644 index 000000000..b5972b8c8 --- /dev/null +++ b/Signal/src/util/StringUtil.m @@ -0,0 +1,144 @@ +#import "StringUtil.h" +#import "DataUtil.h" +#import "Constraints.h" +#import "NumberUtil.h" + +@implementation NSString (Util) +-(NSData*) decodedAsHexString { + require([self length] % 2 == 0); + + NSUInteger n = [self length] / 2; + uint8_t result[n]; + for (NSUInteger i = 0; i < n; i++) { + unsigned int r; + NSScanner* scanner = [NSScanner scannerWithString:[self substringWithRange:NSMakeRange(i*2, 2)]]; + checkOperation([scanner scanHexInt:&r]); + checkOperation(r < 256); + result[i] = (uint8_t)r; + } + return [NSData dataWithBytes:result length:sizeof(result)]; +} +-(NSData*) decodedAsSpaceSeparatedHexString{ + NSArray* hexComponents = [self componentsSeparatedByString:@" "]; + + NSMutableData* result = [NSMutableData new]; + for (NSString* component in hexComponents) { + unsigned int r; + NSScanner* scanner = [NSScanner scannerWithString:component]; + checkOperation([scanner scanHexInt:&r]); + checkOperation(r < 256); + [result appendData:[NSData dataWithSingleByte:(uint8_t)r]]; + } + + return result; +} +-(NSData*) encodedAsUtf8 { + NSData* result = [self dataUsingEncoding:NSUTF8StringEncoding]; + checkOperationDescribe(result != nil, @"Not a UTF8 string."); + return result; +} +-(NSData*) encodedAsAscii { + NSData* result = [self dataUsingEncoding:NSASCIIStringEncoding]; + checkOperationDescribe(result != nil, @"Not an ascii string."); + return result; +} +-(NSRegularExpression*) toRegularExpression { + NSError *regexInitError = NULL; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:self options:0 error:®exInitError]; + checkOperation(regex != nil && regexInitError == NULL); + return regex; +} +-(NSString*) withMatchesAgainst:(NSRegularExpression*)regex replacedBy:(NSString*)replacement { + require(regex != nil); + require(replacement != nil); + NSMutableString* m = [self mutableCopy]; + [regex replaceMatchesInString:m options:0 range:NSMakeRange(0, [m length]) withTemplate:replacement]; + return m; +} +-(bool) containsAnyMatches:(NSRegularExpression*)regex { + require(regex != nil); + return [regex numberOfMatchesInString:self options:0 range:NSMakeRange(0, [self length])] > 0; +} +-(NSString*) withPrefixRemovedElseNull:(NSString*)prefix { + require(prefix != nil); + if ([prefix length] > 0 && ![self hasPrefix:prefix]) return nil; + return [self substringFromIndex:[prefix length]]; +} +-(NSData*) decodedAsJsonIntoData { + NSError* jsonParseError = nil; + id parsedJson = [NSJSONSerialization dataWithJSONObject:[self encodedAsUtf8] options:0 error:&jsonParseError]; + checkOperationDescribe(jsonParseError == nil, ([NSString stringWithFormat:@"Invalid json: %@", self])); + checkOperationDescribe([parsedJson isKindOfClass:[NSData class]], @"Unexpected json data"); + return parsedJson; +} +-(NSDictionary*) decodedAsJsonIntoDictionary { + NSError* jsonParseError = nil; + id parsedJson = [NSJSONSerialization JSONObjectWithData:[self encodedAsUtf8] options:0 error:&jsonParseError]; + checkOperationDescribe(jsonParseError == nil, ([NSString stringWithFormat:@"Json parse error: %@, on json: %@", jsonParseError, self])); + checkOperationDescribe([parsedJson isKindOfClass:[NSDictionary class]], @"Unexpected json data"); + return parsedJson; +} +-(NSData*) decodedAsBase64Data { + const NSUInteger BitsPerBase64Word = 6; + const NSUInteger BitsPerByte = 8; + const uint8_t Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + uint8_t CharToValueMap[256]; + for (NSUInteger i = 0; i < 256; i++) { + CharToValueMap[i] = 255; + } + for (uint8_t i = 0; i < 64; i++) { + CharToValueMap[Base64Chars[i]] = i; + } + + // Determine amount of information (based on length and padding) + NSUInteger paddingCount = 0; + while (paddingCount < 2 && paddingCount < [self length] - 1 && [self characterAtIndex:[self length] - paddingCount - 1] == '=') { + paddingCount += 1; + } + NSUInteger base64WordCount = [self length] - paddingCount; + NSUInteger bitCount = [self length]*BitsPerBase64Word - paddingCount*BitsPerByte; + NSUInteger byteCount = bitCount / BitsPerByte; + checkOperation(bitCount % BitsPerByte == 0); + + // ASCII to base 64 + NSData* asciiData = [self encodedAsAscii]; + uint8_t base64Words[base64WordCount]; + for (NSUInteger i = 0; i < base64WordCount; i++) { + base64Words[i] = CharToValueMap[[asciiData uint8At:i]]; + require(base64Words[i] < 64); + } + + // base 64 to base 2 + bool bits[bitCount]; + for (NSUInteger i = 0; i < base64WordCount; i++) { + for (NSUInteger j = 0; j < BitsPerBase64Word; j++) { + NSUInteger k = (i+1)*BitsPerBase64Word - 1 - j; + if (k >= bitCount) continue; // may occur due to padding + bits[k] = ((base64Words[i] >> j) & 1) != 0; + } + } + + // base 2 to base 256 + uint8_t bytes[byteCount]; + for (NSUInteger i = 0; i < byteCount; i++) { + bytes[i] = 0; + for (NSUInteger j = 0; j < BitsPerByte; j++) { + NSUInteger k = (i+1)*BitsPerByte - 1 - j; + if (bits[k]) bytes[i] |= 1 << j; + } + } + + return [NSData dataWithBytes:bytes length:sizeof(bytes)]; +} +-(NSNumber*) tryParseAsDecimalNumber { + NSNumberFormatter* formatter = [NSNumberFormatter new]; + [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; + return [formatter numberFromString:self]; +} +-(NSNumber*) tryParseAsUnsignedInteger { + NSNumber* value = [self tryParseAsDecimalNumber]; + return [value hasUnsignedIntegerValue] ? value : nil; +} + +@end diff --git a/Signal/src/util/ThreadManager.h b/Signal/src/util/ThreadManager.h new file mode 100644 index 000000000..4d4af578b --- /dev/null +++ b/Signal/src/util/ThreadManager.h @@ -0,0 +1,50 @@ +#import +#import "CancelToken.h" + +@interface RunningThreadRunLoopPair : NSObject + +@property (nonatomic,readonly) NSThread* thread; +@property (nonatomic,readonly) NSRunLoop* runLoop; + ++(RunningThreadRunLoopPair*) startNewWithThreadName:(NSString*)name; +-(void) terminate; + +@end + +/** + * + * The thread manager is responsible for starting and exposing the low/normal/high latency threads. + * + * Low latency: + * - Includes: Audio encoding/decoding, communicating audio data, advancing zrtp handshake, etc. + * - Operations on this thread should complete at human-interaction speeds (<30ms) and avoid swamping. + * - If an operation must be low latency but takes too long, split it into parts that can be interleaved. + * + * Normal latency: + * - Includes: Registration + * - Operations on this thread should complete at human-reaction speeds (<250ms). + * + * High latency: + * - Includes: DNS CNAME lookup (due to gethostbyname blocking and being non-reentrant and non-threadsafe) + * - Operations on this thread should complete at human-patience speeds (<10s). + * + */ + +@interface ThreadManager : NSObject{ +@private RunningThreadRunLoopPair* low; +@private RunningThreadRunLoopPair* normal; +@private RunningThreadRunLoopPair* high; +} + ++(NSThread*)lowLatencyThread; ++(NSRunLoop*)lowLatencyThreadRunLoop; + ++(NSThread*)normalLatencyThread; ++(NSRunLoop*)normalLatencyThreadRunLoop; + ++(NSThread*)highLatencyThread; ++(NSRunLoop*)highLatencyThreadRunLoop; + ++(void) terminate; + +@end diff --git a/Signal/src/util/ThreadManager.m b/Signal/src/util/ThreadManager.m new file mode 100644 index 000000000..a0b137827 --- /dev/null +++ b/Signal/src/util/ThreadManager.m @@ -0,0 +1,86 @@ +#import "ThreadManager.h" +#import "Util.h" + +#define LOW_THREAD_NAME @"Audio Thread" +#define NORMAL_THREAD_NAME @"Background Thread" +#define HIGH_THREAD_NAME @"Blocking Working Thread" + +@implementation RunningThreadRunLoopPair + +@synthesize runLoop, thread; + ++(RunningThreadRunLoopPair*) startNewWithThreadName:(NSString*)name { + require(name != nil); + + RunningThreadRunLoopPair* instance = [RunningThreadRunLoopPair new]; + instance->thread = [[NSThread alloc] initWithTarget:instance selector:@selector(runLoopUntilCancelled) object:nil]; + [instance->thread setName:name]; + [instance->thread start]; + + [Operation asyncRunAndWaitUntilDone:^{ + instance->runLoop = [NSRunLoop currentRunLoop]; + } onThread:instance->thread]; + + return instance; +} +-(void) terminate { + [thread cancel]; +} +-(void) runLoopUntilCancelled { + NSThread* curThread = [NSThread currentThread]; + NSRunLoop* curRunLoop = [NSRunLoop currentRunLoop]; + while (![curThread isCancelled]) { + [curRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; + } +} + +@end + +@implementation ThreadManager + +static ThreadManager* sharedThreadManagerInternal; + ++(ThreadManager*)sharedThreadManager { + @synchronized(self) { + if (sharedThreadManagerInternal == nil){ + sharedThreadManagerInternal = [ThreadManager new]; + sharedThreadManagerInternal->low = [RunningThreadRunLoopPair startNewWithThreadName:LOW_THREAD_NAME]; + sharedThreadManagerInternal->normal = [RunningThreadRunLoopPair startNewWithThreadName:NORMAL_THREAD_NAME]; + sharedThreadManagerInternal->high = [RunningThreadRunLoopPair startNewWithThreadName:HIGH_THREAD_NAME]; + } + } + return sharedThreadManagerInternal; +} + ++(NSThread*)lowLatencyThread { + return [self sharedThreadManager]->low.thread; +} ++(NSRunLoop*)lowLatencyThreadRunLoop { + return [self sharedThreadManager]->low.runLoop; +} + ++(NSThread*)normalLatencyThread { + return [self sharedThreadManager]->normal.thread; +} ++(NSRunLoop *)normalLatencyThreadRunLoop { + return [self sharedThreadManager]->normal.runLoop; +} + ++(NSThread*)highLatencyThread { + return [self sharedThreadManager]->high.thread; +} ++(NSRunLoop *)highLatencyThreadRunLoop { + return [self sharedThreadManager]->high.runLoop; +} + ++(void) terminate { + @synchronized(self) { + if (sharedThreadManagerInternal == nil) return; + [sharedThreadManagerInternal->low terminate]; + [sharedThreadManagerInternal->normal terminate]; + [sharedThreadManagerInternal->high terminate]; + sharedThreadManagerInternal = nil; + } +} + +@end diff --git a/Signal/src/util/TimeUtil.h b/Signal/src/util/TimeUtil.h new file mode 100644 index 000000000..6d091e51e --- /dev/null +++ b/Signal/src/util/TimeUtil.h @@ -0,0 +1,39 @@ +#import +#import "Terminable.h" +#import "CancelToken.h" +#import "Future.h" +#import "Operation.h" + +@interface TimeUtil : NSObject + ++(NSTimeInterval) time; + +/// Result has type Future(TypeOfValueReturnedByFunction) ++(Future*) scheduleEvaluate:(Function)function + afterDelay:(NSTimeInterval)delay + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken; + +/// Result has type Future(TypeOfValueReturnedByFunction) ++(Future*) scheduleEvaluate:(Function)function + at:(NSDate*)date + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken; + ++(void) scheduleRun:(Action)action + afterDelay:(NSTimeInterval)delay + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken; + ++(void) scheduleRun:(Action)action + at:(NSDate*)date + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken; + ++(void) scheduleRun:(Action)action + periodically:(NSTimeInterval)interval + onRunLoop:(NSRunLoop*)runLoop + untilCancelled:(id)untilCancelledToken + andRunImmediately:(BOOL)shouldRunImmediately; + +@end diff --git a/Signal/src/util/TimeUtil.m b/Signal/src/util/TimeUtil.m new file mode 100644 index 000000000..d23eace7c --- /dev/null +++ b/Signal/src/util/TimeUtil.m @@ -0,0 +1,154 @@ +#import "TimeUtil.h" +#import "Util.h" +#import "AnonymousTerminator.h" +#import "Constraints.h" +#import "FutureSource.h" + +@implementation TimeUtil + ++(NSTimeInterval) time { + return [[NSProcessInfo processInfo] systemUptime]; +} + ++(Future*) scheduleEvaluate:(Function)function + afterDelay:(NSTimeInterval)delay + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken { + + require(function != NULL); + require(runLoop != nil); + require(delay >= 0); + + FutureSource* result = [FutureSource new]; + Action evaler = ^{ + [result trySetResult:function()]; + }; + [self scheduleHelper:evaler + withPeriod:delay + onRunLoop:runLoop + repeating:false + untilCancelled:unlessCancelledToken + andRunImmediately:NO]; + + [unlessCancelledToken whenCancelledTryCancel:result]; + return result; +} + ++(Future*) scheduleEvaluate:(Function)function + at:(NSDate*)date + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken { + + require(function != NULL); + require(runLoop != nil); + require(date != nil); + + NSTimeInterval delay = [date timeIntervalSinceNow]; + return [self scheduleEvaluate:function + afterDelay:MAX(0, delay) + onRunLoop:runLoop + unlessCancelled:unlessCancelledToken]; +} + ++(void) scheduleRun:(Action)action + afterDelay:(NSTimeInterval)delay + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken { + + require(action != NULL); + require(runLoop != nil); + require(delay >= 0); + if (delay == INFINITY) return; + + [self scheduleHelper:action + withPeriod:delay + onRunLoop:runLoop + repeating:false + untilCancelled:unlessCancelledToken + andRunImmediately:NO]; +} + ++(void) scheduleRun:(Action)action + at:(NSDate*)date + onRunLoop:(NSRunLoop*)runLoop + unlessCancelled:(id)unlessCancelledToken { + + require(action != NULL); + require(runLoop != nil); + require(date != nil); + + NSTimeInterval delay = [date timeIntervalSinceNow]; + [self scheduleRun:action + afterDelay:MAX(0, delay) + onRunLoop:runLoop + unlessCancelled:unlessCancelledToken]; +} + ++(void) scheduleRun:(Action)action + periodically:(NSTimeInterval)interval + onRunLoop:(NSRunLoop*)runLoop + untilCancelled:(id)untilCancelledToken + andRunImmediately:(BOOL)shouldRunImmediately{ + + require(action != NULL); + require(runLoop != nil); + require(interval > 0); + + [self scheduleHelper:action + withPeriod:interval + onRunLoop:runLoop + repeating:true + untilCancelled:untilCancelledToken + andRunImmediately:shouldRunImmediately]; +} + ++(void) scheduleHelper:(Action)callback + withPeriod:(NSTimeInterval)interval + onRunLoop:(NSRunLoop*)runLoop + repeating:(bool)repeats + untilCancelled:(id)untilCancelledToken + andRunImmediately:(BOOL)shouldRunImmediately{ + + require(callback != NULL); + require(runLoop != nil); + require(interval >= 0); + require(!repeats || interval > 0); + + if ([untilCancelledToken isAlreadyCancelled]) { + return; + } + if (!repeats && interval == 0) { + callback(); + return; + } + if (shouldRunImmediately){ + callback(); + } + + callback = [callback copy]; + __block bool hasBeenCancelled = false; + __block NSObject* cancelLock = [NSObject new]; + + Operation* callbackUnlessCancelled = [Operation operation:^{ + @synchronized(cancelLock) { + if (hasBeenCancelled) return; + callback(); + } + }]; + + NSTimer* timer = [NSTimer timerWithTimeInterval:interval + target:callbackUnlessCancelled + selector:[callbackUnlessCancelled selectorToRun] + userInfo:nil + repeats:repeats]; + [runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; + + [untilCancelledToken whenCancelled:^{ + @synchronized(cancelLock) { + hasBeenCancelled = true; + [timer invalidate]; + } + }]; +} + +@end diff --git a/Signal/src/util/UIUtil.h b/Signal/src/util/UIUtil.h new file mode 100644 index 000000000..82938e156 --- /dev/null +++ b/Signal/src/util/UIUtil.h @@ -0,0 +1,26 @@ +#import + +/** + * + * UTUtil contains various class methods that centralize common app UI functionality that would otherwise be hardcoded. + * + */ + +@interface UIUtil : NSObject + ++ (UIFont *)helveticaNeueLTStdLightFontWithSize:(CGFloat)size; ++ (UIFont *)helveticaNeueLTStdBoldFontWithSize:(CGFloat)size; ++ (UIFont *)helveticaNeueLTStdMediumFontWithSize:(CGFloat)size; ++ (UIFont *)helveticaRegularWithSize:(CGFloat)size; ++ (UIFont *)helveticaLightWithSize:(CGFloat)size; ++ (UIColor*)darkBackgroundColor; ++ (UIColor*)blueColor; ++ (UIColor*)yellowColor; ++ (UIColor*)redColor; ++ (UIColor*)greenColor; ++ (UIColor*)whiteColor; ++ (UIColor*)transparentLightGrayColor; ++ (void)applyRoundedBorderToImageView:(UIImageView *__strong*)imageView; ++ (void)removeRoundedBorderToImageView:(UIImageView *__strong*)imageView; + +@end diff --git a/Signal/src/util/UIUtil.m b/Signal/src/util/UIUtil.m new file mode 100644 index 000000000..315331ffe --- /dev/null +++ b/Signal/src/util/UIUtil.m @@ -0,0 +1,73 @@ +#import "UIUtil.h" + +static NSString *const HELVETICA_NEUE_LTSTD_LIGHT_NAME = @"HelveticaNeueLTStd-Lt"; +static NSString *const HELVETICA_NEUE_LTSTD_BOLD_NAME = @"HelveticaNeueLTStd-Bold"; +static NSString *const HELVETICA_NEUE_LTSTD_MEDIUM_NAME = @"HelveticaNeueLTStd-Md"; +static NSString *const HELVETICA_REGULAR_NAME = @"Helvetica"; +static NSString *const HELVETICA_LIGHT_NAME = @"Helvetica-Light"; + +#define CONTACT_PICTURE_VIEW_BORDER_WIDTH 2.0f + +@implementation UIUtil + ++ (UIFont *)helveticaNeueLTStdLightFontWithSize:(CGFloat)size { + return [UIFont fontWithName:HELVETICA_NEUE_LTSTD_LIGHT_NAME size:size]; +} + ++ (UIFont *)helveticaNeueLTStdBoldFontWithSize:(CGFloat)size { + return [UIFont fontWithName:HELVETICA_NEUE_LTSTD_BOLD_NAME size:size]; +} + ++ (UIFont *)helveticaNeueLTStdMediumFontWithSize:(CGFloat)size { + return [UIFont fontWithName:HELVETICA_NEUE_LTSTD_MEDIUM_NAME size:size]; +} + ++ (UIFont *)helveticaRegularWithSize:(CGFloat)size { + return [UIFont fontWithName:HELVETICA_REGULAR_NAME size:size]; +} + ++ (UIFont *)helveticaLightWithSize:(CGFloat)size { + return [UIFont fontWithName:HELVETICA_LIGHT_NAME size:size]; +} + ++ (UIColor*)darkBackgroundColor { + return [UIColor colorWithRed:35.0f/255.0f green:31.0f/255.0f blue:32.0f/255.0f alpha:1.0f]; +} + ++ (UIColor*)blueColor { + return [UIColor colorWithRed:0.0f green:174.0f/255.0f blue:239.0f/255.0f alpha:1.0f]; +} + ++ (UIColor*)yellowColor { + return [UIColor colorWithRed:1.0f green:221.0f/255.0f blue:170.0f/255.0f alpha:1.0f]; +} + ++ (UIColor*)redColor { + return [UIColor colorWithRed:237.0f/255.0f green:96.0f/255.0f blue:98.0f/255.0f alpha:1.0f]; +} + ++ (UIColor *)greenColor { + return [UIColor colorWithRed:0.0f green:199.0f/255.0f blue:149.0f/255.0f alpha:1.0f]; +} + ++ (UIColor*)whiteColor { + return [UIColor colorWithRed:0.8f green:0.8f blue:0.8f alpha:1.0f]; +} + ++ (UIColor*)transparentLightGrayColor { + return [UIColor colorWithRed:0.5f green:0.5f blue:0.5f alpha:0.7f]; +} + ++ (void)applyRoundedBorderToImageView:(UIImageView *__strong*)imageView { + [[*imageView layer] setBorderWidth:CONTACT_PICTURE_VIEW_BORDER_WIDTH]; + [[*imageView layer] setBorderColor:[[UIColor lightGrayColor] CGColor]]; + [[*imageView layer] setCornerRadius:CGRectGetWidth([*imageView frame])/2]; + [[*imageView layer] setMasksToBounds:YES]; +} + ++ (void)removeRoundedBorderToImageView:(UIImageView *__strong*)imageView { + [[*imageView layer] setBorderWidth:0]; + [[*imageView layer] setCornerRadius:0]; +} + +@end diff --git a/Signal/src/util/Util.h b/Signal/src/util/Util.h new file mode 100644 index 000000000..cd2d817dd --- /dev/null +++ b/Signal/src/util/Util.h @@ -0,0 +1,15 @@ +#import +#import "ArrayUtil.h" +#import "AsyncUtil.h" +#import "Constraints.h" +#import "Crc32.h" +#import "DataUtil.h" +#import "DictionaryUtil.h" +#import "FunctionalUtil.h" +#import "Operation.h" +#import "NumberUtil.h" +#import "StringUtil.h" +#import "TimeUtil.h" +#import "FutureUtil.h" +#import "UIUtil.h" +#import "DateUtil.h" diff --git a/Signal/src/util/Zid.h b/Signal/src/util/Zid.h new file mode 100644 index 000000000..95209f74c --- /dev/null +++ b/Signal/src/util/Zid.h @@ -0,0 +1,8 @@ +#import + +@interface Zid : NSObject { +@private NSData* data; +} ++(Zid*) zidWithData:(NSData*)zidData; +-(NSData*) getData; +@end diff --git a/Signal/src/util/Zid.m b/Signal/src/util/Zid.m new file mode 100644 index 000000000..9455ff5b9 --- /dev/null +++ b/Signal/src/util/Zid.m @@ -0,0 +1,15 @@ +#import "Zid.h" +#import "Constraints.h" + +@implementation Zid ++(Zid*) zidWithData:(NSData*)zidData { + require(zidData != nil); + require([zidData length] == 12); + Zid* s = [Zid new]; + s->data = zidData; + return s; +} +-(NSData*) getData { + return data; +} +@end diff --git a/Signal/src/util/collections/CyclicalBuffer.h b/Signal/src/util/collections/CyclicalBuffer.h new file mode 100644 index 000000000..c69f6ee8d --- /dev/null +++ b/Signal/src/util/collections/CyclicalBuffer.h @@ -0,0 +1,48 @@ +#import + +/** + * + * Cyclic buffer is used to efficiently enqueue and dequeue blocks of data. + * + * Note that methods with 'volatile' in the name have results that can directly + * reference the queue's internal buffer, instead of returning a safe copy. + * The data returned by volatile methods must be used immediately and under the + * constraints that more data is not being enqueued at the time. + * Enqueueing data invalidates all previous volatile results, because the data they + * reference may have been overwritten. + * + */ +@interface CyclicalBuffer : NSObject { +@private NSMutableData* buffer; +@private uint32_t readOffset; +@private uint32_t count; +} + +/// Adds data to the buffer. The buffer will be resized if necessary. +-(void) enqueueData:(NSData*)data; + +/// The number of bytes in the buffer. +-(NSUInteger) enqueuedLength; + +/// Returns a view of the given length of bytes from the buffer. +/// Fails if there isn't enough enqueued data to satisfy the request. +-(NSData*) peekDataWithLength:(NSUInteger)length; + +/// Extracts the given length of bytes from the buffer. +/// Fails if there isn't enough enqueued data to satisfy the request. +-(NSData*) dequeueDataWithLength:(NSUInteger)length; + +/// Dequeues the given length of bytes from the buffer, without returning them. +/// Fails if there isn't enough enqueued data to satisfy the request. +-(void) discard:(NSUInteger)length; + +/// Extracts the given length of bytes from the buffer, POTENTIALLY WITHOUT COPYING. +/// Fails if there isn't enough enqueued data to satisfy the request. +/// Consider result as invalid if more data is enqueued, because its contents may be overwritten. +-(NSData*) dequeuePotentialyVolatileDataWithLength:(NSUInteger)length; + +/// Returns a volatile view of as much upcoming data-to-be-dequeued as possible, WITHOUT COPYING. +/// Consider result as invalid if more data is enqueued, because its contents may be overwritten. +-(NSData*) peekVolatileHeadOfData; + +@end diff --git a/Signal/src/util/collections/CyclicalBuffer.m b/Signal/src/util/collections/CyclicalBuffer.m new file mode 100644 index 000000000..7caabd976 --- /dev/null +++ b/Signal/src/util/collections/CyclicalBuffer.m @@ -0,0 +1,99 @@ +#import "CyclicalBuffer.h" +#import "Constraints.h" +#import "Util.h" + +#define INITIAL_CAPACITY 100 + +@implementation CyclicalBuffer + +-(id) init { + if (self = [super init]) { + buffer = [NSMutableData dataWithLength:INITIAL_CAPACITY]; + } + return self; +} + +-(void) enqueueData:(NSData*)data { + require(data != nil); + if([data length] == 0) return; + + NSUInteger incomingDataLength = [data length]; + NSUInteger bufferCapacity = [buffer length]; + NSUInteger writeOffset = (readOffset + count) % bufferCapacity; + NSUInteger bufferSpaceAvailable = bufferCapacity - count; + NSUInteger writeSlack = bufferCapacity - writeOffset; + + if (bufferSpaceAvailable < incomingDataLength) { + NSUInteger readSlack = bufferCapacity - readOffset; + NSUInteger newCapacity = bufferCapacity * 2 + incomingDataLength; + NSMutableData* newBuffer = [NSMutableData dataWithLength:newCapacity]; + [newBuffer replaceBytesInRange:NSMakeRange(0, MIN(readSlack, count)) withBytes:(uint8_t*)[buffer bytes] + readOffset]; + if (readSlack < count) { + [newBuffer replaceBytesInRange:NSMakeRange(readSlack, count - readSlack) withBytes:(uint8_t*)[buffer bytes]]; + } + buffer = newBuffer; + bufferCapacity = newCapacity; + readOffset = 0; + writeOffset = count; + bufferSpaceAvailable = bufferCapacity - count; + writeSlack = bufferCapacity - writeOffset; + } + + assert(bufferSpaceAvailable >= incomingDataLength); + + [buffer replaceBytesInRange:NSMakeRange(writeOffset, MIN(writeSlack, incomingDataLength)) withBytes:[data bytes]]; + if (incomingDataLength > writeSlack) { + [buffer replaceBytesInRange:NSMakeRange(0, incomingDataLength - writeSlack) withBytes:(uint8_t*)[data bytes] + writeSlack]; + } + count += [data length]; +} + +-(NSUInteger) enqueuedLength { + return count; +} + +-(void) discard:(NSUInteger)length { + require(length <= count); + count -= length; + readOffset = (readOffset + length)%[buffer length]; +} + +-(NSData*) peekDataWithLength:(NSUInteger)length{ + require(length <= count); + if (length == 0) return [NSData data]; + + NSUInteger readSlack = [buffer length] - readOffset; + + NSMutableData* result = [NSMutableData dataWithLength:length]; + [result replaceBytesInRange:NSMakeRange(0, MIN(readSlack, length)) withBytes:(uint8_t*)[buffer bytes] + readOffset]; + if (readSlack < length) { + [result replaceBytesInRange:NSMakeRange(readSlack, length - readSlack) withBytes:[buffer bytes]]; + } + + return result; +} + +-(NSData*) dequeueDataWithLength:(NSUInteger)length { + NSData* result = [self peekDataWithLength:length]; + [self discard:length]; + return result; +} + +-(NSData*) dequeuePotentialyVolatileDataWithLength:(NSUInteger)length { + NSUInteger readSlack = [buffer length] - readOffset; + + if (readSlack < length) return [self dequeueDataWithLength:length]; + + NSData* result = [buffer subdataVolatileWithRange:NSMakeRange(readOffset, length)]; + + [self discard:length]; + return result; +} + +-(NSData*) peekVolatileHeadOfData { + NSUInteger capacity = [buffer length]; + NSUInteger slack = capacity - readOffset; + return [buffer subdataVolatileWithRange:NSMakeRange(readOffset, MIN(count, slack))]; +} + +@end diff --git a/Signal/src/util/collections/PriorityQueue.h b/Signal/src/util/collections/PriorityQueue.h new file mode 100644 index 000000000..5e78c88e7 --- /dev/null +++ b/Signal/src/util/collections/PriorityQueue.h @@ -0,0 +1,15 @@ +#import +#import "Constraints.h" + +@interface PriorityQueue : NSObject { +@private NSMutableArray* items; +} + +@property (readonly,nonatomic,copy) NSComparator comparator; + ++(PriorityQueue*) priorityQueueAscendingWithComparator:(NSComparator)comparator; +-(void)enqueue:(id)item; +-(id)peek; +-(id) dequeue; +-(NSUInteger) count; +@end diff --git a/Signal/src/util/collections/PriorityQueue.m b/Signal/src/util/collections/PriorityQueue.m new file mode 100644 index 000000000..b4595dc2f --- /dev/null +++ b/Signal/src/util/collections/PriorityQueue.m @@ -0,0 +1,66 @@ +#import "PriorityQueue.h" +#import + +@implementation PriorityQueue + ++(PriorityQueue*) priorityQueueAscendingWithComparator:(NSComparator)comparator { + require(comparator != nil); + PriorityQueue* q = [PriorityQueue new]; + q->_comparator = comparator; + q->items = [NSMutableArray array]; + return q; +} + +-(void)enqueue:(id)item { + NSUInteger curIndex = [items count]; + [items addObject:item]; + while (curIndex > 0) { + NSUInteger parentIndex = (curIndex - 1) >> 1; + id parentItem = [items objectAtIndex:parentIndex]; + if (_comparator(item, parentItem) >= 0) break; + + [items setObject:parentItem atIndexedSubscript:curIndex]; + [items setObject:item atIndexedSubscript:parentIndex]; + curIndex = parentIndex; + } +} + +-(id)peek { + requireState([items count] > 0); + return [items objectAtIndex:0]; +} + +-(id) dequeue { + requireState([items count] > 0); + id result = [items objectAtIndex:0]; + + // iteratively pull up smaller child until we hit the bottom of the heap + NSUInteger endangeredIndex = [items count] - 1; + id endangeredItem = [items objectAtIndex:endangeredIndex]; + NSUInteger i = 0; + while (true) { + NSUInteger childIndex1 = i*2+1; + NSUInteger childIndex2 = i*2+2; + if (childIndex1 >= endangeredIndex) break; + + NSUInteger smallerChildIndex = _comparator([items objectAtIndex:childIndex1], [items objectAtIndex:childIndex2]) <= 0 ? childIndex1 : childIndex2; + id smallerChild = [items objectAtIndex:smallerChildIndex]; + bool useEndangered = _comparator(endangeredItem, smallerChild) <= 0; + if (useEndangered) break; + + [items setObject:smallerChild atIndexedSubscript:i]; + i = smallerChildIndex; + } + + // swap the item at the index to be removed into the new empty space at the bottom of heap + [items setObject:endangeredItem atIndexedSubscript:i]; + [items removeObjectAtIndex:endangeredIndex]; + + return result; +} + +-(NSUInteger) count { + return [items count]; +} +@end + diff --git a/Signal/src/util/collections/Queue.h b/Signal/src/util/collections/Queue.h new file mode 100644 index 000000000..638596488 --- /dev/null +++ b/Signal/src/util/collections/Queue.h @@ -0,0 +1,10 @@ +#import + +@interface Queue : NSObject +-(void) enqueue:(id)item; +-(id) dequeue; +-(id) tryDequeue; +-(id) peek; +-(id) peekAt:(NSUInteger)offset; +-(NSUInteger) count; +@end diff --git a/Signal/src/util/collections/Queue.m b/Signal/src/util/collections/Queue.m new file mode 100644 index 000000000..ae4e09680 --- /dev/null +++ b/Signal/src/util/collections/Queue.m @@ -0,0 +1,37 @@ +#import "Queue.h" +#import "Constraints.h" + +@implementation Queue { +@private NSMutableArray* items; +} +-(id) init { + if (self = [super init]) { + self->items = [NSMutableArray array]; + } + return self; +} +-(void) enqueue:(id)item { + [items addObject:item]; +} +-(id) tryDequeue { + if ([self count] == 0) return nil; + return [self dequeue]; +} +-(id) dequeue { + requireState([self count] > 0); + id result = [items objectAtIndex:0]; + [items removeObjectAtIndex:0]; + return result; +} +-(id) peek { + requireState([self count] > 0); + return [items objectAtIndex:0]; +} +-(id) peekAt:(NSUInteger)offset { + require(offset < [self count]); + return [items objectAtIndex:offset]; +} +-(NSUInteger) count { + return [items count]; +} +@end diff --git a/Signal/src/util/constraints/BadArgument.h b/Signal/src/util/constraints/BadArgument.h new file mode 100644 index 000000000..f8ea1309a --- /dev/null +++ b/Signal/src/util/constraints/BadArgument.h @@ -0,0 +1,6 @@ +#import + +@interface BadArgument : NSException ++(BadArgument*) new:(NSString*)reason; ++(void)raise:(NSString *)message; +@end diff --git a/Signal/src/util/constraints/BadArgument.m b/Signal/src/util/constraints/BadArgument.m new file mode 100644 index 000000000..c142d065a --- /dev/null +++ b/Signal/src/util/constraints/BadArgument.m @@ -0,0 +1,10 @@ +#import "BadArgument.h" + +@implementation BadArgument ++(BadArgument*) new:(NSString*)reason { + return [[BadArgument alloc] initWithName:@"Invalid Argument" reason:reason userInfo:nil]; +} ++(void)raise:(NSString *)message { + [BadArgument raise:@"Invalid Argument" format:@"%@", message]; +} +@end diff --git a/Signal/src/util/constraints/BadState.h b/Signal/src/util/constraints/BadState.h new file mode 100644 index 000000000..c8d22d36b --- /dev/null +++ b/Signal/src/util/constraints/BadState.h @@ -0,0 +1,5 @@ +#import + +@interface BadState : NSException ++(void)raise:(NSString *)message; +@end diff --git a/Signal/src/util/constraints/BadState.m b/Signal/src/util/constraints/BadState.m new file mode 100644 index 000000000..26a0e6451 --- /dev/null +++ b/Signal/src/util/constraints/BadState.m @@ -0,0 +1,7 @@ +#import "Constraints.h" + +@implementation BadState ++(void)raise:(NSString *)message { + [BadState raise:@"Invalid State" format:@"%@", message]; +} +@end diff --git a/Signal/src/util/constraints/Constraints.h b/Signal/src/util/constraints/Constraints.h new file mode 100644 index 000000000..57eddf40a --- /dev/null +++ b/Signal/src/util/constraints/Constraints.h @@ -0,0 +1,27 @@ +#import "OperationFailed.h" +#import "BadArgument.h" +#import "SecurityFailure.h" +#import "BadState.h" + +/// 'require(X)' is used to indicate parameter-related preconditions that callers must satisfy. +/// Failure to satisfy indicates a bug in the caller. +#define require(expr) if (!(expr)) [BadArgument raise:[NSString stringWithFormat:@"require %@ (in %s at line %d)", (@#expr), __FILE__, __LINE__]] + +/// 'requireState(X)' is used to indicate callee-state-related preconditions that callers must satisfy. +/// Failure to satisfy indicates a stateful bug in either the caller or the callee. +#define requireState(expr) if (!(expr)) [BadState raise:[NSString stringWithFormat:@"required state: %@ (in %s at line %d)", (@#expr), __FILE__, __LINE__]] + +/// 'checkOperation(X)' is used to throw exceptions if operations fail. +/// Failure does not indicate a bug. +/// Methods may throw these exceptions for callers to catch as a 'returned error' result. +#define checkOperation(expr) if (!(expr)) [OperationFailed raise:[NSString stringWithFormat:@"Operation failed. Expected: %@(in %s at line %d)", (@#expr),__FILE__,__LINE__]] + +/// 'checkOperationDescribe(X, Desc)' is used to throw exceptions if operations fail, and describe the problem. +/// Failure does not indicate a bug. +/// Methods may throw these exceptions for callers to catch as a 'returned error' result. +#define checkOperationDescribe(expr, desc) if (!(expr)) [OperationFailed raise:[NSString stringWithFormat:@"Operation failed: %@ Expected: %@(in %s at line %d)", (desc), (@#expr),__FILE__,__LINE__]] + +/// 'checkSecurityOperation(X, Desc)' is used to throw exceptions if operations fail due to authentication or other crypto failures, and describe the problem. +/// Failure does not indicate a bug. +/// Methods may throw these exceptions for callers to catch as a 'returned error' result. +#define checkSecurityOperation(expr, desc) if (!(expr)) [SecurityFailure raise:[NSString stringWithFormat:@"Security related failure: %@ Expected: %@(in %s at line %d)", (desc), (@#expr),__FILE__,__LINE__]] diff --git a/Signal/src/util/constraints/OperationFailed.h b/Signal/src/util/constraints/OperationFailed.h new file mode 100644 index 000000000..2b7b272b3 --- /dev/null +++ b/Signal/src/util/constraints/OperationFailed.h @@ -0,0 +1,6 @@ +#import + +@interface OperationFailed : NSException ++(OperationFailed*) new:(NSString*)reason; ++(void)raise:(NSString *)message; +@end diff --git a/Signal/src/util/constraints/OperationFailed.m b/Signal/src/util/constraints/OperationFailed.m new file mode 100644 index 000000000..b1b911f38 --- /dev/null +++ b/Signal/src/util/constraints/OperationFailed.m @@ -0,0 +1,10 @@ +#import "Constraints.h" + +@implementation OperationFailed ++(OperationFailed*) new:(NSString*)reason { + return [[OperationFailed alloc] initWithName:@"Operation failed" reason:reason userInfo:nil]; +} ++(void)raise:(NSString *)message { + [OperationFailed raise:@"Operation failed" format:@"%@", message]; +} +@end diff --git a/Signal/src/util/constraints/SecurityFailure.h b/Signal/src/util/constraints/SecurityFailure.h new file mode 100644 index 000000000..2f4f8dfb5 --- /dev/null +++ b/Signal/src/util/constraints/SecurityFailure.h @@ -0,0 +1,7 @@ +#import +#import "OperationFailed.h" + +@interface SecurityFailure : OperationFailed ++(SecurityFailure*) new:(SecurityFailure*)reason; ++(void)raise:(NSString *)message; +@end diff --git a/Signal/src/util/constraints/SecurityFailure.m b/Signal/src/util/constraints/SecurityFailure.m new file mode 100644 index 000000000..c004867b2 --- /dev/null +++ b/Signal/src/util/constraints/SecurityFailure.m @@ -0,0 +1,10 @@ +#import "SecurityFailure.h" + +@implementation SecurityFailure ++(SecurityFailure*) new:(NSString*)reason { + return [[SecurityFailure alloc] initWithName:@"Insecure" reason:reason userInfo:nil]; +} ++(void)raise:(NSString *)message { + [SecurityFailure raise:@"Insecure" format:@"%@", message]; +} +@end diff --git a/Signal/src/util/protocols/Terminable.h b/Signal/src/util/protocols/Terminable.h new file mode 100644 index 000000000..5038ba2c0 --- /dev/null +++ b/Signal/src/util/protocols/Terminable.h @@ -0,0 +1,7 @@ +#import + +/// Cancels something when terminate is called. +/// It must be safe to call terminate multiple times. +@protocol Terminable +-(void) terminate; +@end diff --git a/Signal/src/util/protocols/utilities/AnonymousTerminator.h b/Signal/src/util/protocols/utilities/AnonymousTerminator.h new file mode 100644 index 000000000..a7279aef8 --- /dev/null +++ b/Signal/src/util/protocols/utilities/AnonymousTerminator.h @@ -0,0 +1,11 @@ +#import +#import "Terminable.h" + +@interface AnonymousTerminator : NSObject { +@private bool alreadyCalled; +} +@property (readonly,nonatomic,copy) void (^terminateBlock)(void); + ++(AnonymousTerminator*) cancellerWithCancel:(void (^)(void))terminate; + +@end diff --git a/Signal/src/util/protocols/utilities/AnonymousTerminator.m b/Signal/src/util/protocols/utilities/AnonymousTerminator.m new file mode 100644 index 000000000..67221fe19 --- /dev/null +++ b/Signal/src/util/protocols/utilities/AnonymousTerminator.m @@ -0,0 +1,20 @@ +#import "AnonymousTerminator.h" +#import "Constraints.h" + +@implementation AnonymousTerminator + ++(AnonymousTerminator*) cancellerWithCancel:(void (^)(void))terminate { + require(terminate != nil); + AnonymousTerminator* c = [AnonymousTerminator new]; + c->_terminateBlock = terminate; + return c; +} + +-(void) terminate { + @synchronized(self) { + if (alreadyCalled) return; + alreadyCalled = true; + } + _terminateBlock(); +} +@end diff --git a/Signal/src/view controllers/CallLogViewController.h b/Signal/src/view controllers/CallLogViewController.h new file mode 100644 index 000000000..a48a9eb82 --- /dev/null +++ b/Signal/src/view controllers/CallLogViewController.h @@ -0,0 +1,12 @@ +#import + +#import "ContactsManager.h" +#import "CallLogTableViewCell.h" +#import "SearchBarTitleView.h" + +@interface CallLogViewController : UIViewController + +@property (nonatomic, strong) IBOutlet SearchBarTitleView *searchBarTitleView; +@property (nonatomic, strong) IBOutlet UITableView *recentCallsTableView; + +@end diff --git a/Signal/src/view controllers/CallLogViewController.m b/Signal/src/view controllers/CallLogViewController.m new file mode 100644 index 000000000..c63b71dd5 --- /dev/null +++ b/Signal/src/view controllers/CallLogViewController.m @@ -0,0 +1,178 @@ +#import "Environment.h" +#import "LocalizableText.h" +#import "PreferencesUtil.h" +#import "CallLogViewController.h" +#import "RecentCall.h" +#import "TabBarParentViewController.h" +#import "RecentCallManager.h" + +#import + +#define RECENT_CALL_TABLE_CELL_HEIGHT 43 + +static NSString *const RECENT_CALL_TABLE_CELL_IDENTIFIER = @"CallLogTableViewCell"; + +typedef NSComparisonResult (^CallComparator)(RecentCall*, RecentCall*); + +@interface CallLogViewController () { + NSArray *_recents; + BOOL _tableViewContentMutating; + NSString *_searchTerm; +} + +@end + +@implementation CallLogViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self observeRecentCalls]; + [self observeKeyboardNotifications]; + _searchBarTitleView.titleLabel.text = RECENT_NAV_BAR_TITLE; + _recentCallsTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [super viewWillAppear:animated]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)observeRecentCalls { + ObservableValue *observableRecents = [[[Environment getCurrent] recentCallManager] getObservableRecentCalls]; + + [observableRecents watchLatestValue:^(NSArray *latestRecents) { + if (_searchTerm) { + _recents = [[[Environment getCurrent] recentCallManager] recentsForSearchString:_searchTerm + andExcludeArchived:NO]; + } else { + _recents = latestRecents; + } + + if (!_tableViewContentMutating) { + [_recentCallsTableView reloadData]; + } + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +- (void)deleteRecentCallAtIndexPath:(NSIndexPath *)indexPath { + [_recentCallsTableView beginUpdates]; + [_recentCallsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationLeft]; + + RecentCall *recent; + + + [[[Environment getCurrent] recentCallManager] removeRecentCall:recent]; + + [_recentCallsTableView endUpdates]; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return (NSInteger)[_recents count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + CallLogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RECENT_CALL_TABLE_CELL_IDENTIFIER]; + if (!cell) { + cell = [[CallLogTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:RECENT_CALL_TABLE_CELL_IDENTIFIER]; + cell.delegate = self; + } + RecentCall *recent = _recents[(NSUInteger)indexPath.row]; + [cell configureWithRecentCall:recent]; + + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return RECENT_CALL_TABLE_CELL_HEIGHT; +} + +#pragma mark - RecentCallTableViewCellDelegate + +- (void)recentCallTableViewCellTappedDelete:(CallLogTableViewCell *)cell { + _tableViewContentMutating = YES; + NSIndexPath *indexPath = [_recentCallsTableView indexPathForCell:cell]; + + [_recentCallsTableView beginUpdates]; + [_recentCallsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationLeft]; + + RecentCall *recent = _recents[(NSUInteger)indexPath.row]; + [[[Environment getCurrent] recentCallManager] removeRecentCall:recent]; + + [_recentCallsTableView endUpdates]; + _tableViewContentMutating = NO; +} + +- (void)recentCallTableViewCellTappedCall:(CallLogTableViewCell *)cell { + NSIndexPath *indexPath = [_recentCallsTableView indexPathForCell:cell]; + RecentCall *recent = _recents[(NSUInteger)indexPath.row]; + [(TabBarParentViewController *)self.mm_drawerController.centerViewController showDialerViewControllerWithNumber:recent.phoneNumber]; +} + +#pragma mark - SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term { + _searchTerm = term; + _recents = [[[Environment getCurrent] recentCallManager] recentsForSearchString:term + andExcludeArchived:NO]; + [_recentCallsTableView reloadData]; +} + +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft + animated:YES + completion:nil]; +} + +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view { + _searchTerm = nil; + _recents = [[[Environment getCurrent] recentCallManager] recentsForSearchString:nil + andExcludeArchived:NO]; + [_recentCallsTableView reloadData]; +} + +#pragma mark - Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_recentCallsTableView.frame) - (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _recentCallsTableView.frame = CGRectMake(CGRectGetMinX(_recentCallsTableView.frame), + CGRectGetMinY(_recentCallsTableView.frame), + CGRectGetWidth(_recentCallsTableView.frame), + height); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_recentCallsTableView.frame) + (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _recentCallsTableView.frame = CGRectMake(CGRectGetMinX(_recentCallsTableView.frame), + CGRectGetMinY(_recentCallsTableView.frame), + CGRectGetWidth(_recentCallsTableView.frame), + height); +} + +@end diff --git a/Signal/src/view controllers/ContactBrowseViewController.h b/Signal/src/view controllers/ContactBrowseViewController.h new file mode 100644 index 000000000..98b3ccf92 --- /dev/null +++ b/Signal/src/view controllers/ContactBrowseViewController.h @@ -0,0 +1,22 @@ +#import +#import "SearchBarTitleView.h" + +/** + * + * ContactBrowseViewController displays contacts from ContactsManager inside of a table view. + * This class subscibes to addressbook updates to refresh information and/or add new contacts. + * + */ + +@interface ContactBrowseViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UITableView *contactTableView; +@property (nonatomic, strong) IBOutlet SearchBarTitleView *searchBarTitleView; +@property (nonatomic, strong) IBOutlet UIView *notificationView; +@property (nonatomic, retain) UIRefreshControl *refreshControl; +@property (nonatomic) NSTimer *refreshTimer; + +- (IBAction)notificationViewTapped:(id)sender; +- (void)showNotificationForNewWhisperUsers:(NSArray *)users; + +@end diff --git a/Signal/src/view controllers/ContactBrowseViewController.m b/Signal/src/view controllers/ContactBrowseViewController.m new file mode 100644 index 000000000..123b8a1a8 --- /dev/null +++ b/Signal/src/view controllers/ContactBrowseViewController.m @@ -0,0 +1,269 @@ +#import "Contact.h" +#import "ContactBrowseViewController.h" +#import "ContactDetailViewController.h" +#import "ContactsManager.h" +#import "ContactTableViewCell.h" +#import "InCallViewController.h" +#import "LocalizableText.h" +#import "PreferencesUtil.h" +#import "TabBarParentViewController.h" +#import "NotificationManifest.h" +#import "PhoneNumberDirectoryFilterManager.h" + +#import +#import + +#define NOTIFICATION_VIEW_ANIMATION_DURATION 0.5f +#define REFRESH_TIMEOUT 20 + +static NSString *const CONTACT_BROWSE_TABLE_CELL_IDENTIFIER = @"ContactTableViewCell"; + +@interface ContactBrowseViewController () { + NSDictionary *_latestAlphabeticalContacts; + NSArray *_latestSortedAlphabeticalContactKeys; + NSArray *_latestContacts; + NSArray *_newWhisperUsers; + CGRect _originalTableViewFrame; + BOOL _showingNotificationView; +} + +@end + +@implementation ContactBrowseViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contactsDidRefresh) name:NOTIFICATION_DIRECTORY_WAS_UPDATED object:nil]; + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] + init]; + [refreshControl addTarget:self action:@selector(refreshContacts) forControlEvents:UIControlEventValueChanged]; + self.refreshControl = refreshControl; + [self.contactTableView addSubview:self.refreshControl]; + + [self setupContacts]; + [self observeKeyboardNotifications]; + _originalTableViewFrame = _contactTableView.frame; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [_contactTableView reloadData]; + [_searchBarTitleView updateAutoCorrectionType]; + [[Environment getCurrent].contactsManager enableNewUserNotifications]; + + BOOL showNotificationView = _newWhisperUsers != nil; + if (showNotificationView) { + [self showNotificationViewAnimated:NO]; + } else { + [self hideNotificationView]; + } +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + self.navigationController.navigationBarHidden = NO; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)onSearchOrContactChange:(NSString *)searchTerm { + if (_latestContacts) { + _latestAlphabeticalContacts = [ContactsManager groupContactsByFirstLetter:_latestContacts + matchingSearchString:searchTerm]; + + NSArray *contactKeys = [_latestAlphabeticalContacts allKeys]; + _latestSortedAlphabeticalContactKeys = [contactKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + [_contactTableView reloadData]; + } +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)notificationViewTapped:(id)sender { + [(TabBarParentViewController *)self.mm_drawerController.centerViewController presentInviteContactsViewController]; +} + +- (void)showNotificationForNewWhisperUsers:(NSArray *)users { + + _newWhisperUsers = users; + + BOOL isViewVisible = [self isViewLoaded] && self.view.window; + + if (isViewVisible) { + [self showNotificationViewAnimated:YES]; + } +} + +- (void)showNotificationViewAnimated:(BOOL)animated { + + if (_showingNotificationView) { + return; + } + + CGFloat animationTime = animated ? NOTIFICATION_VIEW_ANIMATION_DURATION : 0.0f; + + [UIView animateWithDuration:animationTime animations:^{ + _notificationView.frame = CGRectMake(CGRectGetMinX(_notificationView.frame), + CGRectGetHeight(_searchBarTitleView.frame), + CGRectGetWidth(_notificationView.frame), + CGRectGetHeight(_notificationView.frame)); + + CGFloat tableViewYOrigin = CGRectGetMinY(_originalTableViewFrame) + CGRectGetHeight(_notificationView.frame); + _contactTableView.frame = CGRectMake(CGRectGetMinX(_contactTableView.frame), + tableViewYOrigin, + CGRectGetWidth(_contactTableView.frame), + CGRectGetHeight(_contactTableView.frame)); + + }]; + + _showingNotificationView = YES; +} + +- (void)hideNotificationView { + if (!_showingNotificationView) { + return; + } + + _notificationView.frame = CGRectMake(CGRectGetMinX(_notificationView.frame), + 0, + CGRectGetWidth(_notificationView.frame), + CGRectGetHeight(_notificationView.frame)); + _contactTableView.frame = _originalTableViewFrame; + _showingNotificationView = NO; +} + +#pragma mark - Contact functions + +- (void)setupContacts { + ObservableValue *observableContacts = [[[Environment getCurrent] contactsManager] getObservableWhisperUsers]; + + [observableContacts watchLatestValue:^(NSArray *latestContacts) { + _latestContacts = latestContacts; + [self onSearchOrContactChange:nil]; + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +- (NSArray *)contactsForSectionIndex:(NSUInteger)index { + return [_latestAlphabeticalContacts valueForKey:[_latestSortedAlphabeticalContactKeys objectAtIndex:index]]; +} + +- (void)pushContactViewControllerForContactIndexPath:(NSIndexPath *)indexPath { + NSArray *contactSection = [self contactsForSectionIndex:(NSUInteger)indexPath.section]; + Contact *contact = contactSection[(NSUInteger)indexPath.row]; + ContactDetailViewController *vc = [ContactDetailViewController contactDetailViewControllerWithContact:contact]; + [self.navigationController pushViewController:vc animated:YES]; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return (NSInteger)[[self contactsForSectionIndex:(NSUInteger)section] count]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [_latestSortedAlphabeticalContactKeys objectAtIndex:(NSUInteger)section]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return (NSInteger)[[_latestAlphabeticalContacts allKeys] count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + ContactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CONTACT_BROWSE_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[ContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CONTACT_BROWSE_TABLE_CELL_IDENTIFIER]; + } + + NSArray *contactSection = [self contactsForSectionIndex:(NSUInteger)indexPath.section]; + Contact *contact = contactSection[(NSUInteger)indexPath.row]; + [cell configureWithContact:contact]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [_searchBarTitleView.searchTextField resignFirstResponder]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + [self pushContactViewControllerForContactIndexPath:indexPath]; +} + +- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { + return _latestSortedAlphabeticalContactKeys; +} + +#pragma mark - SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term { + [self onSearchOrContactChange:term]; +} + +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft + animated:YES + completion:nil]; +} + +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view { + [self onSearchOrContactChange:nil]; +} + +#pragma mark - Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + _contactTableView.frame = CGRectMake(CGRectGetMinX(_contactTableView.frame), + CGRectGetMinY(_contactTableView.frame), + CGRectGetWidth(_contactTableView.frame), + CGRectGetHeight(_contactTableView.frame) - (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT)); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + _contactTableView.frame = CGRectMake(CGRectGetMinX(_contactTableView.frame), + CGRectGetMinY(_contactTableView.frame), + CGRectGetWidth(_contactTableView.frame), + CGRectGetHeight(_contactTableView.frame) + (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT)); +} + +#pragma mark - Refresh controls + +- (void)refreshContacts{ + [[[Environment getCurrent] phoneDirectoryManager] forceUpdate]; + self.refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMEOUT target:self selector:@selector(contactRefreshDidTimeout) userInfo:nil repeats:NO]; +} + +- (void)contactRefreshDidTimeout{ + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:TIMEOUT message:TIMEOUT_CONTACTS_DETAIL delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil]; + [alert show]; + [self.refreshControl endRefreshing]; +} + +- (void)contactsDidRefresh{ + [self.refreshTimer invalidate]; + [self.refreshControl endRefreshing]; +} + +@end diff --git a/Signal/src/view controllers/ContactDetailViewController.h b/Signal/src/view controllers/ContactDetailViewController.h new file mode 100644 index 000000000..09d574ef3 --- /dev/null +++ b/Signal/src/view controllers/ContactDetailViewController.h @@ -0,0 +1,28 @@ +#import + +#import "Contact.h" +#import "ContactDetailTableViewCell.h" +#import "PhoneNumberDirectoryFilterManager.h" + +/** + * + * ContactDetailViewController displays information about a contact in a table view such as additional non-encryped communication methods. + * Any additional non-encrypted information is opened in an external application (Email, SMS, Phone) + * + */ + +@interface ContactDetailViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UIButton *favouriteButton; +@property (nonatomic, strong) IBOutlet UIView *secureInfoHeaderView; +@property (nonatomic, strong) IBOutlet UILabel *contactNameLabel; +@property (nonatomic, strong) IBOutlet UIImageView *contactImageView; +@property (nonatomic, strong) IBOutlet UITableView *contactInfoTableView; + +@property (nonatomic, readonly) Contact *contact; + ++ (ContactDetailViewController *)contactDetailViewControllerWithContact:(Contact *)contact; + +- (IBAction)favouriteButtonTapped; + +@end diff --git a/Signal/src/view controllers/ContactDetailViewController.m b/Signal/src/view controllers/ContactDetailViewController.m new file mode 100644 index 000000000..238fed757 --- /dev/null +++ b/Signal/src/view controllers/ContactDetailViewController.m @@ -0,0 +1,163 @@ +#import "ContactDetailViewController.h" +#import "ContactsManager.h" +#import "InCallViewController.h" +#import "UIUtil.h" + +#define CONTACT_DETAIL_CELL_HEIGHT 49 + +static NSString *const DEFAULT_CONTACT_IMAGE = @"DefaultContactImage.png"; +static NSString *const DETAIL_TABLE_CELL_IDENTIFIER = @"ContactDetailTableViewCell"; +static NSString *const MAIL_URL_PREFIX = @"mailto://"; + +static NSString *const FAVOURITE_TRUE_ICON_NAME = @"favourite_true_icon"; +static NSString *const FAVOURITE_FALSE_ICON_NAME = @"favourite_false_icon"; + +@implementation ContactDetailViewController + ++ (ContactDetailViewController *)contactDetailViewControllerWithContact:(Contact *)contact { + ContactDetailViewController *contactDetailViewController = [ContactDetailViewController new]; + contactDetailViewController->_contact = contact; + return contactDetailViewController; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + _contactInfoTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)viewWillAppear:(BOOL)animated { + + if (_contact) { + self.navigationController.navigationBar.barTintColor = [UIUtil darkBackgroundColor]; + self.navigationController.navigationBar.tintColor = [UIColor whiteColor]; + self.navigationController.navigationBar.translucent = NO; + _contactNameLabel.text = [_contact fullName]; + if (_contact.image) { + _contactImageView.image = _contact.image; + } + [UIUtil applyRoundedBorderToImageView:&_contactImageView]; + [self configureFavouritesButton]; + [_contactInfoTableView reloadData]; + } + [super viewWillAppear:animated]; +} + +#pragma mark - UITableViewDelegate + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return _secureInfoHeaderView; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return _secureInfoHeaderView.bounds.size.height; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSInteger secureNumberCount = (NSInteger)[_contact.userTextPhoneNumbers count] + (NSInteger)[_contact.emails count]; + return _contact.notes != nil ? secureNumberCount + 1 : secureNumberCount; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + ContactDetailTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DETAIL_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[ContactDetailTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:DETAIL_TABLE_CELL_IDENTIFIER]; + } + + if ((NSUInteger)indexPath.row < [_contact.userTextPhoneNumbers count]) { + + PhoneNumber *phoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:_contact.userTextPhoneNumbers[(NSUInteger)indexPath.row]]; + BOOL isSecure = [[[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter] containsPhoneNumber:phoneNumber]; + [cell configureWithPhoneNumber:phoneNumber isSecure:isSecure]; + + } else if ((NSUInteger)indexPath.row < [_contact.userTextPhoneNumbers count] + [_contact.emails count]) { + + NSUInteger emailIndex = (NSUInteger)indexPath.row - [_contact.userTextPhoneNumbers count]; + [cell configureWithEmailString:_contact.emails[emailIndex]]; + + } else { + [cell configureWithNotes:_contact.notes]; + return cell; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + if (indexPath.row < (NSInteger)[[_contact userTextPhoneNumbers] count]) { + + NSString *numberString = _contact.userTextPhoneNumbers[(NSUInteger)indexPath.row]; + PhoneNumber *number = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:numberString]; + BOOL sercureNumberTapped = [self phoneNumberIsSecure:number]; + + if (sercureNumberTapped) { + [self startSecureCallWithNumber:number]; + } else { + [self openPhoneAppWithPhoneNumber:number]; + } + + } else if ((NSUInteger)indexPath.row < [_contact.userTextPhoneNumbers count] + [_contact.emails count]) { + NSUInteger emailIndex = (NSUInteger)indexPath.row - [_contact.userTextPhoneNumbers count]; + [self openEmailAppWithEmail:_contact.emails[emailIndex]]; + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + + BOOL cellNeedsHeightForText = indexPath.row == (NSInteger)[[_contact userTextPhoneNumbers] count] + (NSInteger)[[_contact emails] count]; + + if (cellNeedsHeightForText) { + CGSize size = [_contact.notes sizeWithAttributes:@{NSFontAttributeName:[UIUtil helveticaRegularWithSize:17]}]; + return size.height + CONTACT_DETAIL_CELL_HEIGHT; + } else { + return CONTACT_DETAIL_CELL_HEIGHT; + } +} + +- (void)favouriteButtonTapped { + [[[Environment getCurrent] contactsManager] toggleFavourite:_contact]; + [self configureFavouritesButton]; +} + +- (void)configureFavouritesButton { + if (_contact.isFavourite) { + UIImage *favouriteImage = [UIImage imageNamed:FAVOURITE_TRUE_ICON_NAME]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:favouriteImage + style:UIBarButtonItemStylePlain + target:self + action:@selector(favouriteButtonTapped)]; + self.navigationItem.rightBarButtonItem.tintColor = [UIColor yellowColor]; + } else { + UIImage *favouriteImage = [UIImage imageNamed:FAVOURITE_FALSE_ICON_NAME]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:favouriteImage + style:UIBarButtonItemStylePlain + target:self + action:@selector(favouriteButtonTapped)]; + self.navigationItem.rightBarButtonItem.tintColor = [UIColor whiteColor]; + } +} + +- (void)openPhoneAppWithPhoneNumber:(PhoneNumber *)phoneNumber { + if (phoneNumber) { + [[UIApplication sharedApplication] openURL:[phoneNumber toUrl]]; + } +} + +- (void)openEmailAppWithEmail:(NSString *)email { + NSString *mailURL = [NSString stringWithFormat:@"%@%@",MAIL_URL_PREFIX, email]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailURL]]; +} + +- (void)startSecureCallWithNumber:(PhoneNumber *)number { + [[Environment phoneManager] initiateOutgoingCallToContact:_contact atRemoteNumber:number]; +} + +- (BOOL)phoneNumberIsSecure:(PhoneNumber *)phoneNumber { + PhoneNumberDirectoryFilter* directory = [[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter]; + return phoneNumber != nil && [directory containsPhoneNumber:phoneNumber]; +} + +@end diff --git a/Signal/src/view controllers/CountryCodeViewController.h b/Signal/src/view controllers/CountryCodeViewController.h new file mode 100644 index 000000000..142c54676 --- /dev/null +++ b/Signal/src/view controllers/CountryCodeViewController.h @@ -0,0 +1,23 @@ +#import + +@class CountryCodeViewController; + +@protocol CountryCodeViewControllerDelegate + +- (void)countryCodeViewController:(CountryCodeViewController *)vc + didSelectCountryCode:(NSString *)code + forCountry:(NSString *)country; + +- (void)countryCodeViewControllerDidCancel:(CountryCodeViewController *)vc; + +@end + +@interface CountryCodeViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UITableView *countryCodeTableView; +@property (nonatomic, strong) IBOutlet UISearchBar *searchBar; +@property (nonatomic, assign) id delegate; + +- (IBAction)cancelTapped:(id)sender; + +@end diff --git a/Signal/src/view controllers/CountryCodeViewController.m b/Signal/src/view controllers/CountryCodeViewController.m new file mode 100644 index 000000000..32b918aae --- /dev/null +++ b/Signal/src/view controllers/CountryCodeViewController.m @@ -0,0 +1,74 @@ + +#import "CountryCodeViewController.h" +#import "CountryCodeTableViewCell.h" +#import "NBPhoneNumberUtil.h" +#import "PhoneNumber.h" +#import "PhoneNumberUtil.h" + +static NSString *const CONTRY_CODE_TABLE_CELL_IDENTIFIER = @"CountryCodeTableViewCell"; + +@interface CountryCodeViewController () { + NSArray *_countryCodes; +} + +@end + +@implementation CountryCodeViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + _countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +#pragma mark - Actions + +- (IBAction)cancelTapped:(id)sender { + [_delegate countryCodeViewControllerDidCancel:self]; +} + + +#pragma mark - UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return (NSInteger)[_countryCodes count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + CountryCodeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CONTRY_CODE_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[CountryCodeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CONTRY_CODE_TABLE_CELL_IDENTIFIER]; + } + + NSString *countryCode = _countryCodes[(NSUInteger)indexPath.row]; + NSString *callingCode = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; + NSString *countryName = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; + [cell configureWithCountryCode:callingCode andCountryName:countryName]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + + NSString *countryCode = _countryCodes[(NSUInteger)indexPath.row]; + NSString *callingCode = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; + NSString *countryName = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; + + [_delegate countryCodeViewController:self + didSelectCountryCode:callingCode + forCountry:countryName]; +} + +#pragma mark - UISearchBarDelegate + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + _countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText]; + [_countryCodeTableView reloadData]; +} + +@end diff --git a/Signal/src/view controllers/CountryCodeViewController.xib b/Signal/src/view controllers/CountryCodeViewController.xib new file mode 100644 index 000000000..3c2bd1389 --- /dev/null +++ b/Signal/src/view controllers/CountryCodeViewController.xib @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/DialerViewController.h b/Signal/src/view controllers/DialerViewController.h new file mode 100644 index 000000000..61e8881e7 --- /dev/null +++ b/Signal/src/view controllers/DialerViewController.h @@ -0,0 +1,34 @@ +#import + +#import "DialerButtonView.h" +#import "InteractiveLabel.h" +#import "PhoneNumber.h" + +@interface DialerViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UIButton *backspaceButton; +@property (nonatomic, strong) IBOutlet InteractiveLabel *numberLabel; +@property (nonatomic, strong) IBOutlet UIButton *callButton; +@property (nonatomic, strong) IBOutlet UIButton *addContactButton; +@property (nonatomic, strong) IBOutlet UIImageView *matchedContactImageView; + +@property (nonatomic, strong) IBOutlet DialerButtonView *button0; +@property (nonatomic, strong) IBOutlet DialerButtonView *button1; +@property (nonatomic, strong) IBOutlet DialerButtonView *button2; +@property (nonatomic, strong) IBOutlet DialerButtonView *button3; +@property (nonatomic, strong) IBOutlet DialerButtonView *button4; +@property (nonatomic, strong) IBOutlet DialerButtonView *button5; +@property (nonatomic, strong) IBOutlet DialerButtonView *button6; +@property (nonatomic, strong) IBOutlet DialerButtonView *button7; +@property (nonatomic, strong) IBOutlet DialerButtonView *button8; +@property (nonatomic, strong) IBOutlet DialerButtonView *button9; +@property (nonatomic, strong) IBOutlet DialerButtonView *buttonStar; +@property (nonatomic, strong) IBOutlet DialerButtonView *buttonPound; + +@property (nonatomic, assign) PhoneNumber *phoneNumber; + +- (IBAction)callButtonTapped; +- (IBAction)backspaceButtonTouchDown; +- (IBAction)backspaceButtonTouchUp; + +@end diff --git a/Signal/src/view controllers/DialerViewController.m b/Signal/src/view controllers/DialerViewController.m new file mode 100644 index 000000000..a21989b29 --- /dev/null +++ b/Signal/src/view controllers/DialerViewController.m @@ -0,0 +1,194 @@ +#import "DialerViewController.h" + +#import + +#import "ContactsManager.h" +#import "Environment.h" +#import "InCallViewController.h" +#import "InviteContactModal.h" +#import "LocalizableText.h" +#import "PhoneManager.h" +#import "PhoneNumberDirectoryFilter.h" +#import "PhoneNumberUtil.h" +#import "RecentCallManager.h" + +#define INITIAL_BACKSPACE_TIMER_DURATION 0.5f +#define BACKSPACE_TIME_DECREASE_AMMOUNT 0.1f +#define FOUND_CONTACT_ANIMATION_DURATION 0.25f + +#define E164_PREFIX @"+" + +@interface DialerViewController () { + NSMutableString *_currentNumberMutable; + Contact *_contact; + NSTimer *_backspaceTimer; + float _backspaceDuration; + + InviteContactModal* inviteModal; +} + +@end + +@implementation DialerViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupPasteBehaviour]; + self.title = KEYPAD_NAV_BAR_TITLE; + _currentNumberMutable = [NSMutableString string]; + [self updateNumberLabel]; + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [_callButton setTitle:CALL_BUTTON_TITLE forState:UIControlStateNormal]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + if (_phoneNumber) { + _currentNumberMutable = [[_phoneNumber toE164] mutableCopy]; + [self updateNumberLabel]; + } +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + _phoneNumber = nil; +} + +- (void)setupPasteBehaviour { + [self.numberLabel onPaste:^(id sender) { + + [UIPasteboard generalPasteboard]; + if([[UIPasteboard generalPasteboard] containsPasteboardTypes:UIPasteboardTypeListString]){ + [_currentNumberMutable setString:[self sanitizePhoneNumberFromUnknownSource:[[UIPasteboard generalPasteboard] string]]]; + [self updateNumberLabel]; + } + }]; +} + +-(NSString*) sanitizePhoneNumberFromUnknownSource:(NSString*) dirtyNumber { + NSString* cleanNumber = [PhoneNumberUtil normalizePhoneNumber:dirtyNumber]; + + if ([dirtyNumber hasPrefix:E164_PREFIX]) { + cleanNumber = [NSString stringWithFormat:@"%@%@",E164_PREFIX,cleanNumber]; + } + + return cleanNumber; +} + +#pragma mark - DialerButtonViewDelegate + +- (void)dialerButtonViewDidSelect:(DialerButtonView *)view { + [_currentNumberMutable appendString:view.buttonInput]; + [self updateNumberLabel]; +} + +#pragma mark - Actions + +- (void)backspaceButtonTouchDown { + _backspaceDuration = INITIAL_BACKSPACE_TIMER_DURATION; + [self removeLastDigit]; +} + +- (void)backspaceButtonTouchUp { + [_backspaceTimer invalidate]; + _backspaceTimer = nil; +} + +- (void)removeLastDigit { + NSUInteger n = [_currentNumberMutable length]; + if (n > 0) { + [_currentNumberMutable deleteCharactersInRange:NSMakeRange(n - 1, 1)]; + } + [self updateNumberLabel]; + + _backspaceDuration -= BACKSPACE_TIME_DECREASE_AMMOUNT; + + _backspaceTimer = [NSTimer scheduledTimerWithTimeInterval:_backspaceDuration + target:self + selector:@selector(removeLastDigit) + userInfo:nil + repeats:NO]; +} + +- (void)callButtonTapped { + PhoneNumber *phoneNumber = [self phoneNumberForCurrentInput]; + + BOOL shouldTryCall = [[[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter] containsPhoneNumber:phoneNumber] || [[Environment getCurrent].recentCallManager isPhoneNumberPresentInRecentCalls:phoneNumber]; + + if( shouldTryCall){ + [self initiateCallToPhoneNumber:phoneNumber]; + }else if([phoneNumber isValid]){ + [self promptToInvitePhoneNumber:phoneNumber]; + } +} + +-(void) initiateCallToPhoneNumber:(PhoneNumber*) phoneNumber { + if (_contact) { + [[Environment phoneManager] initiateOutgoingCallToContact:_contact + atRemoteNumber:phoneNumber]; + } else { + [[Environment phoneManager] initiateOutgoingCallToRemoteNumber:phoneNumber]; + } +} + +- (PhoneNumber *)phoneNumberForCurrentInput { + NSString *numberText = [_currentNumberMutable copy]; + + if ([numberText length]> 0 && [[numberText substringToIndex:1] isEqualToString:COUNTRY_CODE_PREFIX]) { + return [PhoneNumber tryParsePhoneNumberFromE164:numberText]; + } else { + return [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:numberText]; + } +} + +- (void)updateNumberLabel { + NSString* numberText = [_currentNumberMutable copy]; + _numberLabel.text = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:numberText]; + PhoneNumber* number = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:numberText]; + [self tryUpdateContactForNumber:number]; +} + +- (void)tryUpdateContactForNumber:(PhoneNumber *)number { + if (number) { + _contact = [[[Environment getCurrent] contactsManager] latestContactForPhoneNumber:number]; + } else { + _contact = nil; + } + + if (_contact) { + if (_contact.image) { + _matchedContactImageView.alpha = 0.0f; + _matchedContactImageView.image = _contact.image; + [UIUtil applyRoundedBorderToImageView:&_matchedContactImageView]; + [UIView animateWithDuration:FOUND_CONTACT_ANIMATION_DURATION animations:^{ + _matchedContactImageView.alpha = 1.0f; + }]; + + } else { + [self removeContactImage]; + } + + [_addContactButton setTitle:[_contact fullName] forState:UIControlStateNormal]; + + } else { + [_addContactButton setTitle:@"" forState:UIControlStateNormal]; + [self removeContactImage]; + } +} + +- (void)removeContactImage { + [UIView animateWithDuration:FOUND_CONTACT_ANIMATION_DURATION animations:^{ + _matchedContactImageView.alpha = 0.0f; + } completion:^(BOOL finished) { + [UIUtil removeRoundedBorderToImageView:&_matchedContactImageView]; + _matchedContactImageView.image = nil; + }]; +} + +-(void) promptToInvitePhoneNumber:(PhoneNumber*) phoneNumber { + inviteModal = [InviteContactModal inviteContactModelWithPhoneNumber:phoneNumber andParentViewController:self]; + [inviteModal presentModalView]; +} + + +@end diff --git a/Signal/src/view controllers/FavouritesViewController.h b/Signal/src/view controllers/FavouritesViewController.h new file mode 100644 index 000000000..26c1fdb40 --- /dev/null +++ b/Signal/src/view controllers/FavouritesViewController.h @@ -0,0 +1,16 @@ +#import +#import "SearchBarTitleView.h" +#import "FavouriteTableViewCell.h" + +/** + * + * FavouritesViewController displays a table view of favourites obtained through the ContactsManager + * + */ + +@interface FavouritesViewController : UIViewController + +@property (nonatomic, strong) IBOutlet SearchBarTitleView *searchBarTitleView; +@property (nonatomic, strong) IBOutlet UITableView *favouriteTableView; + +@end diff --git a/Signal/src/view controllers/FavouritesViewController.m b/Signal/src/view controllers/FavouritesViewController.m new file mode 100644 index 000000000..d5d2c125c --- /dev/null +++ b/Signal/src/view controllers/FavouritesViewController.m @@ -0,0 +1,180 @@ +#import "FavouritesViewController.h" +#import "Environment.h" +#import "ContactDetailViewController.h" +#import "ContactsManager.h" +#import "LocalizableText.h" +#import "FunctionalUtil.h" +#import "PreferencesUtil.h" +#import "TabBarParentViewController.h" + +#import "UIViewController+MMDrawerController.h" + +static NSString *const CONTACT_TABLE_VIEW_CELL_IDENTIFIER = @"ContactTableViewCell"; + +@interface FavouritesViewController () { + NSArray *_favourites; + NSArray *_searchFavourites; + BOOL _isSearching; +} + +@end + +@implementation FavouritesViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self observeLatestFavourites]; + [self observeKeyboardNotifications]; + _favouriteTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBarHidden = YES; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + self.navigationController.navigationBarHidden = NO; +} + +- (void)observeLatestFavourites { + ObservableValue *observableFavourites = [[[Environment getCurrent] contactsManager] getObservableFavourites]; + + [observableFavourites watchLatestValue:^(NSArray *latestFavourites) { + _favourites = latestFavourites; + [_favouriteTableView reloadData]; + [self hideTableViewIfNoFavourites]; + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +- (void)hideTableViewIfNoFavourites { + BOOL hideFavourites = [_favourites count] == 0; + _favouriteTableView.hidden = hideFavourites; +} + +- (void)openLeftSideMenu { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft animated:YES completion:nil]; +} + +- (void)pushContactDetailViewControllerWithContact:(Contact *)contact { + ContactDetailViewController *vc = [ContactDetailViewController contactDetailViewControllerWithContact:contact]; + [self.navigationController pushViewController:vc animated:YES]; +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)favouriteTapped:(Contact *)contact { + + PhoneNumberDirectoryFilter *filter = [[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter]; + + for (PhoneNumber *number in contact.parsedPhoneNumbers) { + if ([filter containsPhoneNumber:number]) { + [(TabBarParentViewController *)self.mm_drawerController.centerViewController showDialerViewControllerWithNumber:number]; + return; + } + } + + [[UIApplication sharedApplication] openURL:[contact.parsedPhoneNumbers[0] toUrl]]; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return _isSearching ? (NSInteger)[_searchFavourites count] : (NSInteger)[_favourites count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + FavouriteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CONTACT_TABLE_VIEW_CELL_IDENTIFIER]; + if (!cell) { + cell = [[FavouriteTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CONTACT_TABLE_VIEW_CELL_IDENTIFIER]; + cell.delegate = self; + } + + Contact *contact = _isSearching ? _searchFavourites[(NSUInteger)indexPath.row] : _favourites[(NSUInteger)indexPath.row]; + [cell configureWithContact:contact]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + [_searchBarTitleView.searchTextField resignFirstResponder]; + Contact *contact = _isSearching ? _searchFavourites[(NSUInteger)indexPath.row] : _favourites[(NSUInteger)indexPath.row]; + [self pushContactDetailViewControllerWithContact:contact]; +} + +#pragma mark - SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term { + _isSearching = YES; + _searchFavourites = [self favouritesForSearchTerm:term]; + [_favouriteTableView reloadData]; +} + +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft + animated:YES + completion:nil]; +} + +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view { + _isSearching = NO; + _searchFavourites = nil; + [_favouriteTableView reloadData]; +} + +- (NSArray *)favouritesForSearchTerm:(NSString *)searchTerm { + return [_favourites filter:^int(Contact *contact) { + return [searchTerm length] == 0 || [ContactsManager name:[contact fullName] matchesQuery:searchTerm]; + }]; +} + +#pragma mark - FavouriteTableViewCellDelegate + +- (void)favouriteTableViewCellTappedCall:(FavouriteTableViewCell *)cell { + NSIndexPath *indexPath = [_favouriteTableView indexPathForCell:cell]; + Contact *contact = _favourites[(NSUInteger)indexPath.row]; + [self favouriteTapped:contact]; +} + +#pragma mark - Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_favouriteTableView.frame) - (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _favouriteTableView.frame = CGRectMake(CGRectGetMinX(_favouriteTableView.frame), + CGRectGetMinY(_favouriteTableView.frame), + CGRectGetWidth(_favouriteTableView.frame), + height); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_favouriteTableView.frame) + (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _favouriteTableView.frame = CGRectMake(CGRectGetMinX(_favouriteTableView.frame), + CGRectGetMinY(_favouriteTableView.frame), + CGRectGetWidth(_favouriteTableView.frame), + height); +} + +@end diff --git a/Signal/src/view controllers/InCallViewController.h b/Signal/src/view controllers/InCallViewController.h new file mode 100644 index 000000000..83cd8c49b --- /dev/null +++ b/Signal/src/view controllers/InCallViewController.h @@ -0,0 +1,40 @@ +#import + +#import "Contact.h" +#import "PhoneManager.h" +#import "PhoneNumber.h" +#import "PhoneNumberDirectoryFilterManager.h" + +@interface InCallViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UILabel *nameLabel; +@property (nonatomic, strong) IBOutlet UILabel *phoneNumberLabel; +@property (nonatomic, strong) IBOutlet UILabel *callStatusLabel; +@property (nonatomic, strong) IBOutlet UIImageView *contactImageView; +@property (nonatomic, strong) IBOutlet UIImageView *connectingIndicatorImageView; +@property (nonatomic, strong) IBOutlet UILabel *authenicationStringLabel; +@property (nonatomic, strong) IBOutlet UIView *verticalSpinnerAlignmentView; +@property (nonatomic, strong) IBOutlet UIView *callStateImageContainerView; + +@property (nonatomic, strong) IBOutlet UIButton *muteButton; +@property (nonatomic, strong) IBOutlet UIButton *speakerButton; + +@property (nonatomic, strong) IBOutlet UIButton *answerButton; +@property (nonatomic, strong) IBOutlet UIButton *rejectButton; + +@property (nonatomic, strong) IBOutlet UIButton *endButton; + +@property (nonatomic, readonly) CallState *callState; +@property (nonatomic, readonly) Contact *potentiallyKnownContact; + ++(InCallViewController*) inCallViewControllerWithCallState:(CallState*)callState + andOptionallyKnownContact:(Contact*)contact; + +- (IBAction)endCallTapped; +- (IBAction)muteButtonTapped; +- (IBAction)speakerButtonTapped; + +- (IBAction)answerButtonTapped; +- (IBAction)rejectButtonTapped; + +@end diff --git a/Signal/src/view controllers/InCallViewController.m b/Signal/src/view controllers/InCallViewController.m new file mode 100644 index 000000000..0528fb2d7 --- /dev/null +++ b/Signal/src/view controllers/InCallViewController.m @@ -0,0 +1,304 @@ +#import "AppAudioManager.h" +#import "CallFailedServerMessage.h" +#import "InCallViewController.h" +#import "LocalizableText.h" +#import "RecentCallManager.h" +#import "Util.h" +#import "CallAudioManager.h" +#import "PhoneManager.h" + +#import + +#define BUTTON_BORDER_WIDTH 1.0f +#define CONTACT_IMAGE_BORDER_WIDTH 2.0f +#define RINGING_ROTATION_DURATION 0.375f +#define CONNECTING_FLASH_DURATION 0.5f +#define END_CALL_CLEANUP_DELAY (int)(3.1f * NSEC_PER_SEC) + +static NSString *const SPINNER_CONNECTING_IMAGE_NAME = @"spinner_connecting"; +static NSString *const SPINNER_CONNECTING_FLASH_IMAGE_NAME = @"spinner_connecting_flash"; +static NSString *const SPINNER_RINGING_IMAGE_NAME = @"spinner_ringing"; +static NSString *const SPINNER_ERROR_FLASH_IMAGE_NAME = @"spinner_error"; + +static NSInteger connectingFlashCounter = 0; + + +@interface InCallViewController () { + BOOL _isMusicPaused; + CallAudioManager *_callAudioManager; + NSTimer *_connectingFlashTimer; + NSTimer *_ringingAnimationTimer; +} + +@end + +@implementation InCallViewController + ++(InCallViewController*) inCallViewControllerWithCallState:(CallState*)callState + andOptionallyKnownContact:(Contact*)contact { + require(callState != nil); + + InCallViewController* controller = [InCallViewController new]; + controller->_potentiallyKnownContact = contact; + controller->_callState = callState; + return controller; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self showCallState]; + [self pauseMusicIfPlaying]; + [self setupButtonBorders]; + [self localizeButtons]; + [[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self startConnectingFlashAnimation]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self stopRingingAnimation]; + [self stopConnectingFlashAnimation]; + [[AppAudioManager sharedInstance] cancellAllAudio]; +} + +- (void)dealloc { + [[UIDevice currentDevice] setProximityMonitoringEnabled:NO]; +} + +-(void) showCallState { + [self clearDetails]; + [self populateImmediateDetails]; + [self handleIncomingDetails]; +} + +- (void)pauseMusicIfPlaying { + if ([[MPMusicPlayerController iPodMusicPlayer] playbackState] == MPMusicPlaybackStatePlaying) { + _isMusicPaused = YES; + [[MPMusicPlayerController iPodMusicPlayer] pause]; + } +} + +- (void)startConnectingFlashAnimation { + if(![_ringingAnimationTimer isValid]){ + _connectingFlashTimer = [NSTimer scheduledTimerWithTimeInterval:CONNECTING_FLASH_DURATION + target:self + selector:@selector(flashConnectingIndicator) + userInfo:nil + repeats:YES]; + } +} + +- (void)flashConnectingIndicator { + + NSString *newImageName; + + if (connectingFlashCounter % 2 == 0) { + newImageName = SPINNER_CONNECTING_IMAGE_NAME; + } else { + newImageName = SPINNER_CONNECTING_FLASH_IMAGE_NAME; + } + + [_connectingIndicatorImageView setImage:[UIImage imageNamed:newImageName]]; + connectingFlashCounter++; +} + +- (void)startRingingAnimation { + [self stopConnectingFlashAnimation]; + _ringingAnimationTimer = [NSTimer scheduledTimerWithTimeInterval:RINGING_ROTATION_DURATION + target:self + selector:@selector(rotateConnectingIndicator) + userInfo:nil + repeats:YES]; + [_ringingAnimationTimer fire]; +} + +- (void)rotateConnectingIndicator { + [_connectingIndicatorImageView setImage:[UIImage imageNamed:SPINNER_RINGING_IMAGE_NAME]]; + [UIView animateWithDuration:RINGING_ROTATION_DURATION delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _connectingIndicatorImageView.transform = CGAffineTransformRotate(_connectingIndicatorImageView.transform, M_PI_2); + } completion:nil]; +} + +- (void)performCallInSessionAnimation { + [UIView animateWithDuration:0.5f animations:^{ + [_callStateImageContainerView setFrame:CGRectMake(0, _callStateImageContainerView.frame.origin.y, _callStateImageContainerView.frame.size.width, _callStateImageContainerView.frame.size.height)]; + }]; +} + +- (void)stopRingingAnimation { + if (_ringingAnimationTimer) { + [_ringingAnimationTimer invalidate]; + } +} + +- (void)stopConnectingFlashAnimation { + if (_connectingFlashTimer) { + [_connectingFlashTimer invalidate]; + } +} + +- (void)showConnectingError { + [self stopRingingAnimation]; + [self stopConnectingFlashAnimation]; + [_connectingIndicatorImageView setImage:[UIImage imageNamed:SPINNER_ERROR_FLASH_IMAGE_NAME]]; +} + +- (void)localizeButtons { + [_endButton setTitle:END_CALL_BUTTON_TITLE forState:UIControlStateNormal]; + [_answerButton setTitle:ANSWER_CALL_BUTTON_TITLE forState:UIControlStateNormal]; + [_rejectButton setTitle:REJECT_CALL_BUTTON_TITLE forState:UIControlStateNormal]; +} + +- (void)setupButtonBorders { + _muteButton.layer.borderColor = [UIUtil blueColor].CGColor; + _speakerButton.layer.borderColor = [UIUtil blueColor].CGColor; + _muteButton.layer.borderWidth = BUTTON_BORDER_WIDTH; + _speakerButton.layer.borderWidth = BUTTON_BORDER_WIDTH; + + if (_potentiallyKnownContact) { + + if (_potentiallyKnownContact.image) { + [UIUtil applyRoundedBorderToImageView:&_contactImageView]; + } + + _nameLabel.text = [_potentiallyKnownContact fullName]; + } else { + _nameLabel.text = UNKNOWN_CONTACT_NAME; + } +} + +-(void) clearDetails { + _callStatusLabel.text = @""; + _nameLabel.text = @""; + _phoneNumberLabel.text = @""; + _authenicationStringLabel.text = @""; + _contactImageView.image = nil; + _authenicationStringLabel.hidden = YES; + [self displayAcceptRejectButtons:NO]; +} + +-(void) populateImmediateDetails { + _phoneNumberLabel.text = [_callState.remoteNumber localizedDescriptionForUser]; + + if (_potentiallyKnownContact) { + _nameLabel.text = [_potentiallyKnownContact fullName]; + if (_potentiallyKnownContact.image) { + _contactImageView.image = _potentiallyKnownContact.image; + } + } +} +-(void) handleIncomingDetails { + [[_callState futureShortAuthenticationString] thenDo:^(NSString* sas) { + _authenicationStringLabel.hidden = NO; + _authenicationStringLabel.text = sas; + [self performCallInSessionAnimation]; + }]; + + [[_callState observableProgress] watchLatestValue:^(CallProgress* latestProgress) { + [self onCallProgressed:latestProgress]; + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +-(void) onCallProgressed:(CallProgress*)latestProgress { + BOOL showAcceptRejectButtons = !_callState.initiatedLocally && [latestProgress type] <= CallProgressType_Ringing; + [self displayAcceptRejectButtons:showAcceptRejectButtons]; + [[AppAudioManager sharedInstance] respondToProgressChange:[latestProgress type] + forLocallyInitiatedCall:_callState.initiatedLocally]; + + if ([latestProgress type] == CallProgressType_Ringing) { + [self startRingingAnimation]; + } + + if ([latestProgress type] == CallProgressType_Terminated) { + [[_callState futureTermination] thenDo:^(CallTermination* termination) { + [self onCallEnded:termination]; + [[AppAudioManager sharedInstance] respondToTerminationType:[termination type]]; + }]; + } else { + _callStatusLabel.text = [latestProgress localizedDescriptionForUser]; + } +} + +-(void) onCallEnded:(CallTermination*)termination { + [self updateViewForTermination:termination]; + [[Environment phoneManager] hangupOrDenyCall]; + + [self dismissViewWithOptionalDelay: [termination type] != CallTerminationType_ReplacedByNext ]; + + if (_isMusicPaused) { + [[MPMusicPlayerController iPodMusicPlayer] play]; + } +} + +- (void)endCallTapped { + [[Environment phoneManager] hangupOrDenyCall]; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)muteButtonTapped { + _muteButton.selected = [[Environment phoneManager] toggleMute]; +} + +- (void)speakerButtonTapped { + _speakerButton.selected = [[AppAudioManager sharedInstance] toggleSpeakerPhone]; +} + +- (void)answerButtonTapped { + [self displayAcceptRejectButtons:NO]; + [[Environment phoneManager] answerCall]; +} + +- (void)rejectButtonTapped { + [self displayAcceptRejectButtons:NO]; + [[Environment phoneManager] hangupOrDenyCall]; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +-(void) updateViewForTermination:(CallTermination*) termination{ + NSString* message = [termination localizedDescriptionForUser]; + + if ([termination type] == CallTerminationType_ServerMessage) { + CallFailedServerMessage* serverMessage = [termination messageInfo]; + // @todo: forcing it to be a prefix is not particularly localizable + message = [message stringByAppendingString:[serverMessage text]]; + } + + _endButton.backgroundColor = [UIColor grayColor]; + _callStatusLabel.textColor = [UIColor redColor]; + + [self showConnectingError]; + _callStatusLabel.text = message; +} + +-(void) dismissViewWithOptionalDelay:(BOOL) useDelay { + if(useDelay && UIApplicationStateActive == [[UIApplication sharedApplication] applicationState]){ + [self dismissViewControllerAfterDelay:END_CALL_CLEANUP_DELAY]; + }else{ + [self dismissViewControllerAnimated:NO completion:nil]; + } +} + +-(void) dismissViewControllerAfterDelay:(int) delay { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ + [self dismissViewControllerAnimated:YES completion:nil]; + }); +} + +-(void) displayAcceptRejectButtons:(BOOL) enable{ + _answerButton.hidden = !enable; + _rejectButton.hidden = !enable; + _endButton.hidden = enable; + +} + + + + + + + +@end diff --git a/Signal/src/view controllers/InboxFeedViewController.h b/Signal/src/view controllers/InboxFeedViewController.h new file mode 100644 index 000000000..a1369cae1 --- /dev/null +++ b/Signal/src/view controllers/InboxFeedViewController.h @@ -0,0 +1,27 @@ +#import + +#import "ContactsManager.h" +#import "InboxFeedTableViewCell.h" +#import "SearchBarTitleView.h" + +/** + * + * InboxFeedViewController is the first view the user sees after they have registered + * The search box searches items in your inbox, and contacts. + * A tutorial is displayed if the user has never made a call. + * This class is subscribed to the inbox feed table view cell delegate which tells us when to delete/archive items. + * + */ + +@interface InboxFeedViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UITableView *inboxFeedTableView; +@property (nonatomic, strong) IBOutlet SearchBarTitleView *searchBarTitleView; +@property (nonatomic, strong) IBOutlet UIView *freshInboxView; +@property (nonatomic, strong) IBOutlet UILabel *freshAppTutorialTopLabel; +@property (nonatomic, strong) IBOutlet UILabel *freshAppTutorialMiddleLabel; + + +@property (nonatomic, assign) FutureSource *apnId; + +@end diff --git a/Signal/src/view controllers/InboxFeedViewController.m b/Signal/src/view controllers/InboxFeedViewController.m new file mode 100644 index 000000000..97e8152cb --- /dev/null +++ b/Signal/src/view controllers/InboxFeedViewController.m @@ -0,0 +1,423 @@ +#import "ContactDetailViewController.h" +#import "ContactTableViewCell.h" +#import "Environment.h" +#import "InboxFeedFooterCell.h" +#import "InboxFeedViewController.h" +#import "LeftSideMenuViewController.h" +#import "LocalizableText.h" +#import "PreferencesUtil.h" +#import "RecentCall.h" +#import "RecentCallManager.h" +#import "RegisterViewController.h" + +#import + +#define CONTACT_TABLE_VIEW_CELL_HEIGHT 44 +#define FOOTER_CELL_HEIGHT 44 +#define INBOX_TABLE_VIEW_CELL_HEIGHT 71 + +#define SEARCH_TABLE_SECTION_FEED 0 +#define SEARCH_TABLE_SECTION_REGISTERED 1 +#define SEARCH_TABLE_SECTION_UNREGISTERED 2 + +#define TABLE_VIEW_NUM_SECTIONS_DEFAULT 1 +#define TABLE_VIEW_NUM_SECTIONS_SEARCHING 3 + +static NSString *const INBOX_FEED_TABLE_VIEW_CELL_IDENTIFIER = @"InboxFeedTableViewCell"; +static NSString *const CONTACT_TABLE_VIEW_CELL_IDENTIFIER = @"ContactTableViewCell"; +static NSString *const FOOTER_TABLE_CELL_IDENTIFIER = @"InboxFeedFooterCell"; + +@interface InboxFeedViewController () { + NSArray *_inboxFeed; + BOOL _tableViewContentMutating; + BOOL _isSearching; + + NSArray *_searchInboxFeed; + NSArray *_searchRegisteredContacts; + NSArray *_searchUnregisteredContacts; +} + +@end + +@implementation InboxFeedViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self observeRecentCalls]; + [self observeKeyboardNotifications]; + [self setupLabelLocalizationAndStyles]; + + if (![[Environment preferences] getIsRegistered]) { + RegisterViewController *registerViewController = [RegisterViewController registerViewControllerForApn:_apnId]; + [self presentViewController:registerViewController animated:NO completion:nil]; + } + _inboxFeedTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self markMissedCallsAsViewed]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [_searchBarTitleView updateAutoCorrectionType]; + [_inboxFeedTableView reloadData]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)observeRecentCalls { + ObservableValue *observableContacts = [[[Environment getCurrent] contactsManager] getObservableContacts]; + + [observableContacts watchLatestValue:^(id latestValue) { + + ObservableValue *observableRecents = [[[Environment getCurrent] recentCallManager] getObservableRecentCalls]; + + [observableRecents watchLatestValue:^(NSArray *latestRecents) { + _inboxFeed = [[[Environment getCurrent] recentCallManager] recentsForSearchString:nil + andExcludeArchived:YES]; + [self updateTutorialVisibility]; + if (!_tableViewContentMutating) { + [_inboxFeedTableView reloadData]; + } + if (_isSearching) { + [_searchBarTitleView textField:_searchBarTitleView.searchTextField + shouldChangeCharactersInRange:NSMakeRange(0, 0) + replacementString:SEARCH_BAR_DEFAULT_EMPTY_STRING]; + } + } onThread:[NSThread mainThread] untilCancelled:nil]; + + + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)setupLabelLocalizationAndStyles { + _freshAppTutorialTopLabel.text = INBOX_VIEW_TUTORIAL_LABEL_TOP; + _freshAppTutorialMiddleLabel.text = INBOX_VIEW_TUTORIAL_LABEL_MIDDLE; +} + +#pragma mark - Viewed / Unviewed calls + +- (void)markMissedCallsAsViewed { + BOOL needsSave = NO; + + for (RecentCall *recent in _inboxFeed) { + if (!recent.userNotified) { + recent.userNotified = true; + needsSave = true; + } + } + if (needsSave) { + [[[Environment getCurrent] recentCallManager] saveContactsToDefaults]; + [(TabBarParentViewController *)self.mm_drawerController.centerViewController updateMissedCallCountLabel]; + [_inboxFeedTableView reloadData]; + } +} + +#pragma mark - Actions + +- (void)showRecentCallViewControllerWithRecentCall:(RecentCall *)recent { + [(TabBarParentViewController *)self.mm_drawerController.centerViewController showDialerViewControllerWithNumber:recent.phoneNumber]; +} + +- (void)showContactViewControllerWithContact:(Contact *)contact { + ContactDetailViewController *vc = [ContactDetailViewController contactDetailViewControllerWithContact:contact]; + [self.navigationController pushViewController:vc animated:YES]; +} + +- (void)removeNewsFeedCell:(InboxFeedTableViewCell *)cell willDelete:(BOOL)delete { + _tableViewContentMutating = YES; + NSIndexPath *indexPath = [_inboxFeedTableView indexPathForCell:cell]; + + [_inboxFeedTableView beginUpdates]; + + RecentCall *recent; + + if (_isSearching) { + recent = _searchInboxFeed[(NSUInteger)indexPath.row]; + } else { + recent = _inboxFeed[(NSUInteger)indexPath.row]; + } + + recent.userNotified = YES; + + UITableViewRowAnimation animation; + + if (delete) { + animation = UITableViewRowAnimationLeft; + [[[Environment getCurrent] recentCallManager] removeRecentCall:recent]; + } else { + animation = UITableViewRowAnimationRight; + [[[Environment getCurrent] recentCallManager] archiveRecentCall:recent]; + } + + [_inboxFeedTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:animation]; + + [_inboxFeedTableView endUpdates]; + _tableViewContentMutating = NO; +} + +- (void)updateTutorialVisibility { + _freshInboxView.hidden = ![[[Environment getCurrent] preferences] getFreshInstallTutorialsEnabled]; + _inboxFeedTableView.hidden = !_freshInboxView.hidden; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + if (_isSearching) { + return TABLE_VIEW_NUM_SECTIONS_SEARCHING; + } else { + return TABLE_VIEW_NUM_SECTIONS_DEFAULT; + } +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + if (section == SEARCH_TABLE_SECTION_FEED) { + return @""; + } else if (section == SEARCH_TABLE_SECTION_REGISTERED) { + return TABLE_SECTION_TITLE_REGISTERED; + } else { + return TABLE_SECTION_TITLE_UNREGISTERED; + } +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + + if (_isSearching) { + if (section == SEARCH_TABLE_SECTION_FEED) { + return (NSInteger)[_searchInboxFeed count]; + } else if (section == SEARCH_TABLE_SECTION_REGISTERED) { + return (NSInteger)[_searchRegisteredContacts count]; + } else { + return (NSInteger)[_searchUnregisteredContacts count]; + } + } else { + NSInteger inboxFeedAndInfoCellCount = (NSInteger)[_inboxFeed count] + 1; + return inboxFeedAndInfoCellCount; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (_isSearching) { + return [self searchCellForIndexPath:indexPath]; + } else { + return [self inboxFeedCellForIndexPath:indexPath]; + } +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + [_searchBarTitleView.searchTextField resignFirstResponder]; + + if (_isSearching) { + if (indexPath.section == SEARCH_TABLE_SECTION_FEED) { + [self showRecentCallViewControllerWithRecentCall:_searchInboxFeed[(NSUInteger)indexPath.row]]; + } else if (indexPath.section == SEARCH_TABLE_SECTION_REGISTERED) { + [self showContactViewControllerWithContact:_searchRegisteredContacts[(NSUInteger)indexPath.row]]; + } else { + [self showContactViewControllerWithContact:_searchUnregisteredContacts[(NSUInteger)indexPath.row]]; + } + } else { + if (indexPath.row < (NSInteger)[_inboxFeed count]) { + [self showRecentCallViewControllerWithRecentCall:_inboxFeed[(NSUInteger)indexPath.row]]; + } + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == SEARCH_TABLE_SECTION_FEED) { + if ((NSUInteger)indexPath.row == [_inboxFeed count]) { + return FOOTER_CELL_HEIGHT; + } else { + return INBOX_TABLE_VIEW_CELL_HEIGHT; + } + } else { + return CONTACT_TABLE_VIEW_CELL_HEIGHT; + } +} + +#pragma mark - Table cell creation + +- (UITableViewCell *)searchCellForIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == SEARCH_TABLE_SECTION_FEED) { + return [self inboxCellForIndexPath:indexPath andIsSearching:YES]; + } else { + return [self contactCellForIndexPath:indexPath]; + } +} + +- (UITableViewCell *)inboxFeedCellForIndexPath:(NSIndexPath *)indexPath { + if (!_isSearching && (NSUInteger)[indexPath row] == [_inboxFeed count]) { + return [self inboxFeedFooterCell]; + } else { + return [self inboxCellForIndexPath:indexPath andIsSearching:NO]; + } +} + +- (UITableViewCell *)inboxCellForIndexPath:(NSIndexPath *)indexPath andIsSearching:(BOOL)isSearching { + InboxFeedTableViewCell *cell = [_inboxFeedTableView dequeueReusableCellWithIdentifier:INBOX_FEED_TABLE_VIEW_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[InboxFeedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:INBOX_FEED_TABLE_VIEW_CELL_IDENTIFIER]; + cell.delegate = self; + } + + RecentCall *recent = isSearching ? _searchInboxFeed[(NSUInteger)indexPath.row] : _inboxFeed[(NSUInteger)indexPath.row]; + [cell configureWithRecentCall:recent]; + return cell; +} + +- (UITableViewCell *)contactCellForIndexPath:(NSIndexPath *)indexPath { + ContactTableViewCell *cell = [_inboxFeedTableView dequeueReusableCellWithIdentifier:CONTACT_TABLE_VIEW_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[ContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CONTACT_TABLE_VIEW_CELL_IDENTIFIER]; + } + + NSUInteger searchIndex = (NSUInteger)indexPath.row; + Contact *contact; + + if (indexPath.section == SEARCH_TABLE_SECTION_REGISTERED) { + contact = _searchRegisteredContacts[searchIndex]; + } else { + contact = _searchUnregisteredContacts[searchIndex]; + } + + [cell configureWithContact:contact]; + + return cell; +} + +- (UITableViewCell *)inboxFeedFooterCell { + InboxFeedFooterCell *cell = [_inboxFeedTableView dequeueReusableCellWithIdentifier:FOOTER_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[InboxFeedFooterCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:FOOTER_TABLE_CELL_IDENTIFIER]; + } + + return cell; +} + +#pragma mark - HomeFeedTableViewCellDelegate + +- (void)inboxFeedTableViewCellTappedDelete:(InboxFeedTableViewCell *)cell { + [self removeNewsFeedCell:cell willDelete:YES]; +} + +- (void)inboxFeedTableViewCellTappedArchive:(InboxFeedTableViewCell *)cell { + [self removeNewsFeedCell:cell willDelete:NO]; +} + +#pragma mark - SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term { + BOOL searching = [term length] > 0; + _isSearching = searching; + + if (searching) { + _freshInboxView.hidden = YES; + _inboxFeedTableView.hidden = NO; + _searchInboxFeed = [[[Environment getCurrent] recentCallManager] recentsForSearchString:term + andExcludeArchived:YES]; + + [self reloadSearchContactsForTerm:term]; + } else { + [self updateTutorialVisibility]; + _searchInboxFeed = nil; + _searchRegisteredContacts = nil; + } + [_inboxFeedTableView reloadData]; +} + +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft + animated:YES + completion:nil]; +} + +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view { + _isSearching = false; + [self updateTutorialVisibility]; + [_inboxFeedTableView reloadData]; +} + +- (void)reloadSearchContactsForTerm:(NSString *)term { + + NSArray *contacts = [[[Environment getCurrent] contactsManager] latestContactsWithSearchString:term]; + + NSMutableArray *registeredContacts = [NSMutableArray array]; + NSMutableArray *unregisteredContacts = [NSMutableArray array]; + + for (Contact *contact in contacts) { + BOOL registeredContact = NO; + + for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { + if ([[[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter] containsPhoneNumber:phoneNumber]) { + registeredContact = YES; + } + } + + if (registeredContact) { + [registeredContacts addObject:contact]; + } else { + [unregisteredContacts addObject:contact]; + } + } + + _searchRegisteredContacts = [registeredContacts copy]; + _searchUnregisteredContacts = [unregisteredContacts copy]; +} + +#pragma mark - Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_inboxFeedTableView.frame) - (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _inboxFeedTableView.frame = CGRectMake(CGRectGetMinX(_inboxFeedTableView.frame), + CGRectGetMinY(_inboxFeedTableView.frame), + CGRectGetWidth(_inboxFeedTableView.frame), + height); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_inboxFeedTableView.frame) + (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _inboxFeedTableView.frame = CGRectMake(CGRectGetMinX(_inboxFeedTableView.frame), + CGRectGetMinY(_inboxFeedTableView.frame), + CGRectGetWidth(_inboxFeedTableView.frame), + height); + if (!_searchInboxFeed) { + [self updateTutorialVisibility]; + } +} +@end diff --git a/Signal/src/view controllers/InviteContactModal.h b/Signal/src/view controllers/InviteContactModal.h new file mode 100644 index 000000000..627073d02 --- /dev/null +++ b/Signal/src/view controllers/InviteContactModal.h @@ -0,0 +1,10 @@ +#import + +#import "PhoneNumber.h" + +@interface InviteContactModal : NSObject + ++(InviteContactModal*) inviteContactModelWithPhoneNumber:(PhoneNumber*) phoneNumber andParentViewController:(UIViewController*) parent; +-(void) presentModalView; + +@end diff --git a/Signal/src/view controllers/InviteContactModal.m b/Signal/src/view controllers/InviteContactModal.m new file mode 100644 index 000000000..06702dbd0 --- /dev/null +++ b/Signal/src/view controllers/InviteContactModal.m @@ -0,0 +1,44 @@ +#import "InviteContactModal.h" + +#import "InviteContactsViewController.h" +#import "LocalizableText.h" +#import "SmsInvite.h" + +#define CANCEL_BUTTON_INDEX 0 +#define INVITE_BUTTON_INDEX 1 + +@implementation InviteContactModal { + UIAlertView* alertView; + UIViewController* parent; + SmsInvite* smsInvite; + PhoneNumber* phoneNumber; +} + ++(InviteContactModal*) inviteContactModelWithPhoneNumber:(PhoneNumber*) phoneNumber andParentViewController:(UIViewController*) parent { + InviteContactModal* inviteModal = [InviteContactModal new]; + inviteModal->alertView = [[UIAlertView alloc] initWithTitle:INVITE_USER_MODAL_TITLE + message:INVITE_USER_MODAL_TEXT + delegate:inviteModal + cancelButtonTitle:INVITE_USER_MODAL_BUTTON_CANCEL + otherButtonTitles:INVITE_USER_MODAL_BUTTON_INVITE, nil]; + inviteModal->parent = parent; + inviteModal->phoneNumber = phoneNumber; + return inviteModal; +} +-(void) presentModalView{ + [alertView show]; +} + +-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if(INVITE_BUTTON_INDEX == buttonIndex){ + smsInvite = [SmsInvite smsInviteWithParent:parent]; + [smsInvite sendSMSInviteToNumber:phoneNumber]; + } +} + + + + + + +@end diff --git a/Signal/src/view controllers/InviteContactsViewController.h b/Signal/src/view controllers/InviteContactsViewController.h new file mode 100644 index 000000000..c4e6c89c8 --- /dev/null +++ b/Signal/src/view controllers/InviteContactsViewController.h @@ -0,0 +1,15 @@ +#import +#import +#import "SearchBarTitleView.h" + +@interface InviteContactsViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UIView *unseenWhisperUsersHeaderView; +@property (nonatomic, strong) IBOutlet UIView *regularContactsHeaderView; +@property (nonatomic, strong) IBOutlet UITableView *contactTableView; + +- (IBAction)dismissNewWhisperUsersTapped:(id)sender; + +- (void)updateWithNewWhisperUsers:(NSArray *)users; + +@end diff --git a/Signal/src/view controllers/InviteContactsViewController.m b/Signal/src/view controllers/InviteContactsViewController.m new file mode 100644 index 000000000..83ad2a56c --- /dev/null +++ b/Signal/src/view controllers/InviteContactsViewController.m @@ -0,0 +1,265 @@ +#import "InviteContactsViewController.h" + +#import + +#import "ContactsManager.h" +#import "ContactTableViewCell.h" +#import "Environment.h" +#import "FunctionalUtil.h" +#import "LocalizableText.h" +#import "ObservableValue.h" +#import "SmsInvite.h" +#import "TabBarParentViewController.h" +#import "UnseenWhisperUserCell.h" + + + +#define FIRST_TABLE_SECTION 0 +#define SECOND_TABLE_SECTION 1 + +static NSString *const NEW_USERS_TABLE_SECTION_IDENTIFIER = @"UnseenWhisperUserCell"; +static NSString *const INVITE_CONTACTS_TABLE_CELL_IDENTIFIER = @"ContactTableViewCell"; + +@interface InviteContactsViewController () { + NSArray *_latestContacts; + NSArray *_displayedContacts; + NSArray *_selectedContactNumbers; + NSArray *_newWhisperUsers; + + BOOL _isSearching; + NSString *_currentSearchTerm; + SmsInvite* smsInvite; +} + +@end + +@implementation InviteContactsViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupContacts]; + [self observeKeyboardNotifications]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBarHidden = YES; + if (_newWhisperUsers) { + [(TabBarParentViewController *)self.mm_drawerController.centerViewController setNewWhisperUsersAsSeen:_newWhisperUsers]; + [_contactTableView reloadData]; + } +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)setupContacts { + ObservableValue *observableContacts = [[[Environment getCurrent] contactsManager] getObservableContacts]; + + [observableContacts watchLatestValue:^(NSArray *latestContacts) { + _latestContacts = [self getUnregisteredUsersFromAllUsers:latestContacts searchTerm:nil]; + _displayedContacts = _latestContacts; + [_contactTableView reloadData]; + } onThread:[NSThread mainThread] untilCancelled:nil]; +} + +- (NSArray *)getUnregisteredUsersFromAllUsers:(NSArray *)users searchTerm:(NSString *)searchTerm { + ContactsManager *contactsManager = [[Environment getCurrent] contactsManager]; + + return [users filter:^int(Contact *contact) { + + BOOL matchesSearchQuery = YES; + + if (searchTerm != nil) { + matchesSearchQuery = [ContactsManager name:[contact fullName] matchesQuery:searchTerm]; + } + + return ![contactsManager isContactRegisteredWithWhisper:contact] && matchesSearchQuery; + }]; +} + +- (void)presentActionSheetWithNumbersForContact:(Contact *)contact { + + _selectedContactNumbers = contact.parsedPhoneNumbers; + + UIActionSheet *actionSheet = [UIActionSheet new]; + actionSheet.delegate = self; + actionSheet.title = INVITE_USERS_ACTION_SHEET_TITLE; + + for (PhoneNumber *number in _selectedContactNumbers) { + [actionSheet addButtonWithTitle:[number localizedDescriptionForUser]]; + } + actionSheet.cancelButtonIndex = [actionSheet addButtonWithTitle:TXT_CANCEL_TITLE]; + + [actionSheet showInView:self.mm_drawerController.centerViewController.view]; +} + +#pragma mark - Actions + +- (IBAction)dismissNewWhisperUsersTapped:(id)sender { + [_contactTableView beginUpdates]; + + NSMutableArray *indexPaths = [NSMutableArray array]; + + for (int i = 0; i < (NSInteger)[_newWhisperUsers count]; i++) { + [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:FIRST_TABLE_SECTION]]; + } + [_contactTableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; + _newWhisperUsers = nil; + [_contactTableView endUpdates]; + [_contactTableView reloadData]; +} + +- (void)updateWithNewWhisperUsers:(NSArray *)users { + _newWhisperUsers = users; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (section == FIRST_TABLE_SECTION) { + return _isSearching ? 0 : (NSInteger)[_newWhisperUsers count]; + } else { + return (NSInteger)[_displayedContacts count]; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == FIRST_TABLE_SECTION && !_isSearching) { + return [self cellForNewWhisperUserAtIndexPath:indexPath]; + } else { + return [self cellForUnregisteredContactAtIndexPath:indexPath]; + } +} + +- (UITableViewCell *)cellForNewWhisperUserAtIndexPath:(NSIndexPath *)indexPath { + UnseenWhisperUserCell *cell = [_contactTableView dequeueReusableCellWithIdentifier:NEW_USERS_TABLE_SECTION_IDENTIFIER]; + + if (!cell) { + cell = [[UnseenWhisperUserCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:NEW_USERS_TABLE_SECTION_IDENTIFIER]; + } + + [cell configureWithContact:_newWhisperUsers[(NSUInteger)indexPath.row]]; + return cell; +} + +- (UITableViewCell *)cellForUnregisteredContactAtIndexPath:(NSIndexPath *)indexPath { + ContactTableViewCell *cell = [_contactTableView dequeueReusableCellWithIdentifier:INVITE_CONTACTS_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[ContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:INVITE_CONTACTS_TABLE_CELL_IDENTIFIER]; + } + + [cell configureWithContact:_displayedContacts[(NSUInteger)indexPath.row]]; + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + if (indexPath.section == SECOND_TABLE_SECTION) { + Contact *contact = _displayedContacts[(NSUInteger)indexPath.row]; + [self presentActionSheetWithNumbersForContact:contact]; + } +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + if (section == FIRST_TABLE_SECTION && !_isSearching && [_newWhisperUsers count] > 0) { + return _unseenWhisperUsersHeaderView; + } else if (section == SECOND_TABLE_SECTION) { + return _regularContactsHeaderView; + } else { + return nil; + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (_isSearching || ([_newWhisperUsers count] == 0 && section == FIRST_TABLE_SECTION)) { + return 0.0f; + } else { + + CGFloat newUsersViewHeight = CGRectGetHeight(_unseenWhisperUsersHeaderView.frame); + CGFloat regularContactsViewHeight = CGRectGetHeight(_regularContactsHeaderView.frame); + + return section == FIRST_TABLE_SECTION ? newUsersViewHeight : regularContactsViewHeight; + } +} + +#pragma mark - UIActionSheetDelegate + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { + if (buttonIndex != actionSheet.cancelButtonIndex) { + smsInvite = [SmsInvite smsInviteWithParent:self]; + [smsInvite sendSMSInviteToNumber:_selectedContactNumbers[(NSUInteger)buttonIndex]]; + } +} + +#pragma mark - SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term { + _isSearching = YES; + _currentSearchTerm = term; + _displayedContacts = [self getUnregisteredUsersFromAllUsers:_latestContacts searchTerm:term]; + [_contactTableView reloadData]; +} + +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view { + _isSearching = NO; + _currentSearchTerm = nil; + _displayedContacts = [self getUnregisteredUsersFromAllUsers:_latestContacts searchTerm:_currentSearchTerm]; + [_contactTableView reloadData]; +} + +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft + animated:YES + completion:nil]; +} + +#pragma mark - Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_contactTableView.frame) - (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _contactTableView.frame = CGRectMake(CGRectGetMinX(_contactTableView.frame), + CGRectGetMinY(_contactTableView.frame), + CGRectGetWidth(_contactTableView.frame), + height); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; + CGFloat height = CGRectGetHeight(_contactTableView.frame) + (keyboardSize.height-BOTTOM_TAB_BAR_HEIGHT); + _contactTableView.frame = CGRectMake(CGRectGetMinX(_contactTableView.frame), + CGRectGetMinY(_contactTableView.frame), + CGRectGetWidth(_contactTableView.frame), + height); +} + +@end diff --git a/Signal/src/view controllers/InviteContactsViewController.xib b/Signal/src/view controllers/InviteContactsViewController.xib new file mode 100644 index 000000000..cb59e16c9 --- /dev/null +++ b/Signal/src/view controllers/InviteContactsViewController.xib @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal/src/view controllers/LeftSideMenuViewController.h b/Signal/src/view controllers/LeftSideMenuViewController.h new file mode 100644 index 000000000..1c12cdca2 --- /dev/null +++ b/Signal/src/view controllers/LeftSideMenuViewController.h @@ -0,0 +1,26 @@ +#import + +#import "ContactBrowseViewController.h" +#import "ContactsManager.h" +#import "FutureSource.h" +#import "TabBarParentViewController.h" + +/** + * + * LeftSideMenuViewController is the nav bin view controller which can be swiped in from the left and/or tapped open from a button + * + */ + +@interface LeftSideMenuViewController : UIViewController + +@property (nonatomic, strong) TabBarParentViewController *centerTabBarViewController; +@property (nonatomic, strong) IBOutlet UITableView *menuOptionTableView; +@property (nonatomic, strong) IBOutlet UIView *firstSectionHeaderView; +@property (nonatomic, strong) IBOutlet UIView *secondSectionHeaderView; + +- (void)showDialerViewController; +- (void)showContactsViewController; +- (void)showRecentsViewController; +- (void)showFavouritesViewController; + +@end diff --git a/Signal/src/view controllers/LeftSideMenuViewController.m b/Signal/src/view controllers/LeftSideMenuViewController.m new file mode 100644 index 000000000..2c9751e03 --- /dev/null +++ b/Signal/src/view controllers/LeftSideMenuViewController.m @@ -0,0 +1,201 @@ +#import "LeftSideMenuViewController.h" +#import "LocalizableText.h" +#import "LeftSideMenuCell.h" +#import "UIUtil.h" + +#import + +#define FIRST_SECTION_INDEX 0 +#define SECOND_SECTION_INDEX 1 + +#define NUMBER_OF_TABLE_VIEW_SECTIONS 2 + +static NSString *SIDE_MENU_TABLE_CELL_IDENTIFIER = @"LeftSideMenuCell"; +static NSString *WHISPER_SYSTEMS_URL = @"http://whispersystems.org/"; +static NSString *WHISPER_SYSTEMS_BLOG_URL = @"http://whispersystems.org/blog"; +static NSString *WHISPER_SYSTEMS_BUGREPORT_URL = @"http://support.whispersystems.org"; + +@interface LeftSideMenuViewController () { + NSArray *_firstSectionOptions; + NSArray *_secondSectionOptions; +} + +@end + +@implementation LeftSideMenuViewController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + _centerTabBarViewController = [TabBarParentViewController new]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + _firstSectionOptions = @[MAIN_MENU_OPTION_RECENT_CALLS, + MAIN_MENU_OPTION_FAVOURITES, + MAIN_MENU_OPTION_CONTACTS, + MAIN_MENU_OPTION_DIALER, + MAIN_MENU_INVITE_CONTACTS]; + + _secondSectionOptions = @[MAIN_MENU_OPTION_SETTINGS, + MAIN_MENU_OPTION_ABOUT, + MAIN_MENU_OPTION_REPORT_BUG, + MAIN_MENU_OPTION_BLOG]; + + self.mm_drawerController.closeDrawerGestureModeMask = MMCloseDrawerGestureModePanningCenterView | MMCloseDrawerGestureModeTapCenterView; + self.mm_drawerController.openDrawerGestureModeMask = MMOpenDrawerGestureModeBezelPanningCenterView; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (BOOL)isLeftSideViewOpenCompletely { + return self.mm_drawerController.visibleLeftDrawerWidth >= self.mm_drawerController.maximumLeftDrawerWidth; +} + +#pragma mark - View Controller Presentation + +- (void)showRecentsViewController { + [_centerTabBarViewController presentRecentCallsViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)showContactsViewController { + [_centerTabBarViewController presentContactsViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)showDialerViewController { + [_centerTabBarViewController presentDialerViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)showSettingsViewController { + [_centerTabBarViewController presentSettingsViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)showFavouritesViewController { + [_centerTabBarViewController presentFavouritesViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)showInviteContactsViewController { + [_centerTabBarViewController presentInviteContactsViewController]; + [self.mm_drawerController closeDrawerAnimated:YES completion:nil]; +} + +- (void)selectMenuOption:(NSString *)menuOption { + if ([menuOption isEqualToString:MAIN_MENU_OPTION_RECENT_CALLS]) { + [self showRecentsViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_FAVOURITES]) { + [self showFavouritesViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_CONTACTS]) { + [self showContactsViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_DIALER]) { + [self showDialerViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_INVITE_CONTACTS]) { + [self showInviteContactsViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_SETTINGS]) { + [self showSettingsViewController]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_ABOUT]) { + [self openAboutWhisperUrl]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_REPORT_BUG]) { + [self openBugReporterUrl]; + } + if ([menuOption isEqualToString:MAIN_MENU_OPTION_BLOG]) { + [self openBlogUrl]; + } +} + +- (void)openAboutWhisperUrl { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:WHISPER_SYSTEMS_URL]]; +} + +- (void)openBugReporterUrl { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:WHISPER_SYSTEMS_BUGREPORT_URL]]; +} + +- (void)openBlogUrl { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:WHISPER_SYSTEMS_BLOG_URL]]; +} + +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (section == FIRST_SECTION_INDEX) { + return CGRectGetHeight(_firstSectionHeaderView.frame); + } else { + return CGRectGetHeight(_secondSectionHeaderView.frame); + } +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + if (section == FIRST_SECTION_INDEX) { + return _firstSectionHeaderView; + } else { + return _secondSectionHeaderView; + } +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return NUMBER_OF_TABLE_VIEW_SECTIONS; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (section == FIRST_SECTION_INDEX) { + return (NSInteger)[_firstSectionOptions count]; + } else { + return (NSInteger)[_secondSectionOptions count]; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + LeftSideMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:SIDE_MENU_TABLE_CELL_IDENTIFIER]; + + if (!cell) { + cell = [[LeftSideMenuCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:SIDE_MENU_TABLE_CELL_IDENTIFIER]; + cell.backgroundColor = [UIColor clearColor]; + } + + if (indexPath.section == FIRST_SECTION_INDEX) { + cell.menuTitleLabel.text = _firstSectionOptions[(NSUInteger)indexPath.row]; + } else { + cell.menuTitleLabel.text = _secondSectionOptions[(NSUInteger)indexPath.row]; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + if ([self isLeftSideViewOpenCompletely]) { + NSString *menuOption; + if (indexPath.section == FIRST_SECTION_INDEX) { + menuOption = _firstSectionOptions[(NSUInteger)indexPath.row]; + } else { + menuOption = _secondSectionOptions[(NSUInteger)indexPath.row]; + } + + [self selectMenuOption:menuOption]; + } +} + +@end diff --git a/Signal/src/view controllers/NextResponderScrollView.h b/Signal/src/view controllers/NextResponderScrollView.h new file mode 100644 index 000000000..f1e240349 --- /dev/null +++ b/Signal/src/view controllers/NextResponderScrollView.h @@ -0,0 +1,12 @@ +#import + +/** + * + * This scroll view is used in inbox feed table cell to pass touches through to the next responder- + * because the scroll view touches override the table cell touches otherwise. + * + */ + +@interface NextResponderScrollView : UIScrollView + +@end diff --git a/Signal/src/view controllers/NextResponderScrollView.m b/Signal/src/view controllers/NextResponderScrollView.m new file mode 100644 index 000000000..bb1d905df --- /dev/null +++ b/Signal/src/view controllers/NextResponderScrollView.m @@ -0,0 +1,26 @@ +#import "NextResponderScrollView.h" + +@implementation NextResponderScrollView + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if (!self.dragging){ + [self.nextResponder touchesBegan: touches withEvent:event]; + } else { + [super touchesBegan: touches withEvent: event]; + } +} +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + if (!self.dragging){ + [self.nextResponder touchesEnded: touches withEvent:event]; + } else { + [super touchesEnded: touches withEvent: event]; + } +} +-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + if (!self.dragging){ + [self.nextResponder touchesCancelled:touches withEvent:event]; + } else { + [super touchesEnded: touches withEvent: event]; + } +} +@end diff --git a/Signal/src/view controllers/PreferenceListViewController.h b/Signal/src/view controllers/PreferenceListViewController.h new file mode 100644 index 000000000..622025a2f --- /dev/null +++ b/Signal/src/view controllers/PreferenceListViewController.h @@ -0,0 +1,26 @@ +#import + +/** + * + * PreferenceListViewController displays a list of options and highlights a selected one indicated by selectedValueBlock. + * When selected, the selected block is called and the value should be updated manually. + * + */ + +typedef void (^SelectedBlock) (NSString *newValue); +typedef NSString* (^GetSelectedValueBlock) (); + +@interface PreferenceListViewController : UIViewController { + @private SelectedBlock selectedBlock; + @private GetSelectedValueBlock getSelectedValueBlock; + @private NSString *settingsValue; +} + +@property (nonatomic, strong) IBOutlet UITableView *optionTableView; +@property (nonatomic, strong) NSArray *options; + ++ (PreferenceListViewController *)preferenceListViewControllerForSelectedValue:(GetSelectedValueBlock)selectedValueBlock + andOptions:(NSArray *)options + andSelectedBlock:(SelectedBlock)block; + +@end diff --git a/Signal/src/view controllers/PreferenceListViewController.m b/Signal/src/view controllers/PreferenceListViewController.m new file mode 100644 index 000000000..5ceb4d7ea --- /dev/null +++ b/Signal/src/view controllers/PreferenceListViewController.m @@ -0,0 +1,67 @@ +#import "Environment.h" +#import "PreferencesUtil.h" +#import "PreferenceListTableViewCell.h" +#import "PreferenceListViewController.h" +#import "Util.h" + +static NSString *const PREFERENCE_LIST_TABLE_VIEW_CELL = @"PreferenceListTableViewCell"; + +@implementation PreferenceListViewController + ++ (PreferenceListViewController *)preferenceListViewControllerForSelectedValue:(GetSelectedValueBlock)selectedValueBlock + andOptions:(NSArray *)options + andSelectedBlock:(SelectedBlock)block { + require(selectedValueBlock != nil); + require(block != nil); + + PreferenceListViewController *vc = [PreferenceListViewController new]; + vc.options = options; + vc->selectedBlock = block; + vc->getSelectedValueBlock = selectedValueBlock; + return vc; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.navigationController.navigationBar.barTintColor = [UIUtil darkBackgroundColor]; + self.navigationController.navigationBar.tintColor = [UIColor whiteColor]; + self.navigationController.navigationBar.translucent = NO; + + settingsValue = getSelectedValueBlock(); + [_optionTableView reloadData]; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return (NSInteger)[_options count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + PreferenceListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PREFERENCE_LIST_TABLE_VIEW_CELL]; + if (!cell) { + cell = [[PreferenceListTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:PREFERENCE_LIST_TABLE_VIEW_CELL]; + } + + if ([settingsValue isEqualToString:_options[(NSUInteger)indexPath.row]]) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + + NSString *date = _options[(NSUInteger)indexPath.row]; + cell.preferenceTextLabel.text = [date lowercaseString]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + selectedBlock(_options[(NSUInteger)indexPath.row]); + settingsValue = getSelectedValueBlock(); + [_optionTableView reloadData]; +} + +@end diff --git a/Signal/src/view controllers/PreferenceListViewController.xib b/Signal/src/view controllers/PreferenceListViewController.xib new file mode 100644 index 000000000..4be5a1cb4 --- /dev/null +++ b/Signal/src/view controllers/PreferenceListViewController.xib @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/RegisterViewController.h b/Signal/src/view controllers/RegisterViewController.h new file mode 100644 index 000000000..425aca610 --- /dev/null +++ b/Signal/src/view controllers/RegisterViewController.h @@ -0,0 +1,43 @@ +#import + +#import "CancelTokenSource.h" +#import "CountryCodeViewController.h" +#import "FutureSource.h" + +@interface RegisterViewController : UIViewController { +@private Future* futureApnId; +@private FutureSource* registered; +@private FutureSource* futureChallengeAcceptedSource; +@private CancelTokenSource* life; +} + +@property (nonatomic, strong) IBOutlet UIButton *registerButton; +@property (nonatomic, strong) IBOutlet UIButton *challengeButton; +@property (nonatomic, strong) IBOutlet UITextField *phoneNumberTextField; +@property (nonatomic, strong) IBOutlet UILabel *countryCodeLabel; +@property (nonatomic, strong) IBOutlet UILabel *countryNameLabel; +@property (nonatomic, strong) IBOutlet UITextField *challengeTextField; +@property (nonatomic, strong) IBOutlet UILabel *registerErrorLabel; +@property (nonatomic, strong) IBOutlet UILabel *challengeErrorLabel; +@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *registerActivityIndicator; +@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *challengeActivityIndicator; +@property (nonatomic, strong) IBOutlet UIScrollView *scrollView; +@property (nonatomic, strong) IBOutlet UIView *containerView; +@property (nonatomic, strong) IBOutlet UIButton *registerCancelButton; +@property (nonatomic, strong) IBOutlet UIButton *continueToWhisperButton; + +@property (nonatomic, strong) IBOutlet UILabel *challengeNumberLabel; +@property (nonatomic, strong) IBOutlet UILabel *voiceChallengeTextLabel; +@property (nonatomic, strong) IBOutlet UIButton *initiateVoiceVerificationButton; + +- (IBAction)registerPhoneNumberTapped; +- (IBAction)registerCancelButtonTapped; +- (IBAction)verifyChallengeTapped; +- (IBAction)dismissTapped; +- (IBAction)changeNumberTapped; +- (IBAction)changeCountryCodeTapped; +- (IBAction)initiateVoiceVerificationButtonHandler; + ++ (RegisterViewController*)registerViewControllerForApn:(Future *)apnId; + +@end diff --git a/Signal/src/view controllers/RegisterViewController.m b/Signal/src/view controllers/RegisterViewController.m new file mode 100644 index 000000000..92b22c3cf --- /dev/null +++ b/Signal/src/view controllers/RegisterViewController.m @@ -0,0 +1,405 @@ +#import "Environment.h" +#import "HttpManager.h" +#import "KeyChainStorage.h" +#import "LocalizableText.h" +#import "NBAsYouTypeFormatter.h" +#import "PhoneNumber.h" +#import "PhoneNumberDirectoryFilterManager.h" +#import "PhoneNumberUtil.h" +#import "PreferencesUtil.h" +#import "RegisterViewController.h" +#import "SignalUtil.h" +#import "ThreadManager.h" +#import "Util.h" + +#define REGISTER_VIEW_NUMBER 0 +#define CHALLENGE_VIEW_NUMBER 1 + +#define COUNTRY_CODE_CHARACTER_MAX 3 + +#define SERVER_TIMEOUT_SECONDS 20 +#define SMS_VERIFICATION_TIMEOUT_SECONDS 4*60 +#define VOICE_VERIFICATION_COOLDOWN_SECONDS 4 + +#define IPHONE_BLUE [UIColor colorWithRed:22 green:173 blue:214 alpha:1] + +@interface RegisterViewController () { + NSMutableString *_enteredPhoneNumber; + NSTimer* countdownTimer; + NSDate *timeoutDate; +} + +@end + +@implementation RegisterViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self populateDefaultCountryNameAndCode]; + + [futureApnId catchDo:^(id error) { + // todo: remove this; just here for testing purposes to catch apn not being set + _registerErrorLabel.text = [error description]; + }]; + + _scrollView.contentSize = _containerView.bounds.size; + + BOOL isRegisteredAlready = [[Environment preferences] getIsRegistered]; + _registerCancelButton.hidden = !isRegisteredAlready; + + [self initializeKeyboardHandlers]; + [self setPlaceholderTextColor:[UIColor lightGrayColor]]; + _enteredPhoneNumber = [NSMutableString string]; +} + ++ (RegisterViewController*)registerViewControllerForApn:(Future *)apnId { + require(apnId != nil); + + RegisterViewController *viewController = [RegisterViewController new]; + viewController->futureApnId = apnId; + viewController->registered = [FutureSource new]; + viewController->life = [CancelTokenSource cancelTokenSource]; + [[viewController->life getToken] whenCancelledTryCancel:viewController->registered]; + + return viewController; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleDefault; +} + +- (void)setPlaceholderTextColor:(UIColor *)color { + NSAttributedString *placeholder = _phoneNumberTextField.attributedPlaceholder; + if ([placeholder length]) { + NSDictionary * attributes = [placeholder attributesAtIndex:0 + effectiveRange:NULL]; + + NSMutableDictionary *newAttributes = [[NSMutableDictionary alloc] initWithDictionary:attributes]; + [newAttributes setObject:color forKey:NSForegroundColorAttributeName]; + + NSString *placeholderString = [placeholder string]; + _phoneNumberTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:placeholderString + attributes:newAttributes]; + } +} + +- (void)localizeButtonText { + [_registerCancelButton setTitle:TXT_CANCEL_TITLE forState:UIControlStateNormal]; + [_continueToWhisperButton setTitle:CONTINUE_TO_WHISPER_TITLE forState:UIControlStateNormal]; + [_registerButton setTitle:REGISTER_BUTTON_TITLE forState:UIControlStateNormal]; + [_challengeButton setTitle:CHALLENGE_CODE_BUTTON_TITLE forState:UIControlStateNormal]; +} + +- (IBAction)registerCancelButtonTapped { + [self dismissView]; +} + +- (void) dismissView { + [self stopVoiceVerificationCountdownTimer]; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)populateDefaultCountryNameAndCode { + NSLocale *locale = [NSLocale currentLocale]; + NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; + NSNumber *cc = [[NBPhoneNumberUtil sharedInstance] getCountryCodeForRegion:countryCode]; + + _countryCodeLabel.text = [NSString stringWithFormat:@"%@%@",COUNTRY_CODE_PREFIX, cc]; + _countryNameLabel.text = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; +} + +- (IBAction)changeNumberTapped { + [self showViewNumber:REGISTER_VIEW_NUMBER]; +} + +- (IBAction)changeCountryCodeTapped { + CountryCodeViewController *countryCodeController = [CountryCodeViewController new]; + countryCodeController.delegate = self; + [self presentViewController:countryCodeController animated:YES completion:nil]; +} + +-(Future*) asyncRegister:(PhoneNumber*)phoneNumber untilCancelled:(id)cancelToken { + // @todo: should we force regenerating of all keys? + // @todo: clear current registered status before making a new one, to avoid splinching issues? + [KeyChainStorage setLocalNumberTo:phoneNumber]; + + CancellableOperationStarter regStarter = ^Future *(id internalUntilCancelledToken) { + HttpRequest *registerRequest = [HttpRequest httpRequestToStartRegistrationOfPhoneNumber]; + + return [HttpManager asyncOkResponseFromMasterServer:registerRequest + unlessCancelled:internalUntilCancelledToken + andErrorHandler:[Environment errorNoter]]; + }; + Future *futurePhoneRegistrationStarted = [AsyncUtil raceCancellableOperation:regStarter + againstTimeout:30.0 + untilCancelled:cancelToken]; + + Future *futurePhoneRegistrationVerified = [futurePhoneRegistrationStarted then:^(id _) { + [self showViewNumber:CHALLENGE_VIEW_NUMBER]; + [[Environment preferences] setIsRegistered:NO]; + [self.challengeNumberLabel setText:[phoneNumber description]]; + [_registerCancelButton removeFromSuperview]; + [self startVoiceVerificationCountdownTimer]; + self->futureChallengeAcceptedSource = [FutureSource new]; + return futureChallengeAcceptedSource; + }]; + + Future *futureApnToRegister = [futurePhoneRegistrationVerified then:^(HttpResponse* okResponse) { + // @todo: keep handling code for simulator? + return [futureApnId catch:^id(id error) { + return nil; + }]; + }]; + + return [futureApnToRegister then:^Future*(NSData* deviceToken) { + // @todo: distinguish between simulator no-apn error and other no-apn errors + if (deviceToken == nil) return futureApnToRegister; + + HttpRequest* request = [HttpRequest httpRequestToRegisterForApnSignalingWithDeviceToken:deviceToken]; + return [HttpManager asyncOkResponseFromMasterServer:request + unlessCancelled:cancelToken + andErrorHandler:[Environment errorNoter]]; + }]; +} + +- (void)registerPhoneNumberTapped { + NSString *phoneNumber = [NSString stringWithFormat:@"%@%@", _countryCodeLabel.text, _phoneNumberTextField.text]; + PhoneNumber* localNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber]; + if(localNumber==nil){ return; } + + [_phoneNumberTextField resignFirstResponder]; + + Future* futureFinished = [self asyncRegister:localNumber untilCancelled:[life getToken]]; + [_registerActivityIndicator startAnimating]; + _registerButton.enabled = NO; + _registerErrorLabel.text = @""; + [futureFinished catchDo:^(id error) { + [_challengeActivityIndicator stopAnimating]; + _registerButton.enabled = YES; + // todo: localize + _registerErrorLabel.text = [error description]; + }]; +} + +- (void)dismissTapped { + [self dismissView]; +} + +- (void)verifyChallengeTapped { + [_challengeTextField resignFirstResponder]; + _challengeButton.enabled = NO; + [_challengeActivityIndicator startAnimating]; + + HttpRequest *verifyRequest = [HttpRequest httpRequestToVerifyAccessToPhoneNumberWithChallenge:_challengeTextField.text]; + Future *futureDone = [HttpManager asyncOkResponseFromMasterServer:verifyRequest + unlessCancelled:nil + andErrorHandler:[Environment errorNoter]]; + + _challengeErrorLabel.text = @""; + [futureDone catchDo:^(id error) { + if ([error isKindOfClass:[HttpResponse class]]) { + HttpResponse* badResponse = error; + if ([badResponse getStatusCode] == 401) { + // @todo: human readable, localizable + _challengeErrorLabel.text = @"Incorrect Challenge Code"; + return; + } + } + [Environment errorNoter](error, @"While Verifying Challenge.", NO); + // @todo: human readable, localizable + _challengeErrorLabel.text = [NSString stringWithFormat:@"Unexpected failure: %@", error]; + }]; + + [futureDone thenDo:^(id result) { + [[Environment preferences] setIsRegistered:YES]; + [[[Environment getCurrent] phoneDirectoryManager] forceUpdate]; + [registered trySetResult:@YES]; + [self dismissView]; + [futureChallengeAcceptedSource trySetResult:result]; + }]; + + [futureDone finallyDo:^(Future *completed) { + _challengeButton.enabled = YES; + [_challengeActivityIndicator stopAnimating]; + }]; +} + +- (void)showViewNumber:(NSInteger)viewNumber { + + + if (viewNumber == REGISTER_VIEW_NUMBER) { + [_registerActivityIndicator stopAnimating]; + _registerButton.enabled = YES; + } + + [self stopVoiceVerificationCountdownTimer]; + + [_scrollView setContentOffset:CGPointMake(_scrollView.frame.size.width*viewNumber, 0) animated:YES]; +} + +- (void)presentInvalidCountryCodeError { + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:REGISTER_CC_ERR_ALERT_VIEW_TITLE + message:REGISTER_CC_ERR_ALERT_VIEW_MESSAGE + delegate:nil + cancelButtonTitle:REGISTER_CC_ERR_ALERT_VIEW_DISMISS + otherButtonTitles:nil]; + [alertView show]; +} + +- (void) startVoiceVerificationCountdownTimer{ + [self.initiateVoiceVerificationButton.titleLabel setTextAlignment:NSTextAlignmentCenter]; + self.initiateVoiceVerificationButton.hidden = NO; + + NSTimeInterval smsTimeoutTimeInterval = SMS_VERIFICATION_TIMEOUT_SECONDS; + + NSDate *now = [[NSDate alloc] init]; + timeoutDate = [[NSDate alloc] initWithTimeInterval:smsTimeoutTimeInterval sinceDate:now]; + + countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(countdowntimerFired) + userInfo:nil repeats:YES]; +} + +- (void) stopVoiceVerificationCountdownTimer{ + [countdownTimer invalidate]; +} + +- (void) countdowntimerFired { + NSDate *now = [[NSDate alloc] init]; + + NSCalendar *sysCalendar = [NSCalendar currentCalendar]; + unsigned int unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; + NSDateComponents *conversionInfo = [sysCalendar components:unitFlags fromDate:now toDate:timeoutDate options:0]; + NSString* timeLeft = [NSString stringWithFormat:@"%d:%02d",[conversionInfo minute],[conversionInfo second]]; + + [self.voiceChallengeTextLabel setText:timeLeft]; + + if (0 <= [now compare:timeoutDate]) { + [self initiateVoiceVerification]; + } + +} + +- (void) initiateVoiceVerification{ + [self stopVoiceVerificationCountdownTimer]; + CancellableOperationStarter callStarter = ^Future *(id internalUntilCancelledToken) { + HttpRequest* voiceVerifyReq = [HttpRequest httpRequestToStartRegistrationOfPhoneNumberWithVoice]; + + [self.voiceChallengeTextLabel setText:@"Calling" ]; + return [HttpManager asyncOkResponseFromMasterServer:voiceVerifyReq + unlessCancelled:internalUntilCancelledToken + andErrorHandler:[Environment errorNoter]]; + }; + Future *futureVoiceVerificationStarted = [AsyncUtil raceCancellableOperation:callStarter + againstTimeout:SERVER_TIMEOUT_SECONDS + untilCancelled:[life getToken]]; + [futureVoiceVerificationStarted catchDo:^(id errorId) { + HttpResponse* error = (HttpResponse*)errorId; + [self.voiceChallengeTextLabel setText:[error getStatusText]]; + }]; + + [futureVoiceVerificationStarted finally:^id(id _id) { + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, VOICE_VERIFICATION_COOLDOWN_SECONDS * NSEC_PER_SEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [self.voiceChallengeTextLabel setText:@"Re-Call"]; + }); + + return _id; + }]; + + +} + +- (IBAction)initiateVoiceVerificationButtonHandler { + [self initiateVoiceVerification]; +} + +#pragma mark - Keyboard notifications + +- (void)initializeKeyboardHandlers{ + UITapGestureRecognizer *outsideTabRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboardFromAppropriateSubView)]; + [self.view addGestureRecognizer:outsideTabRecognizer]; + + [self observeKeyboardNotifications]; + +} + +-(void) dismissKeyboardFromAppropriateSubView { + [self.view endEditing:NO]; +} + +- (void)observeKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)keyboardWillShow:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;; + _scrollView.frame = CGRectMake(CGRectGetMinX(_scrollView.frame), + CGRectGetMinY(_scrollView.frame)-keyboardSize.height, + CGRectGetWidth(_scrollView.frame), + CGRectGetHeight(_scrollView.frame)); + }]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + double duration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + [UIView animateWithDuration:duration animations:^{ + _scrollView.frame = CGRectMake(CGRectGetMinX(_scrollView.frame), + CGRectGetMinY(self.view.frame), + CGRectGetWidth(_scrollView.frame), + CGRectGetHeight(_scrollView.frame)); + }]; +} + +#pragma mark - CountryCodeViewControllerDelegate + +- (void)countryCodeViewController:(CountryCodeViewController *)vc + didSelectCountryCode:(NSString *)code + forCountry:(NSString *)country { + _countryCodeLabel.text = code; + _countryNameLabel.text = country; + [self updatePhoneNumberFieldWithString:code]; + [vc dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)countryCodeViewControllerDidCancel:(CountryCodeViewController *)vc { + [vc dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - UITextFieldDelegate + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range + replacementString:(NSString *)string { + + BOOL handleBackspace = range.length == 1; + if (handleBackspace) { + NSRange backspaceRange = NSMakeRange([_enteredPhoneNumber length] - 1, 1); + [_enteredPhoneNumber replaceCharactersInRange:backspaceRange withString:string]; + } else { + NSString* sanitizedString = [[string componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet ] invertedSet]] componentsJoinedByString:@""]; + [_enteredPhoneNumber appendString:sanitizedString]; + } + + [self updatePhoneNumberFieldWithString:_enteredPhoneNumber]; + return NO; +} + +-(void) updatePhoneNumberFieldWithString:(NSString*) input { + NSString* result = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:_enteredPhoneNumber + withSpecifiedCountryCodeString:_countryCodeLabel.text]; + _phoneNumberTextField.text = result; +} + +@end diff --git a/Signal/src/view controllers/SettingsViewController.h b/Signal/src/view controllers/SettingsViewController.h new file mode 100644 index 000000000..da8f0dc70 --- /dev/null +++ b/Signal/src/view controllers/SettingsViewController.h @@ -0,0 +1,57 @@ +#import + +#import "FutureSource.h" +#import "LocalizableCustomFontLabel.h" +#import "SettingsTableHeaderView.h" + +/** + * + * SettingsViewController displays a list of settings in sections which can animate between being expanded or collapsed. + * The expanded/collapsed preference of the sections is remembered by the preference util. + * Table cell text labels are localized by setting them to a custom label class that has a localization key which are both set in the xib - + * and localized when the cell appears. + * Preferences are saved to preference util when tapped. + * + */ + +@interface SettingsViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UITableView *settingsTableView; +@property (nonatomic, strong) IBOutlet UILabel *phoneNumberLabel; +@property (nonatomic, strong) IBOutlet UILabel *currentDateFormatLabel; +@property (nonatomic, strong) IBOutlet UILabel *titleLabel; + +@property (nonatomic, strong) IBOutlet SettingsTableHeaderView *privacyAndSecurityHeaderView; +@property (nonatomic, strong) IBOutlet UITableViewCell *hideContactImagesCell; +@property (nonatomic, strong) IBOutlet UITableViewCell *disableAutocorrectCell; +@property (nonatomic, strong) IBOutlet UITableViewCell *disableHistoryCell; +@property (nonatomic, strong) IBOutlet UITableViewCell *clearHistoryLogCell; + +@property (nonatomic, strong) IBOutlet UIButton *hideContactImagesButton; +@property (nonatomic, strong) IBOutlet UIButton *disableAutocorrectButton; +@property (nonatomic, strong) IBOutlet UIButton *disableHistoryButton; + +@property (nonatomic, strong) IBOutlet SettingsTableHeaderView *locationOverridesHeaderView; +@property (nonatomic, strong) IBOutlet UITableViewCell *dateFormatCell; + +@property (nonatomic, strong) IBOutlet SettingsTableHeaderView *callQualityHeaderView; +@property (nonatomic, strong) IBOutlet UITableViewCell *feedbackCell; +@property (nonatomic, strong) IBOutlet UITableViewCell *directoryUpdateCell; + +@property (nonatomic, strong) IBOutlet UIButton *sendFeedbackButton; + +@property (nonatomic, assign) FutureSource *apnId; + +- (IBAction)registerTapped; + +- (IBAction)privacyAndSecurityTapped; + +- (IBAction)hideContactImagesButtonTapped; +- (IBAction)disableAutocorrectButtonTapped; +- (IBAction)disableHistoryButtonTapped; + +- (IBAction)sendFeedbackButtonTapped; + +- (IBAction)menuButtonTapped; + +@end diff --git a/Signal/src/view controllers/SettingsViewController.m b/Signal/src/view controllers/SettingsViewController.m new file mode 100644 index 000000000..5c061de98 --- /dev/null +++ b/Signal/src/view controllers/SettingsViewController.m @@ -0,0 +1,313 @@ +#import "Environment.h" +#import "FutureUtil.h" +#import "KeyChainStorage.h" +#import "LocalizableText.h" +#import "Operation.h" +#import "PreferencesUtil.h" +#import "PhoneNumber.h" +#import "PreferenceListViewController.h" +#import "RecentCallManager.h" +#import "RegisterViewController.h" +#import "SettingsViewController.h" + +#import "UIViewController+MMDrawerController.h" + +#define SECTION_HEADER_VIEW_HEIGHT 27 +#define PRIVACY_SECTION_INDEX 0 +#define LOCALIZATION_SECTION_INDEX 1 +#define CALL_QUALITY_SECTION_INDEX 2 + +static NSString *const CHECKBOX_CHECKMARK_IMAGE_NAME = @"checkbox_checkmark"; +static NSString *const CHECKBOX_EMPTY_IMAGE_NAME = @"checkbox_empty"; + +@interface SettingsViewController () { + NSArray *_sectionHeaderViews; + NSArray *_privacyTableViewCells; + NSArray *_localizationTableViewCells; + NSArray *_callQualityTableViewCells; +} + +@end + +@implementation SettingsViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + _sectionHeaderViews = @[_privacyAndSecurityHeaderView, + _locationOverridesHeaderView]; + + _titleLabel.text = SETTINGS_NAV_BAR_TITLE; +} + +- (void)viewWillAppear:(BOOL)animated { + [self configureLocalNumber]; + [self configureAllCells]; + [self configureCheckboxPreferences]; + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [super viewWillAppear:animated]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [self saveExpandedSectionPreferences]; + + if ([self.navigationController.viewControllers count] > 1) { + [self.navigationController setNavigationBarHidden:NO animated:YES]; + } + + [super viewWillDisappear:animated]; +} + +- (void)menuButtonTapped { + [self.mm_drawerController openDrawerSide:MMDrawerSideLeft animated:YES completion:nil]; +} + +#pragma mark - Local number + +- (void)configureLocalNumber { + PhoneNumber *localNumber = [KeyChainStorage tryGetLocalNumber]; + if (localNumber) { + _phoneNumberLabel.attributedText = [self localNumberAttributedStringForNumber:localNumber]; + } else { + _phoneNumberLabel.text = @""; + } +} + +- (NSAttributedString *)localNumberAttributedStringForNumber:(PhoneNumber *)number { + NSString *numberPrefixString = SETTINGS_NUMBER_PREFIX; + NSString *localNumberString = [number toE164]; + + NSString *displayString = [NSString stringWithFormat:@"%@ %@", numberPrefixString, localNumberString]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:displayString]; + + UIFont *prefixFont = [UIUtil helveticaNeueLTStdLightFontWithSize:_phoneNumberLabel.font.pointSize]; + UIFont *numberFont = [UIUtil helveticaNeueLTStdBoldFontWithSize:_phoneNumberLabel.font.pointSize]; + + [attributedString addAttribute:NSFontAttributeName + value:prefixFont + range:NSMakeRange(0, [numberPrefixString length])]; + + [attributedString addAttribute:NSFontAttributeName + value:numberFont + range:NSMakeRange([numberPrefixString length] + 1, [localNumberString length])]; + return attributedString; +} + +#pragma mark - Preferences + +- (void)configureCheckboxPreferences { + NSArray *buttons = @[_hideContactImagesButton, + _disableAutocorrectButton, + _disableHistoryButton, + _sendFeedbackButton]; + + for (UIButton *button in buttons) { + [button setImage:[UIImage imageNamed:CHECKBOX_EMPTY_IMAGE_NAME] + forState:UIControlStateNormal]; + + [button setImage:[UIImage imageNamed:CHECKBOX_CHECKMARK_IMAGE_NAME] + forState:UIControlStateSelected]; + } + PropertyListPreferences *prefs = [[Environment getCurrent] preferences]; + _hideContactImagesButton.selected = ![prefs getContactImagesEnabled]; + _disableAutocorrectButton.selected = ![prefs getAutocorrectEnabled]; + _disableHistoryButton.selected = ![prefs getHistoryLogEnabled]; + _sendFeedbackButton.selected = [prefs getAnonymousFeedbackEnabled]; +} + +- (void)configureAllCells { + PropertyListPreferences *prefs = [[Environment getCurrent] preferences]; + NSArray *expandedSectionPrefs = [prefs getOrGenerateSettingsRowExpandedPrefs]; + + BOOL privacyExpanded = [expandedSectionPrefs[PRIVACY_SECTION_INDEX] boolValue]; + _privacyTableViewCells = privacyExpanded ? [self privacyAndSecurityCells] : nil; + [_privacyAndSecurityHeaderView setColumnStateExpanded:privacyExpanded andIsAnimated:NO]; + + BOOL localizationExpanded = [expandedSectionPrefs[LOCALIZATION_SECTION_INDEX] boolValue]; + _localizationTableViewCells = localizationExpanded ? [self localizationCells] : nil; + [_locationOverridesHeaderView setColumnStateExpanded:localizationExpanded andIsAnimated:NO]; + + _currentDateFormatLabel.text = [[prefs getDateFormat] lowercaseString]; +} + +- (void)saveExpandedSectionPreferences { + NSMutableArray *expandedSectionPrefs = [NSMutableArray array]; + NSNumber *numberBoolYes = [NSNumber numberWithBool:YES]; + NSNumber *numberBoolNo = [NSNumber numberWithBool:NO]; + + [expandedSectionPrefs addObject:(_privacyTableViewCells ? numberBoolYes : numberBoolNo)]; + [expandedSectionPrefs addObject:(_localizationTableViewCells ? numberBoolYes : numberBoolNo)]; + [[[Environment getCurrent] preferences] setSettingsRowExpandedPrefs:expandedSectionPrefs]; +} + +#pragma mark - Table View Helpers + +- (NSArray *)privacyAndSecurityCells { + return @[_hideContactImagesCell, + _disableAutocorrectCell, + _disableHistoryCell, + _clearHistoryLogCell]; +} + +- (NSArray *)localizationCells { + return @[_dateFormatCell]; +} + +- (NSArray *)indexPathsForCells:(NSArray *)cells forRow:(NSInteger)row { + NSMutableArray *indexPaths = [NSMutableArray array]; + for (NSUInteger i = 0; i < [cells count]; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(NSInteger)i inSection:row]; + [indexPaths addObject:indexPath]; + } + return indexPaths; +} + +- (NSArray *)cellsForRow:(NSInteger)row { + if (row == PRIVACY_SECTION_INDEX) { + return [self privacyAndSecurityCells]; + } else if(row == LOCALIZATION_SECTION_INDEX) { + return [self localizationCells]; + } else { + return @[]; + } +} + +#pragma mark - Actions + +- (void)registerTapped { + RegisterViewController *registerViewController = [RegisterViewController registerViewControllerForApn:_apnId]; + [self presentViewController:registerViewController animated:YES completion:nil]; +} + +- (void)privacyAndSecurityTapped { + [self toggleCells:&_privacyTableViewCells forRow:PRIVACY_SECTION_INDEX]; + BOOL columnExpanded = _privacyTableViewCells != nil; + [_privacyAndSecurityHeaderView setColumnStateExpanded:columnExpanded andIsAnimated:YES]; +} + +- (void)localizationTapped { + [self toggleCells:&_localizationTableViewCells forRow:LOCALIZATION_SECTION_INDEX]; + BOOL columnExpanded = _localizationTableViewCells != nil; + [_locationOverridesHeaderView setColumnStateExpanded:columnExpanded andIsAnimated:YES]; +} + +- (void)toggleCells:(NSArray *__strong*)cells forRow:(NSInteger)row { + [_settingsTableView beginUpdates]; + if (*cells) { + [_settingsTableView deleteRowsAtIndexPaths:[self indexPathsForCells:*cells forRow:row] + withRowAnimation:UITableViewRowAnimationFade]; + *cells = nil; + } else { + *cells = [self cellsForRow:row]; + [_settingsTableView insertRowsAtIndexPaths:[self indexPathsForCells:*cells forRow:row] + withRowAnimation:UITableViewRowAnimationFade]; + } + [_settingsTableView endUpdates]; +} + +- (IBAction)hideContactImagesButtonTapped { + _hideContactImagesButton.selected = !_hideContactImagesButton.selected; + [[[Environment getCurrent] preferences] setContactImagesEnabled:!_hideContactImagesButton.selected]; +} + +- (IBAction)disableAutocorrectButtonTapped { + _disableAutocorrectButton.selected = !_disableAutocorrectButton.selected; + [[[Environment getCurrent] preferences] setAutocorrectEnabled:!_disableAutocorrectButton.selected]; +} + +- (IBAction)disableHistoryButtonTapped { + _disableHistoryButton.selected = !_disableHistoryButton.selected; + [[[Environment getCurrent] preferences] setHistoryLogEnabled:!_disableHistoryButton.selected]; +} + +- (IBAction)sendFeedbackButtonTapped { + _sendFeedbackButton.selected = !_sendFeedbackButton.selected; + [[[Environment getCurrent] preferences] setAnonymousFeedbackEnabled:_sendFeedbackButton.selected]; +} + +- (void)clearHistory { + [[[Environment getCurrent] recentCallManager] clearRecentCalls]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:SETTINGS_LOG_CLEAR_TITLE + message:SETTINGS_LOG_CLEAR_MESSAGE + delegate:nil + cancelButtonTitle:nil + otherButtonTitles:SETTINGS_LOG_CLEAR_CONFIRM, nil]; + [alertView show]; +} + +- (void)showDateFormatPicker { + NSArray *dateFormats = [[[Environment getCurrent] preferences] getAvailableDateFormats]; + + PreferenceListViewController *prefPicker = [PreferenceListViewController preferenceListViewControllerForSelectedValue:^NSString *{ + return [[Environment preferences] getDateFormat]; + } andOptions:dateFormats andSelectedBlock:^(NSString *newValue) { + [[Environment preferences] setDateFormat:newValue]; + }]; + + [self.navigationController pushViewController:prefPicker animated:YES]; +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return (NSInteger)[_sectionHeaderViews count]; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return _sectionHeaderViews[(NSUInteger)section]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return SECTION_HEADER_VIEW_HEIGHT; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + UIView *headerView = _sectionHeaderViews[(NSUInteger)section]; + if (headerView == _privacyAndSecurityHeaderView) { + return (NSInteger)[_privacyTableViewCells count]; + } else if (headerView == _locationOverridesHeaderView) { + return (NSInteger)[_localizationTableViewCells count]; + } else { + return 0; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UIView *headerView = _sectionHeaderViews[(NSUInteger)indexPath.section]; + UITableViewCell *cell = nil; + if (headerView == _privacyAndSecurityHeaderView) { + cell = _privacyTableViewCells[(NSUInteger)indexPath.row]; + } else if (headerView == _locationOverridesHeaderView) { + cell = _localizationTableViewCells[(NSUInteger)indexPath.row]; + } + + [self findAndLocalizeLabelsForView:cell]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + if (cell == _clearHistoryLogCell) { + [self clearHistory]; + } + if (cell == _dateFormatCell) { + [self showDateFormatPicker]; + } +} + +- (void)findAndLocalizeLabelsForView:(UIView *)view { + for (UIView *subview in view.subviews) { + if ([subview respondsToSelector:@selector(localizationKey)]) { + LocalizableCustomFontLabel *label = (LocalizableCustomFontLabel *)subview; + if (label.localizationKey) { + label.text = NSLocalizedString(label.localizationKey, @""); + } + } + [self findAndLocalizeLabelsForView:subview]; + } +} + +@end diff --git a/Signal/src/view controllers/TabBarParentViewController.h b/Signal/src/view controllers/TabBarParentViewController.h new file mode 100644 index 000000000..6326d2d46 --- /dev/null +++ b/Signal/src/view controllers/TabBarParentViewController.h @@ -0,0 +1,37 @@ +#import + +#import "InboxFeedViewController.h" +#import "SettingsViewController.h" + +#define BOTTOM_TAB_BAR_HEIGHT 64 + +@interface TabBarParentViewController : UIViewController + +@property (nonatomic, strong) IBOutlet UIButton *tabBarFavouritesButton; +@property (nonatomic, strong) IBOutlet UIButton *tabBarInboxButton; +@property (nonatomic, strong) IBOutlet UIButton *tabBarContactsButton; +@property (nonatomic, strong) IBOutlet UIButton *tabBarDialerButton; +@property (nonatomic, strong) IBOutlet UIButton *tabBarCallLogButton; + +@property (nonatomic, strong) IBOutlet UILabel *missedCallCountLabel; +@property (nonatomic, strong) IBOutlet UIImageView *whisperUserUpdateImageView; + +@property (nonatomic, strong) IBOutlet UIView *viewControllerFrameView; +@property (nonatomic, strong) InboxFeedViewController *inboxFeedViewController; +@property (nonatomic, strong) SettingsViewController *settingsViewController; + +- (IBAction)presentInboxViewController; +- (IBAction)presentDialerViewController; +- (IBAction)presentContactsViewController; +- (IBAction)presentFavouritesViewController; +- (IBAction)presentRecentCallsViewController; +- (void)presentInviteContactsViewController; + +- (void)presentSettingsViewController; +- (void)updateMissedCallCountLabel; + +- (void)setNewWhisperUsersAsSeen:(NSArray *)users; + +- (void)showDialerViewControllerWithNumber:(PhoneNumber *)number; + +@end diff --git a/Signal/src/view controllers/TabBarParentViewController.m b/Signal/src/view controllers/TabBarParentViewController.m new file mode 100644 index 000000000..1149d96d9 --- /dev/null +++ b/Signal/src/view controllers/TabBarParentViewController.m @@ -0,0 +1,211 @@ +#import "AppAudioManager.h" +#import "CallLogViewController.h" +#import "ContactBrowseViewController.h" +#import "DialerViewController.h" +#import "Environment.h" +#import "FavouritesViewController.h" +#import "InviteContactsViewController.h" +#import "NotificationManifest.h" +#import "RecentCallManager.h" +#import "RegisterViewController.h" +#import "TabBarParentViewController.h" + +#import + +@interface TabBarParentViewController () { + DialerViewController *_dialerViewController; + ContactBrowseViewController *_contactsViewController; + CallLogViewController *_callLogViewController; + FavouritesViewController *_favouritesViewController; + InviteContactsViewController *_inviteContactsViewController; + + UINavigationController *_contactNavigationController; + UINavigationController *_dialerNavigationController; + UINavigationController *_callLogNavigationController; + UINavigationController *_inboxFeedNavigationController; + UINavigationController *_favouritesNavigationController; + UINavigationController *_settingsNavigationController; + UINavigationController *_inviteContactsNavigationController; + + UIViewController *_currentViewController; +} + +@end + +@implementation TabBarParentViewController + +- (id)init { + if ((self = [super init])) { + _settingsViewController = [SettingsViewController new]; + _inboxFeedViewController = [InboxFeedViewController new]; + _settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:_settingsViewController]; + _inviteContactsViewController = [InviteContactsViewController new]; + _inviteContactsNavigationController = [[UINavigationController alloc] initWithRootViewController:_inviteContactsViewController]; + _contactsViewController = [ContactBrowseViewController new]; + _contactNavigationController = [[UINavigationController alloc] initWithRootViewController:_contactsViewController]; + [[AppAudioManager sharedInstance] requestRequiredPermissionsIfNeeded]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self updateMissedCallCountLabel]; + if (!_currentViewController) { + [self presentInboxViewController]; + } + _whisperUserUpdateImageView.hidden = [self hideUserUpdateNotification]; + + ObservableValue *recentCallObservable = [[[Environment getCurrent] recentCallManager] getObservableRecentCalls]; + [recentCallObservable watchLatestValue:^(NSArray *latestRecents) { + [self updateMissedCallCountLabel]; + } onThread:[NSThread mainThread] untilCancelled:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(newUsersDetected:) + name:NOTIFICATION_NEW_USERS_AVAILABLE + object:nil]; + +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return _currentViewController == _dialerNavigationController ? UIStatusBarStyleDefault: UIStatusBarStyleLightContent; +} + +- (void)presentChildViewController:(UIViewController *)controller { + [self removeCurrentChildViewController]; + _currentViewController = controller; + [self addChildViewController:controller]; + controller.view.frame = _viewControllerFrameView.frame; + [_viewControllerFrameView addSubview:controller.view]; + [controller didMoveToParentViewController:self]; + [self setNeedsStatusBarAppearanceUpdate]; + + _tabBarFavouritesButton.backgroundColor = [UIUtil darkBackgroundColor]; + _tabBarContactsButton.backgroundColor = [UIUtil darkBackgroundColor]; + _tabBarDialerButton.backgroundColor = [UIUtil darkBackgroundColor]; + _tabBarInboxButton.backgroundColor = [UIUtil darkBackgroundColor]; + _tabBarCallLogButton.backgroundColor = [UIUtil darkBackgroundColor]; +} + +- (void)removeCurrentChildViewController { + if (_currentViewController) { + [_currentViewController willMoveToParentViewController:nil]; + [_currentViewController.view removeFromSuperview]; + [_currentViewController removeFromParentViewController]; + } +} + +- (void)presentInboxViewController { + if (!_inboxFeedNavigationController) { + _inboxFeedNavigationController = [[UINavigationController alloc] initWithRootViewController:_inboxFeedViewController]; + } + + if (_currentViewController == _inboxFeedNavigationController) { + [_inboxFeedNavigationController popToRootViewControllerAnimated:YES]; + } else { + [self presentChildViewController:_inboxFeedNavigationController]; + _tabBarInboxButton.backgroundColor = [UIColor darkGrayColor]; + } +} + +- (IBAction)presentDialerViewController { + [self showDialerViewControllerWithNumber:nil]; +} + +- (void)presentContactsViewController { + [_contactNavigationController popToRootViewControllerAnimated:NO]; + [self presentChildViewController:_contactNavigationController]; + _tabBarContactsButton.backgroundColor = [UIColor darkGrayColor]; +} + +- (void)presentRecentCallsViewController { + if (!_callLogViewController) { + _callLogViewController = [CallLogViewController new]; + _callLogNavigationController = [[UINavigationController alloc] initWithRootViewController:_callLogViewController]; + } + + [self presentChildViewController:_callLogNavigationController]; + _tabBarCallLogButton.backgroundColor = [UIColor darkGrayColor]; +} + +- (void)presentFavouritesViewController { + if (!_favouritesViewController) { + _favouritesViewController = [FavouritesViewController new]; + _favouritesNavigationController = [[UINavigationController alloc] initWithRootViewController:_favouritesViewController]; + } + + [_favouritesNavigationController popToRootViewControllerAnimated:NO]; + [self presentChildViewController:_favouritesNavigationController]; + _tabBarFavouritesButton.backgroundColor = [UIColor darkGrayColor]; +} + +- (void)presentInviteContactsViewController { + [_inviteContactsNavigationController popToRootViewControllerAnimated:NO]; + [self presentChildViewController:_inviteContactsNavigationController]; +} + +- (void)presentSettingsViewController { + [self presentChildViewController:_settingsNavigationController]; +} + +- (void)presentLeftSideMenu { + [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil]; +} + +- (void)showDialerViewControllerWithNumber:(PhoneNumber *)number { + if (!_dialerViewController) { + _dialerViewController = [DialerViewController new]; + _dialerNavigationController = [[UINavigationController alloc] initWithRootViewController:_dialerViewController]; + } + if (number) { + _dialerViewController.phoneNumber = number; + } + [_dialerNavigationController popToRootViewControllerAnimated:NO]; + [self presentChildViewController:_dialerNavigationController]; + _tabBarDialerButton.backgroundColor = [UIColor darkGrayColor]; +} + +- (void)updateMissedCallCountLabel { + NSUInteger missedCallCount = [[[Environment getCurrent] recentCallManager] missedCallCount]; + if (missedCallCount > 0) { + _tabBarInboxButton.frame = CGRectMake(CGRectGetMinX(_tabBarInboxButton.frame), + CGRectGetMinY(_tabBarInboxButton.frame), + CGRectGetWidth(_tabBarInboxButton.frame), + CGRectGetHeight(_tabBarInboxButton.frame) - CGRectGetHeight(_missedCallCountLabel.frame)); + _missedCallCountLabel.text = [NSString stringWithFormat:@"%d",missedCallCount]; + _missedCallCountLabel.hidden = NO; + } else { + _tabBarInboxButton.frame = CGRectMake(CGRectGetMinX(_tabBarInboxButton.frame), + CGRectGetMinY(_tabBarInboxButton.frame), + CGRectGetWidth(_tabBarInboxButton.frame), + CGRectGetHeight(_tabBarInboxButton.frame)); + _missedCallCountLabel.hidden = YES; + } +} + +#pragma mark - Contact Updates + +- (void)newUsersDetected:(NSNotification* )notification { + dispatch_async( dispatch_get_main_queue(), ^{ + NSArray *newUsers = [[notification userInfo] objectForKey:NOTIFICATION_DATAKEY_NEW_USERS]; + [self updateNewUsers:newUsers]; + }); +} + +- (void)updateNewUsers:(NSArray *)users { + [_inviteContactsViewController updateWithNewWhisperUsers:users]; + [_contactsViewController showNotificationForNewWhisperUsers:users]; + _whisperUserUpdateImageView.hidden = [self hideUserUpdateNotification]; +} + +- (void)setNewWhisperUsersAsSeen:(NSArray *)users { + [[[Environment getCurrent] contactsManager] addContactsToKnownWhisperUsers:users]; + [_contactsViewController showNotificationForNewWhisperUsers:nil]; + _whisperUserUpdateImageView.hidden = [self hideUserUpdateNotification]; + } + +-(BOOL) hideUserUpdateNotification { + return (0 == [[[Environment getCurrent] contactsManager] getNumberOfUnacknowledgedCurrentUsers]); +} +@end diff --git a/Signal/src/view controllers/TabBarParentViewController.xib b/Signal/src/view controllers/TabBarParentViewController.xib new file mode 100644 index 000000000..f17110837 --- /dev/null +++ b/Signal/src/view controllers/TabBarParentViewController.xib @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/CallAudioManagerDemo.xib b/Signal/src/view controllers/xibs/CallAudioManagerDemo.xib new file mode 100644 index 000000000..34c71d7dd --- /dev/null +++ b/Signal/src/view controllers/xibs/CallAudioManagerDemo.xib @@ -0,0 +1,539 @@ + + + + 1552 + 12F37 + 3084 + 1187.39 + 626.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 2083 + + + IBProxyObject + IBUIButton + IBUILabel + IBUITableView + IBUIView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 292 + {{11, 173}, {140, 52}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + 1 + Answer + + 3 + MQA + + + 1 + MCAwLjQ0ODMwNTg3NjQgMC4wMTQ0MjU2MDIyMQA + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + + 292 + {{159, 173}, {153, 52}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + 1 + Reject + + + 1 + MC40NDgzMDU4NzY0IDAuMDEwNTY4MjM3MjkgMAA + + + + + + + 292 + {{159, 232}, {153, 15}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + 1 + Force Register + + + 1 + MC40NDgzMDU4NzY0IDAuMDEwNTY4MjM3MjkgMAA + + + + + + + 292 + {{11, 8}, {301, 44}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + 1 + Call ... + + + 1 + MCAwLjQ0ODMwNTg3NjQgMC4wMTQ0MjU2MDIyMQA + + + + + + + 292 + {{159, 8}, {153, 44}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + 1 + Hangup + + + 1 + MC41IDAgMAA + + + + + + + 274 + {{-7, 254}, {357, 294}} + + _NS:9 + + YES + IBCocoaTouchFramework + YES + 1 + 0 + YES + 20 + 22 + 22 + + + + 292 + {{11, 90}, {301, 27}} + + + _NS:9 + NO + YES + 7 + NO + IBCocoaTouchFramework + [Call Progress] + + 1 + MCAwIDAAA + darkTextColor + + + 0 + 10 + 0 + + 1 + 17 + + + Helvetica + 17 + 16 + + NO + 301 + + + + 292 + {{11, 52}, {301, 30}} + + + _NS:9 + NO + YES + 7 + NO + IBCocoaTouchFramework + [Remote Number] + + + 0 + 10 + 0 + + + NO + 301 + + + + 292 + {{11, 125}, {301, 25}} + + + _NS:9 + NO + YES + 7 + NO + IBCocoaTouchFramework + [Short Authentication String] + + + 0 + 10 + 0 + + + NO + 301 + + + {{0, 20}, {320, 548}} + + + + 3 + MQA + + 2 + + + + + IBUIScreenMetrics + + YES + + + + + + {320, 568} + {568, 320} + + + IBCocoaTouchFramework + Retina 4 Full Screen + 2 + + IBCocoaTouchFramework + + + + + + + view + + + + 10 + + + + rejectButton + + + + 133 + + + + answerButton + + + + 134 + + + + callButton + + + + 135 + + + + hangupButton + + + + 136 + + + + loggingTableView + + + + 147 + + + + remoteNumberLabel + + + + 152 + + + + callProgressLabel + + + + 153 + + + + shortAuthStringLabel + + + + 154 + + + + registerLocalNumberButton + + + + 157 + + + + answer + + + 7 + + 140 + + + + hangup + + + 7 + + 143 + + + + call + + + 7 + + 139 + + + + reject + + + 7 + + 141 + + + + dataSource + + + + 145 + + + + delegate + + + + 146 + + + + forceRegister + + + 7 + + 158 + + + + + + 0 + + + + + + 1 + + + + + + + + + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 29 + + + + + 33 + + + + + 124 + + + + + 144 + + + + + 128 + + + + + 148 + + + + + 150 + + + + + 151 + + + + + 155 + + + + + + + CallAudioManagerDemo + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 158 + + + 0 + IBCocoaTouchFramework + YES + 3 + 2083 + + diff --git a/Signal/src/view controllers/xibs/CallLogViewController.xib b/Signal/src/view controllers/xibs/CallLogViewController.xib new file mode 100644 index 000000000..8de772697 --- /dev/null +++ b/Signal/src/view controllers/xibs/CallLogViewController.xib @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/ContactBrowseViewController.xib b/Signal/src/view controllers/xibs/ContactBrowseViewController.xib new file mode 100644 index 000000000..03535e74f --- /dev/null +++ b/Signal/src/view controllers/xibs/ContactBrowseViewController.xib @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/ContactDetailTableViewCell.xib b/Signal/src/view controllers/xibs/ContactDetailTableViewCell.xib new file mode 100644 index 000000000..39a34b132 --- /dev/null +++ b/Signal/src/view controllers/xibs/ContactDetailTableViewCell.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/ContactDetailViewController.xib b/Signal/src/view controllers/xibs/ContactDetailViewController.xib new file mode 100644 index 000000000..ecf90e18c --- /dev/null +++ b/Signal/src/view controllers/xibs/ContactDetailViewController.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal/src/view controllers/xibs/DialerViewController.xib b/Signal/src/view controllers/xibs/DialerViewController.xib new file mode 100644 index 000000000..0bc9e6eb4 --- /dev/null +++ b/Signal/src/view controllers/xibs/DialerViewController.xib @@ -0,0 +1,938 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal/src/view controllers/xibs/DowngradeCallViewController.xib b/Signal/src/view controllers/xibs/DowngradeCallViewController.xib new file mode 100644 index 000000000..0babfcc55 --- /dev/null +++ b/Signal/src/view controllers/xibs/DowngradeCallViewController.xib @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/FavouritesViewController.xib b/Signal/src/view controllers/xibs/FavouritesViewController.xib new file mode 100644 index 000000000..5602d43d0 --- /dev/null +++ b/Signal/src/view controllers/xibs/FavouritesViewController.xib @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/InCallViewController.xib b/Signal/src/view controllers/xibs/InCallViewController.xib new file mode 100644 index 000000000..1fccd410b --- /dev/null +++ b/Signal/src/view controllers/xibs/InCallViewController.xib @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/InboxFeedViewController.xib b/Signal/src/view controllers/xibs/InboxFeedViewController.xib new file mode 100644 index 000000000..2434fd854 --- /dev/null +++ b/Signal/src/view controllers/xibs/InboxFeedViewController.xib @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal/src/view controllers/xibs/LeftSideMenuViewController.xib b/Signal/src/view controllers/xibs/LeftSideMenuViewController.xib new file mode 100644 index 000000000..641741eec --- /dev/null +++ b/Signal/src/view controllers/xibs/LeftSideMenuViewController.xib @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/view controllers/xibs/RegisterViewController.xib b/Signal/src/view controllers/xibs/RegisterViewController.xib new file mode 100644 index 000000000..250243f50 --- /dev/null +++ b/Signal/src/view controllers/xibs/RegisterViewController.xib @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal/src/view controllers/xibs/SettingsViewController.xib b/Signal/src/view controllers/xibs/SettingsViewController.xib new file mode 100644 index 000000000..caa97d4ec --- /dev/null +++ b/Signal/src/view controllers/xibs/SettingsViewController.xib @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/CallLogTableViewCell.h b/Signal/src/views/CallLogTableViewCell.h new file mode 100644 index 000000000..a693e8c55 --- /dev/null +++ b/Signal/src/views/CallLogTableViewCell.h @@ -0,0 +1,36 @@ +#import +#import "RecentCall.h" +#import "Contact.h" +#import "NextResponderScrollView.h" + +/** + * + * RecentCallTableViewCell displays a Recent Call object and handles deleting by - + * swiping past an offset greater than the delete button width + * + */ + +@class CallLogTableViewCell; +@protocol CallLogTableViewCellDelegate + +- (void)recentCallTableViewCellTappedDelete:(CallLogTableViewCell *)cell; +- (void)recentCallTableViewCellTappedCall:(CallLogTableViewCell *)cell; + +@end + +@interface CallLogTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *contactNameLabel; +@property (nonatomic, strong) IBOutlet UILabel *contactNumberLabel; +@property (nonatomic, strong) IBOutlet UILabel *timeLabel; +@property (nonatomic, strong) IBOutlet UIImageView *callTypeImageView; +@property (nonatomic, strong) IBOutlet NextResponderScrollView *scrollView; +@property (nonatomic, strong) IBOutlet UIView *contentContainerView; +@property (nonatomic, strong) IBOutlet UIView *deleteView; +@property (nonatomic, strong) IBOutlet UIImageView *deleteImageView; +@property (nonatomic, assign) id delegate; + +- (void)configureWithRecentCall:(RecentCall *)recentCall; +- (IBAction)phoneCallButtonTapped; + +@end diff --git a/Signal/src/views/CallLogTableViewCell.m b/Signal/src/views/CallLogTableViewCell.m new file mode 100644 index 000000000..78e0b64a5 --- /dev/null +++ b/Signal/src/views/CallLogTableViewCell.m @@ -0,0 +1,98 @@ +#import "CallLogTableViewCell.h" +#import "Environment.h" +#import "ContactsManager.h" +#import "PreferencesUtil.h" +#import "LocalizableText.h" +#import "Util.h" + +#define DELETE_IMAGE_VIEW_WIDTH 19.0f + +@implementation CallLogTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) + owner:self + options:nil] objectAtIndex:0]; + if (self) { + _scrollView.contentSize = CGSizeMake(CGRectGetWidth(_contentContainerView.bounds), + CGRectGetHeight(_scrollView.frame)); + _deleteImageView.image = [_deleteImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)prepareForReuse { + _scrollView.contentOffset = CGPointMake(0, 0); + [super prepareForReuse]; +} + +- (void)configureWithRecentCall:(RecentCall *)recentCall { + Contact *contact = [[[Environment getCurrent] contactsManager] latestContactWithRecordId:recentCall.contactRecordID]; + if (contact) { + _contactNameLabel.text = [contact fullName]; + } else { + _contactNameLabel.text = UNKNOWN_CONTACT_NAME; + } + + if (recentCall.callType == RPRecentCallTypeOutgoing) { + _callTypeImageView.image = [UIImage imageNamed:CALL_TYPE_IMAGE_NAME_OUTGOING]; + } else { + _callTypeImageView.image = [UIImage imageNamed:CALL_TYPE_IMAGE_NAME_INCOMING]; + } + + _contactNumberLabel.text = [recentCall.phoneNumber localizedDescriptionForUser]; + + if ([DateUtil dateIsOlderThanOneWeek:[recentCall date]]) { + _timeLabel.text = [[DateUtil dateFormatter] stringFromDate:[recentCall date]]; + } else { + _timeLabel.text = [[DateUtil weekdayFormatter] stringFromDate:[recentCall date]]; + } +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + if (scrollView.contentOffset.x > CGRectGetWidth(_deleteView.frame)) { + _deleteImageView.tintColor = [UIUtil redColor]; + + _deleteImageView.bounds = CGRectMake(_deleteImageView.bounds.origin.x, + _deleteImageView.bounds.origin.y, + DELETE_IMAGE_VIEW_WIDTH, + _deleteImageView.bounds.size.height); + } else { + + float ratio = _scrollView.contentOffset.x / CGRectGetWidth(_deleteView.frame); + float newWidth = DELETE_IMAGE_VIEW_WIDTH/2 + (DELETE_IMAGE_VIEW_WIDTH * ratio)/2.0f; + + _deleteImageView.bounds = CGRectMake(_deleteImageView.bounds.origin.x, + _deleteImageView.bounds.origin.y, + newWidth, + _deleteImageView.bounds.size.height); + _deleteImageView.tintColor = [UIColor whiteColor]; + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + + if (scrollView.contentOffset.x > CGRectGetWidth(_deleteView.frame)) { + [_delegate recentCallTableViewCellTappedDelete:self]; + } else { + *targetContentOffset = CGPointMake(0, 0); + } +} + +#pragma mark - Actions + +- (IBAction)phoneCallButtonTapped { + [_delegate recentCallTableViewCellTappedCall:self]; +} + +@end diff --git a/Signal/src/views/ContactDetailTableViewCell.h b/Signal/src/views/ContactDetailTableViewCell.h new file mode 100644 index 000000000..5500cb8ee --- /dev/null +++ b/Signal/src/views/ContactDetailTableViewCell.h @@ -0,0 +1,21 @@ +#import +#import "PhoneNumber.h" + +/** + * + * ContactDetailTableViewCell displays a contact communication item (Phone number, email, notes) + * This will hide/show SMS/Phone buttons when needed, depending on the item type. + * The color side view is blue if Whisper number or notes, and green otherwise. + * + */ + +@interface ContactDetailTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *infoTypeLabel; +@property (nonatomic, strong) IBOutlet UILabel *infoDisplayLabel; + +- (void)configureWithPhoneNumber:(PhoneNumber *)numberString isSecure:(BOOL)isSecure; +- (void)configureWithEmailString:(NSString *)emailString; +- (void)configureWithNotes:(NSString *)notes; + +@end diff --git a/Signal/src/views/ContactDetailTableViewCell.m b/Signal/src/views/ContactDetailTableViewCell.m new file mode 100644 index 000000000..d8741e57f --- /dev/null +++ b/Signal/src/views/ContactDetailTableViewCell.m @@ -0,0 +1,49 @@ +#import "ContactDetailTableViewCell.h" +#import "LocalizableText.h" +#import "PhoneNumber.h" +#import "Environment.h" +#import "PhoneNumberDirectoryFilter.h" +#import "PhoneNumberDirectoryFilterManager.h" + +#define INFO_DISPLAY_LABEL_DEFAULT_WIDTH 202 +@implementation ContactDetailTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil][0]; + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithPhoneNumber:(PhoneNumber *)phoneNumber isSecure:(BOOL)isSecure { + _infoDisplayLabel.frame = CGRectMake(_infoDisplayLabel.frame.origin.x, + _infoDisplayLabel.frame.origin.y, + INFO_DISPLAY_LABEL_DEFAULT_WIDTH, + CGRectGetHeight(_infoDisplayLabel.frame)); + + _infoDisplayLabel.text = [phoneNumber localizedDescriptionForUser]; + + if (isSecure) { + _infoTypeLabel.text = CONTACT_DETAIL_COMM_TYPE_SECURE; + } else { + _infoTypeLabel.text = CONTACT_DETAIL_COMM_TYPE_INSECURE; + } +} + +- (void)configureWithEmailString:(NSString *)emailString { + _infoTypeLabel.text = CONTACT_DETAIL_COMM_TYPE_EMAIL; + _infoDisplayLabel.text = emailString; +} + +- (void)configureWithNotes:(NSString *)notes { + _infoDisplayLabel.frame = CGRectMake(_infoDisplayLabel.frame.origin.x, + _infoDisplayLabel.frame.origin.y, + fabs(_infoDisplayLabel.frame.origin.x - CGRectGetWidth(self.frame)), + CGRectGetHeight(_infoDisplayLabel.frame)); + _infoDisplayLabel.text = notes; + _infoTypeLabel.text = CONTACT_DETAIL_COMM_TYPE_NOTES; +} + +@end diff --git a/Signal/src/views/ContactTableViewCell.h b/Signal/src/views/ContactTableViewCell.h new file mode 100644 index 000000000..b8f4a4a79 --- /dev/null +++ b/Signal/src/views/ContactTableViewCell.h @@ -0,0 +1,17 @@ +#import +#import "ContactsManager.h" + +/** + * + * ContactTableViewCell displays a contact from a Contact object. + * + */ + +@interface ContactTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *nameLabel; +@property (nonatomic, strong) IBOutlet UIImageView *contactPictureView; + +- (void)configureWithContact:(Contact *)contact; + +@end diff --git a/Signal/src/views/ContactTableViewCell.m b/Signal/src/views/ContactTableViewCell.m new file mode 100644 index 000000000..cca7f377f --- /dev/null +++ b/Signal/src/views/ContactTableViewCell.m @@ -0,0 +1,58 @@ +#import "ContactTableViewCell.h" + +#define CONTACT_TABLE_CELL_BORDER_WIDTH 1.0f + +@implementation ContactTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] objectAtIndex:0]; + _contactPictureView.layer.borderColor = [[UIColor lightGrayColor] CGColor]; + _contactPictureView.layer.masksToBounds = YES; + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithContact:(Contact *)contact { + + _nameLabel.attributedText = [self attributedStringForContact:contact]; + + UIImage *image = contact.image; + BOOL imageNotNil = image != nil; + [self configureBorder:imageNotNil]; + + if (imageNotNil) { + _contactPictureView.image = image; + } else { + _contactPictureView.image = nil; + } +} + +- (void)configureBorder:(BOOL)show { + _contactPictureView.layer.borderWidth = show ? CONTACT_TABLE_CELL_BORDER_WIDTH : 0; + _contactPictureView.layer.cornerRadius = show ? CGRectGetWidth(_contactPictureView.frame)/2 : 0; +} + +- (NSAttributedString *)attributedStringForContact:(Contact *)contact { + NSMutableAttributedString *fullNameAttributedString = [[NSMutableAttributedString alloc] initWithString:[contact fullName]]; + + UIFont *firstNameFont; + UIFont *lastNameFont; + + if (ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst) { + firstNameFont = [UIFont boldSystemFontOfSize:_nameLabel.font.pointSize]; + lastNameFont = [UIFont systemFontOfSize:_nameLabel.font.pointSize]; + } else{ + firstNameFont = [UIFont systemFontOfSize:_nameLabel.font.pointSize]; + lastNameFont = [UIFont boldSystemFontOfSize:_nameLabel.font.pointSize]; + } + [fullNameAttributedString addAttribute:NSFontAttributeName value:firstNameFont range:NSMakeRange(0, [[contact firstName] length])]; + [fullNameAttributedString addAttribute:NSFontAttributeName value:lastNameFont range:NSMakeRange([[contact firstName] length] + 1, [[contact lastName] length])]; + + [fullNameAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, [[contact fullName] length])]; + return fullNameAttributedString; +} + +@end diff --git a/Signal/src/views/CountryCodeTableViewCell.h b/Signal/src/views/CountryCodeTableViewCell.h new file mode 100644 index 000000000..ba6c3eaf1 --- /dev/null +++ b/Signal/src/views/CountryCodeTableViewCell.h @@ -0,0 +1,10 @@ +#import + +@interface CountryCodeTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *countryCodeLabel; +@property (nonatomic, strong) IBOutlet UILabel *countryNameLabel; + +- (void)configureWithCountryCode:(NSString *)code andCountryName:(NSString *)name; + +@end diff --git a/Signal/src/views/CountryCodeTableViewCell.m b/Signal/src/views/CountryCodeTableViewCell.m new file mode 100644 index 000000000..f4174f3a4 --- /dev/null +++ b/Signal/src/views/CountryCodeTableViewCell.m @@ -0,0 +1,21 @@ +#import "CountryCodeTableViewCell.h" + +@implementation CountryCodeTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) + owner:self + options:nil] firstObject]; + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithCountryCode:(NSString *)code andCountryName:(NSString *)name { + _countryCodeLabel.text = code; + _countryNameLabel.text = name; +} + +@end diff --git a/Signal/src/views/DialerButtonView.h b/Signal/src/views/DialerButtonView.h new file mode 100644 index 000000000..8049277aa --- /dev/null +++ b/Signal/src/views/DialerButtonView.h @@ -0,0 +1,32 @@ +#import + +/** + * + * This class exists because a UIButton can't have 2 lines of text. + * DialerButtonView gives us customization and also localizes the label text. + * Localize text by the setting properties in the xib for letterLocalizationKey and numberLocalizationKey. + * A protocol is implemented to pass touch events for touchUpInside. + * + */ + +@class DialerButtonView; + +@protocol DialerButtonViewDelegate +- (void)dialerButtonViewDidSelect:(DialerButtonView *)view; +@end + +@interface DialerButtonView : UIView + +@property (nonatomic, strong) NSString *buttonInput; +@property (nonatomic, strong) NSString *letterLocalizationKey; +@property (nonatomic, strong) NSString *numberLocalizationKey; + +@property (nonatomic, strong) IBOutlet UILabel *numberLabel; +@property (nonatomic, strong) IBOutlet UILabel *letterLabel; +@property (nonatomic, assign) IBOutlet id delegate; + +- (IBAction)buttonTouchUp; +- (IBAction)buttonTouchCancel; +- (IBAction)buttonTouchDown; + +@end diff --git a/Signal/src/views/DialerButtonView.m b/Signal/src/views/DialerButtonView.m new file mode 100644 index 000000000..40f035563 --- /dev/null +++ b/Signal/src/views/DialerButtonView.m @@ -0,0 +1,40 @@ +#import "DialerButtonView.h" +#import "UIUtil.h" + +@implementation DialerButtonView + +- (void)awakeFromNib { + [super awakeFromNib]; + _numberLabel.text = NSLocalizedString(_numberLocalizationKey, @""); + _letterLabel.text = NSLocalizedString(_letterLocalizationKey, @""); + self.layer.cornerRadius = 4.0f; + self.layer.borderColor = [[UIUtil darkBackgroundColor] CGColor]; +} + +- (void)setSelected:(BOOL)isSelected { + + if (isSelected) { + self.backgroundColor = [UIUtil transparentLightGrayColor]; + self.layer.borderWidth = 0.5f; + } else { + self.backgroundColor = [UIColor clearColor]; + self.layer.borderWidth = 0.0f; + } +} + +#pragma mark - Actions + +- (void)buttonTouchUp { + [self setSelected:NO]; + [_delegate dialerButtonViewDidSelect:self]; +} + +- (void)buttonTouchCancel { + [self setSelected:NO]; +} + +- (void)buttonTouchDown { + [self setSelected:YES]; +} + +@end diff --git a/Signal/src/views/FavouriteTableViewCell.h b/Signal/src/views/FavouriteTableViewCell.h new file mode 100644 index 000000000..bf0c9bd47 --- /dev/null +++ b/Signal/src/views/FavouriteTableViewCell.h @@ -0,0 +1,26 @@ +#import +#import "ContactsManager.h" + +/** + * + * FavouriteTableViewCell displays a contact from a Contact object. + * + */ + +@class FavouriteTableViewCell; + +@protocol FavouriteTableViewCellDelegate +- (void)favouriteTableViewCellTappedCall:(FavouriteTableViewCell *)cell; +@end + + +@interface FavouriteTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *nameLabel; +@property (nonatomic, strong) IBOutlet UIImageView *contactPictureView; +@property (nonatomic, strong) id delegate; + +- (void)configureWithContact:(Contact *)contact; +- (IBAction)callTapped; + +@end diff --git a/Signal/src/views/FavouriteTableViewCell.m b/Signal/src/views/FavouriteTableViewCell.m new file mode 100644 index 000000000..b8ec040b0 --- /dev/null +++ b/Signal/src/views/FavouriteTableViewCell.m @@ -0,0 +1,51 @@ +#import "FavouriteTableViewCell.h" + +#define FAVOURITE_TABLE_CELL_BORDER_WIDTH 1.0f + +@implementation FavouriteTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] objectAtIndex:0]; + _contactPictureView.layer.borderColor = [[UIColor lightGrayColor] CGColor]; + _contactPictureView.layer.masksToBounds = YES; + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithContact:(Contact *)contact { + + _nameLabel.attributedText = [self attributedStringForContact:contact]; + + UIImage *image = contact.image; + BOOL imageNotNil = image != nil; + [self configureBorder:imageNotNil]; + + if (imageNotNil) { + _contactPictureView.image = image; + } else { + _contactPictureView.image = nil; + } +} + +- (void)callTapped { + [_delegate favouriteTableViewCellTappedCall:self]; +} + +- (void)configureBorder:(BOOL)show { + _contactPictureView.layer.borderWidth = show ? FAVOURITE_TABLE_CELL_BORDER_WIDTH : 0; + _contactPictureView.layer.cornerRadius = show ? CGRectGetWidth(_contactPictureView.frame)/2 : 0; +} + +- (NSAttributedString *)attributedStringForContact:(Contact *)contact { + NSMutableAttributedString *fullNameAttributedString = [[NSMutableAttributedString alloc] initWithString:[contact fullName]]; + + [fullNameAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:_nameLabel.font.pointSize] range:NSMakeRange(0, [[contact firstName] length])]; + [fullNameAttributedString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:_nameLabel.font.pointSize] range:NSMakeRange([[contact firstName] length] + 1, [[contact lastName] length])]; + [fullNameAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, [[contact fullName] length])]; + return fullNameAttributedString; +} + +@end diff --git a/Signal/src/views/InboxFeedFooterCell.h b/Signal/src/views/InboxFeedFooterCell.h new file mode 100644 index 000000000..c2b944473 --- /dev/null +++ b/Signal/src/views/InboxFeedFooterCell.h @@ -0,0 +1,15 @@ +#import + +/** + * + * The last cell of Inbox feed that displays an active count of the items left to be archived/deleted + * + */ + +@interface InboxFeedFooterCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *inboxCountLabel; +@property (nonatomic, strong) IBOutlet UILabel *inboxMessageLabelFirst; +@property (nonatomic, strong) IBOutlet UILabel *inboxMessageLabelSecond; + +@end diff --git a/Signal/src/views/InboxFeedFooterCell.m b/Signal/src/views/InboxFeedFooterCell.m new file mode 100644 index 000000000..048ec28b6 --- /dev/null +++ b/Signal/src/views/InboxFeedFooterCell.m @@ -0,0 +1,31 @@ +#import "InboxFeedFooterCell.h" +#import "RecentCallManager.h" +#import "LocalizableText.h" + +@implementation InboxFeedFooterCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil][0]; + if (self) { + ObservableValue *recentCallObserver = [[[Environment getCurrent] recentCallManager] getObservableRecentCalls]; + [recentCallObserver watchLatestValue:^(id latestValue) { + NSUInteger inboxCount = [[[[Environment getCurrent] recentCallManager] recentsForSearchString:nil andExcludeArchived:YES] count]; + if (inboxCount == 0) { + _inboxCountLabel.text = @""; + _inboxMessageLabelFirst.text = HOME_FOOTER_FIRST_MESSAGE_CALLS_NIL; + _inboxMessageLabelSecond.text = HOME_FOOTER_SECOND_MESSAGE_CALLS_NIL; + } else { + _inboxCountLabel.text = [NSString stringWithFormat:@"%d", inboxCount]; + _inboxMessageLabelFirst.text = HOME_FOOTER_FIRST_MESSAGE_CALLS_UNSORTED; + _inboxMessageLabelSecond.text = inboxCount == 1 ? HOME_FOOTER_SECOND_MESSAGE_CALL_UNSORTED : HOME_FOOTER_SECOND_MESSAGE_CALLS_UNSORTED; + } + } onThread:[NSThread mainThread] untilCancelled:nil]; + } + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +@end diff --git a/Signal/src/views/InboxFeedFooterCell.xib b/Signal/src/views/InboxFeedFooterCell.xib new file mode 100644 index 000000000..d7c76bb3a --- /dev/null +++ b/Signal/src/views/InboxFeedFooterCell.xib @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/InboxFeedTableViewCell.h b/Signal/src/views/InboxFeedTableViewCell.h new file mode 100644 index 000000000..fcc76b0f5 --- /dev/null +++ b/Signal/src/views/InboxFeedTableViewCell.h @@ -0,0 +1,39 @@ +#import +#import "RecentCall.h" +#import "ContactsManager.h" +#import "NextResponderScrollView.h" + +/** + * + * InboxFeedTableViewCell displays a non-archived Recent Call object and delegates deleting and archiving. + * Archiving and deleting is started by the scroll view being scrolled past/below an offset greater than the respective button. + * + */ + +@class InboxFeedTableViewCell; +@protocol InboxFeedTableViewCellDelegate + +- (void)inboxFeedTableViewCellTappedDelete:(InboxFeedTableViewCell *)cell; +- (void)inboxFeedTableViewCellTappedArchive:(InboxFeedTableViewCell *)cell; + +@end + +@interface InboxFeedTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *nameLabel; +@property (nonatomic, strong) IBOutlet UIImageView *contactPictureView; +@property (nonatomic, strong) IBOutlet UIImageView *callTypeImageView; +@property (nonatomic, strong) IBOutlet UILabel *numberLabel; +@property (nonatomic, strong) IBOutlet UILabel *timeLabel; +@property (nonatomic, strong) IBOutlet NextResponderScrollView *scrollView; +@property (nonatomic, strong) IBOutlet UIView *contentContainerView; +@property (nonatomic, strong) IBOutlet UIView *missedCallView; +@property (nonatomic, strong) IBOutlet UIView *deleteView; +@property (nonatomic, strong) IBOutlet UIView *archiveView; +@property (nonatomic, strong) IBOutlet UIImageView *deleteImageView; +@property (nonatomic, strong) IBOutlet UIImageView *archiveImageView; +@property (nonatomic, assign) id delegate; + +- (void)configureWithRecentCall:(RecentCall *)recentCall; + +@end diff --git a/Signal/src/views/InboxFeedTableViewCell.m b/Signal/src/views/InboxFeedTableViewCell.m new file mode 100644 index 000000000..2bec8d2ba --- /dev/null +++ b/Signal/src/views/InboxFeedTableViewCell.m @@ -0,0 +1,158 @@ +#import "InboxFeedTableViewCell.h" +#import "LocalizableText.h" +#import "Environment.h" +#import "Util.h" + +#define ARCHIVE_IMAGE_VIEW_WIDTH 22.0f +#define DELETE_IMAGE_VIEW_WIDTH 19.0f +#define TIME_LABEL_SIZE 10 +#define DATE_LABEL_SIZE 13 + +#define MISSED_CALL_VIEW_CORNER_RADIUS 6.0f + +@implementation InboxFeedTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) + owner:self + options:nil] objectAtIndex:0]; + + + if (self) { + _scrollView.contentSize = CGSizeMake(CGRectGetWidth(_contentContainerView.bounds), + CGRectGetHeight(_scrollView.frame)); + + [UIUtil applyRoundedBorderToImageView:&_contactPictureView]; + + _scrollView.contentOffset = CGPointMake(CGRectGetWidth(_archiveView.frame), 0); + _missedCallView.layer.cornerRadius = MISSED_CALL_VIEW_CORNER_RADIUS; + _deleteImageView.image = [_deleteImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + _archiveImageView.image = [_archiveImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + return self; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithRecentCall:(RecentCall *)recentCall { + Contact *contact = [[[Environment getCurrent] contactsManager] latestContactWithRecordId:recentCall.contactRecordID]; + + if (contact) { + _nameLabel.text = [contact fullName]; + if (contact.image) { + _contactPictureView.image = contact.image; + } else { + _contactPictureView.image = nil; + } + } else { + _nameLabel.text = UNKNOWN_CONTACT_NAME; + _contactPictureView.image = nil; + } + + if (recentCall.callType == RPRecentCallTypeOutgoing) { + _callTypeImageView.image = [UIImage imageNamed:CALL_TYPE_IMAGE_NAME_OUTGOING]; + } else { + _callTypeImageView.image = [UIImage imageNamed:CALL_TYPE_IMAGE_NAME_INCOMING]; + } + + _missedCallView.hidden = recentCall.userNotified; + _numberLabel.text = [recentCall.phoneNumber localizedDescriptionForUser]; + _timeLabel.attributedText = [self dateArrributedString:[recentCall date]]; +} + +#pragma mark - Date formatting + +- (NSAttributedString *)dateArrributedString:(NSDate *)date { + + NSString *dateString; + NSString *timeString = [[DateUtil timeFormatter] stringFromDate:date]; + + + if ([DateUtil dateIsOlderThanOneWeek:date]) { + dateString = [[DateUtil dateFormatter] stringFromDate:date]; + } else { + dateString = [[DateUtil weekdayFormatter] stringFromDate:date]; + } + + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[timeString stringByAppendingString:dateString]]; + + [attributedString addAttribute:NSForegroundColorAttributeName + value:[UIColor darkGrayColor] + range:NSMakeRange(0, [timeString length])]; + + [attributedString addAttribute:NSForegroundColorAttributeName + value:[UIUtil darkBackgroundColor] + range:NSMakeRange([timeString length],[dateString length])]; + + [attributedString addAttribute:NSFontAttributeName + value:[UIUtil helveticaLightWithSize:TIME_LABEL_SIZE] + range:NSMakeRange(0, [timeString length])]; + + [attributedString addAttribute:NSFontAttributeName + value:[UIUtil helveticaRegularWithSize:DATE_LABEL_SIZE] + range:NSMakeRange([timeString length],[dateString length])]; + + return attributedString; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + if (_scrollView.contentOffset.x < 0) { + _archiveImageView.tintColor = [UIUtil redColor]; + _archiveImageView.bounds = CGRectMake(_archiveImageView.bounds.origin.x, + _archiveImageView.bounds.origin.y, + ARCHIVE_IMAGE_VIEW_WIDTH, + _archiveImageView.bounds.size.height); + } else { + + float ratio = (_archiveView.frame.size.width/2.0f - _scrollView.contentOffset.x) / (_archiveView.frame.size.width/2.0f); + float newWidth = ARCHIVE_IMAGE_VIEW_WIDTH/2 + (ARCHIVE_IMAGE_VIEW_WIDTH * ratio)/2.0f; + _archiveImageView.bounds = CGRectMake(_archiveImageView.bounds.origin.x, + _archiveImageView.bounds.origin.y, + newWidth, + _archiveImageView.bounds.size.height); + _archiveImageView.tintColor = [UIColor whiteColor]; + + } + + if (scrollView.contentOffset.x > CGRectGetWidth(_archiveView.frame)*2) { + _deleteImageView.tintColor = [UIUtil redColor]; + _deleteImageView.bounds = CGRectMake(_deleteImageView.bounds.origin.x, + _deleteImageView.bounds.origin.y, + DELETE_IMAGE_VIEW_WIDTH, + _deleteImageView.bounds.size.height); + } else { + + float ratio = _scrollView.contentOffset.x / (CGRectGetWidth(_deleteView.frame)*2); + float newWidth = DELETE_IMAGE_VIEW_WIDTH/2 + (DELETE_IMAGE_VIEW_WIDTH * ratio)/2.0f; + + _deleteImageView.bounds = CGRectMake(_deleteImageView.bounds.origin.x, + _deleteImageView.bounds.origin.y, + newWidth, + _deleteImageView.bounds.size.height); + _deleteImageView.tintColor = [UIColor whiteColor]; + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + + if (_scrollView.contentOffset.x < 0) { + [_delegate inboxFeedTableViewCellTappedArchive:self]; + } else { + *targetContentOffset = CGPointMake(CGRectGetWidth(_archiveView.frame), 0); + } + + if (scrollView.contentOffset.x > CGRectGetWidth(_archiveView.frame)*2) { + [_delegate inboxFeedTableViewCellTappedDelete:self]; + } else { + *targetContentOffset = CGPointMake(CGRectGetWidth(_archiveView.frame), 0); + } +} + +@end diff --git a/Signal/src/views/InteractiveLabel.h b/Signal/src/views/InteractiveLabel.h new file mode 100644 index 000000000..68b56c4a2 --- /dev/null +++ b/Signal/src/views/InteractiveLabel.h @@ -0,0 +1,8 @@ +#import + +@interface InteractiveLabel : UILabel + +-(void) onPaste:(void(^)(id sender)) pasteBlock; +-(void) onCopy:(void(^)(id sender)) copyBlock; + +@end diff --git a/Signal/src/views/InteractiveLabel.m b/Signal/src/views/InteractiveLabel.m new file mode 100644 index 000000000..598be8f61 --- /dev/null +++ b/Signal/src/views/InteractiveLabel.m @@ -0,0 +1,71 @@ +#import "InteractiveLabel.h" + +@interface InteractiveLabel () + +@property (copy) void (^pasteBlock) (id sender); +@property (copy) void (^copyBlock) (id sender); + +@end + +@implementation InteractiveLabel + +#pragma mark Menu Setup + +-(id) initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if(self) { + [self setupGestureRecognizer]; + } + return self; +} + +-(void) awakeFromNib { + [super awakeFromNib]; + [self setupGestureRecognizer]; +} + +-(BOOL) canBecomeFirstResponder{ + return YES; +} + +-(void) setupGestureRecognizer { + UILongPressGestureRecognizer* recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didRegisterLongPress:)]; + [self addGestureRecognizer:recognizer]; +} + + +-(void) didRegisterLongPress:(UILongPressGestureRecognizer*) recognizer { + if (recognizer.state == UIGestureRecognizerStateBegan) { + [self becomeFirstResponder]; + UIMenuController *menu = [UIMenuController sharedMenuController]; + [menu setTargetRect:self.frame inView:self.superview]; + [menu setMenuVisible:YES animated:YES]; + + } +} + +//todo: set custom dispaly logic via block for runtime decision +-(BOOL) canPerformAction:(SEL)action withSender:(id)sender { + if(action == @selector(paste:)) { return nil != self.pasteBlock;}; + if(action == @selector(copy:)) { return nil != self.copyBlock; }; + + return NO; +} + +#pragma mark Block Handling + +-(void) onPaste:(void (^)(id sender)) pasteAction { + self.pasteBlock = pasteAction; +} + +-(void) onCopy:(void (^)(id sender)) copyAction { + self.copyBlock = copyAction; +} + +-(void) paste:(id)sender { + if(self.pasteBlock){ + self.pasteBlock(sender); + } +} + +@end diff --git a/Signal/src/views/LeftSideMenuCell.h b/Signal/src/views/LeftSideMenuCell.h new file mode 100644 index 000000000..d6904c612 --- /dev/null +++ b/Signal/src/views/LeftSideMenuCell.h @@ -0,0 +1,7 @@ +#import + +@interface LeftSideMenuCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *menuTitleLabel; + +@end diff --git a/Signal/src/views/LeftSideMenuCell.m b/Signal/src/views/LeftSideMenuCell.m new file mode 100644 index 000000000..161f398db --- /dev/null +++ b/Signal/src/views/LeftSideMenuCell.m @@ -0,0 +1,26 @@ +#import "LeftSideMenuCell.h" +#import "UIUtil.h" + +@implementation LeftSideMenuCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) + owner:self + options:nil] firstObject]; + return self; +} + +- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { + [super setHighlighted:highlighted animated:animated]; + if (highlighted) { + _menuTitleLabel.textColor = [UIUtil darkBackgroundColor]; + } else { + _menuTitleLabel.textColor = [UIUtil whiteColor]; + } +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +@end diff --git a/Signal/src/views/LocalizableCustomFontLabel.h b/Signal/src/views/LocalizableCustomFontLabel.h new file mode 100644 index 000000000..4b6d82027 --- /dev/null +++ b/Signal/src/views/LocalizableCustomFontLabel.h @@ -0,0 +1,24 @@ +#import + +/** + * + * This class enables us to set custom fonts for labels in the xib without making an outlet and setting it manually + * Also contains a property for localization purposes + * + */ + +#define CUSTOM_FONT_LABELS \ +LocalizableCustomFontLabel(HelveticaNeueLTStdBoldLabel, HelveticaNeueLTStd-Bold) \ +LocalizableCustomFontLabel(HelveticaNeueLTStdLightLabel, HelveticaNeueLTStd-Lt) \ +LocalizableCustomFontLabel(HelveticaNeueLTStdMedLabel, HelveticaNeueLTStd-Md) \ +LocalizableCustomFontLabel(HelveticaNeueLTStdThinLabel, HelveticaNeueLTStd-Th) \ + +@interface LocalizableCustomFontLabel : UILabel + +@property (nonatomic, strong) NSString *localizationKey; + +@end + +#define LocalizableCustomFontLabel(CLASS_NAME, FONT_NAME) @interface CLASS_NAME : LocalizableCustomFontLabel {} @end +CUSTOM_FONT_LABELS +#undef LocalizableCustomFontLabel diff --git a/Signal/src/views/LocalizableCustomFontLabel.m b/Signal/src/views/LocalizableCustomFontLabel.m new file mode 100644 index 000000000..616689c1d --- /dev/null +++ b/Signal/src/views/LocalizableCustomFontLabel.m @@ -0,0 +1,34 @@ +#import "LocalizableCustomFontLabel.h" + +@implementation LocalizableCustomFontLabel + +- (void)awakeFromNib { + [super awakeFromNib]; + + NSString *fontName = [self fontName]; + self.font = [UIFont fontWithName:fontName size:self.font.pointSize]; +} + +- (void)setLocalizationKey:(NSString *)localizationKey { + _localizationKey = localizationKey; + if (_localizationKey) { + self.text = NSLocalizedString(_localizationKey, @""); + } +} + +- (NSString *)fontName { + return nil; +} + +@end + +#define LocalizableCustomFontLabel(CLASS_NAME, FONT_NAME) \ +@implementation CLASS_NAME \ +- (NSString *)fontName { \ +return @"" # FONT_NAME; \ +} \ +@end \ + +CUSTOM_FONT_LABELS + +#undef LocalizableCustomFontLabel diff --git a/Signal/src/views/PreferenceListTableViewCell.h b/Signal/src/views/PreferenceListTableViewCell.h new file mode 100644 index 000000000..811430591 --- /dev/null +++ b/Signal/src/views/PreferenceListTableViewCell.h @@ -0,0 +1,13 @@ +#import + +/** + * + * PreferenceListTableViewCell displays a preference. + * + */ + +@interface PreferenceListTableViewCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *preferenceTextLabel; + +@end diff --git a/Signal/src/views/PreferenceListTableViewCell.m b/Signal/src/views/PreferenceListTableViewCell.m new file mode 100644 index 000000000..292c9df33 --- /dev/null +++ b/Signal/src/views/PreferenceListTableViewCell.m @@ -0,0 +1,13 @@ +#import "PreferenceListTableViewCell.h" + +@implementation PreferenceListTableViewCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject]; +} + +- (NSString *)reuseIdentifier { + return NSStringFromClass([self class]); +} + +@end diff --git a/Signal/src/views/PreferenceListTableViewCell.xib b/Signal/src/views/PreferenceListTableViewCell.xib new file mode 100644 index 000000000..6afc60d1b --- /dev/null +++ b/Signal/src/views/PreferenceListTableViewCell.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/SearchBarTitleView.h b/Signal/src/views/SearchBarTitleView.h new file mode 100644 index 000000000..c7120bd98 --- /dev/null +++ b/Signal/src/views/SearchBarTitleView.h @@ -0,0 +1,27 @@ +#import + +#define SEARCH_BAR_DEFAULT_EMPTY_STRING @"" + +@class SearchBarTitleView; + +@protocol SearchBarTitleViewDelegate + +- (void)searchBarTitleView:(SearchBarTitleView *)view didSearchForTerm:(NSString *)term; +- (void)searchBarTitleViewDidEndSearching:(SearchBarTitleView *)view; +- (void)searchBarTitleViewDidTapMenu:(SearchBarTitleView *)view; + +@end + +@interface SearchBarTitleView : UIView + +@property (nonatomic, strong) IBOutlet UILabel *titleLabel; +@property (nonatomic, strong) IBOutlet UIView *searchBarContainer; +@property (nonatomic, strong) IBOutlet UITextField *searchTextField; +@property (nonatomic, strong) IBOutlet UIButton *searchButton; +@property (nonatomic, strong) IBOutlet UIButton *cancelButton; +@property (nonatomic, strong) IBOutlet UIButton *menuButton; +@property (nonatomic, assign) IBOutlet id delegate; + +- (void)updateAutoCorrectionType; + +@end diff --git a/Signal/src/views/SearchBarTitleView.m b/Signal/src/views/SearchBarTitleView.m new file mode 100644 index 000000000..2edac0530 --- /dev/null +++ b/Signal/src/views/SearchBarTitleView.m @@ -0,0 +1,97 @@ +#import "SearchBarTitleView.h" +#import "Environment.h" +#import "PreferencesUtil.h" +#import "LocalizableText.h" + +#import + +#define SEARCH_BAR_ANIMATION_DURATION 0.25 + +@implementation SearchBarTitleView + +- (void)awakeFromNib { + [self localizeAndStyle]; + [self setupEvents]; +} + +- (void)localizeAndStyle { + NSDictionary *labelAttributes = @{NSForegroundColorAttributeName:[UIColor grayColor]}; + + _searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:TXT_SEARCH_PLACEHOLDER_TEXT + attributes:labelAttributes]; + _searchTextField.tintColor = [UIColor grayColor]; +} + +- (void)setupEvents { + _searchTextField.delegate = self; + + [_searchButton addTarget:self + action:@selector(searchButtonTapped) + forControlEvents:UIControlEventTouchUpInside]; + + [_cancelButton addTarget:self + action:@selector(cancelButtonTapped) + forControlEvents:UIControlEventTouchUpInside]; + + [_menuButton addTarget:self + action:@selector(menuButtonTapped) + forControlEvents:UIControlEventTouchUpInside]; +} + +#pragma mark - Actions + +- (void)searchButtonTapped { + [UIView animateWithDuration:SEARCH_BAR_ANIMATION_DURATION animations:^{ + [_searchBarContainer setFrame:CGRectMake(0, + CGRectGetMinY(_searchBarContainer.frame), + CGRectGetWidth(_searchBarContainer.frame), + CGRectGetHeight(_searchBarContainer.frame))]; + } completion:^(BOOL finished) { + [_searchTextField becomeFirstResponder]; + }]; +} + +- (void)cancelButtonTapped { + [_delegate searchBarTitleViewDidEndSearching:self]; + [UIView animateWithDuration:SEARCH_BAR_ANIMATION_DURATION animations:^{ + [_searchBarContainer setFrame:CGRectMake(CGRectGetWidth(self.frame) - CGRectGetWidth(_searchButton.frame), + CGRectGetMinY(_searchBarContainer.frame), + CGRectGetWidth(_searchBarContainer.frame), + CGRectGetHeight(_searchBarContainer.frame))]; + } completion:^(BOOL finished) { + _searchTextField.text = SEARCH_BAR_DEFAULT_EMPTY_STRING; + [_searchTextField resignFirstResponder]; + }]; +} + +- (void)updateAutoCorrectionType { + BOOL autoCorrectEnabled = [[[Environment getCurrent] preferences] getAutocorrectEnabled]; + _searchTextField.autocorrectionType = autoCorrectEnabled ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo; +} + +- (void)menuButtonTapped { + [_delegate searchBarTitleViewDidTapMenu:self]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidEndEditing:(UITextField *)textField { + [textField resignFirstResponder]; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + + BOOL searchTapped = [string rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location != NSNotFound; + + if(searchTapped) { + [textField resignFirstResponder]; + return NO; + } + + NSString *searchString = [textField.text stringByReplacingCharactersInRange:range withString:string]; + [_delegate searchBarTitleView:self didSearchForTerm:searchString]; + + return YES; +} + +@end diff --git a/Signal/src/views/SettingsTableHeaderView.h b/Signal/src/views/SettingsTableHeaderView.h new file mode 100644 index 000000000..0957748e9 --- /dev/null +++ b/Signal/src/views/SettingsTableHeaderView.h @@ -0,0 +1,15 @@ +#import + +/** + * + * The header view of the settings table view sections which handles rotating the image that indicates collapsed/expanded state + * + */ + +@interface SettingsTableHeaderView : UIView + +@property (nonatomic, strong) IBOutlet UIImageView *columnStateImageView; + +- (void)setColumnStateExpanded:(BOOL)isExpanded andIsAnimated:(BOOL)animated; + +@end diff --git a/Signal/src/views/SettingsTableHeaderView.m b/Signal/src/views/SettingsTableHeaderView.m new file mode 100644 index 000000000..93c8587a7 --- /dev/null +++ b/Signal/src/views/SettingsTableHeaderView.m @@ -0,0 +1,17 @@ +#import "SettingsTableHeaderView.h" + +#define STATE_TRANSITION_ANIMATION_DURATION 0.25 + +@implementation SettingsTableHeaderView + +- (void)setColumnStateExpanded:(BOOL)isExpanded andIsAnimated:(BOOL)animated { + [UIView animateWithDuration:animated ? STATE_TRANSITION_ANIMATION_DURATION : 0.0 animations:^{ + if (isExpanded) { + _columnStateImageView.transform = CGAffineTransformMakeRotation(0 * M_PI/180); + } else { + _columnStateImageView.transform = CGAffineTransformMakeRotation(270 * M_PI/180); + } + }]; +} + +@end diff --git a/Signal/src/views/UnseenWhisperUserCell.h b/Signal/src/views/UnseenWhisperUserCell.h new file mode 100644 index 000000000..9943e873c --- /dev/null +++ b/Signal/src/views/UnseenWhisperUserCell.h @@ -0,0 +1,11 @@ +#import +#import "Contact.h" + +@interface UnseenWhisperUserCell : UITableViewCell + +@property (nonatomic, strong) IBOutlet UILabel *nameLabel; +@property (nonatomic, strong) IBOutlet UILabel *numberLabel; + +- (void)configureWithContact:(Contact *)contact; + +@end diff --git a/Signal/src/views/UnseenWhisperUserCell.m b/Signal/src/views/UnseenWhisperUserCell.m new file mode 100644 index 000000000..a100a068b --- /dev/null +++ b/Signal/src/views/UnseenWhisperUserCell.m @@ -0,0 +1,31 @@ +#import "Environment.h" +#import "PhoneNumberDirectoryFilter.h" +#import "PhoneNumberDirectoryFilterManager.h" +#import "UnseenWhisperUserCell.h" + +@implementation UnseenWhisperUserCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject]; + return self; +} + +- (NSString *)restorationIdentifier { + return NSStringFromClass([self class]); +} + +- (void)configureWithContact:(Contact *)contact { + _nameLabel.text = [contact fullName]; + + PhoneNumberDirectoryFilter *filter = [[[Environment getCurrent] phoneDirectoryManager] getCurrentFilter]; + BOOL foundPhoneNumber = NO; + + for (PhoneNumber *number in contact.parsedPhoneNumbers) { + if ([filter containsPhoneNumber:number]) { + foundPhoneNumber = YES; + _numberLabel.text = [number localizedDescriptionForUser]; + } + } +} + +@end diff --git a/Signal/src/views/xibs/CallLogTableViewCell.xib b/Signal/src/views/xibs/CallLogTableViewCell.xib new file mode 100644 index 000000000..23df94314 --- /dev/null +++ b/Signal/src/views/xibs/CallLogTableViewCell.xib @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/ContactTableViewCell.xib b/Signal/src/views/xibs/ContactTableViewCell.xib new file mode 100644 index 000000000..0ebe08bb0 --- /dev/null +++ b/Signal/src/views/xibs/ContactTableViewCell.xib @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/CountryCodeTableViewCell.xib b/Signal/src/views/xibs/CountryCodeTableViewCell.xib new file mode 100644 index 000000000..61754ac47 --- /dev/null +++ b/Signal/src/views/xibs/CountryCodeTableViewCell.xib @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/FavouriteTableViewCell.xib b/Signal/src/views/xibs/FavouriteTableViewCell.xib new file mode 100644 index 000000000..d04e41ff2 --- /dev/null +++ b/Signal/src/views/xibs/FavouriteTableViewCell.xib @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/InboxFeedTableViewCell.xib b/Signal/src/views/xibs/InboxFeedTableViewCell.xib new file mode 100644 index 000000000..e01a8ca58 --- /dev/null +++ b/Signal/src/views/xibs/InboxFeedTableViewCell.xib @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/LeftSideMenuCell.xib b/Signal/src/views/xibs/LeftSideMenuCell.xib new file mode 100644 index 000000000..04919cdff --- /dev/null +++ b/Signal/src/views/xibs/LeftSideMenuCell.xib @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/src/views/xibs/UnseenWhisperUserCell.xib b/Signal/src/views/xibs/UnseenWhisperUserCell.xib new file mode 100644 index 000000000..17902e962 --- /dev/null +++ b/Signal/src/views/xibs/UnseenWhisperUserCell.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Signal/test/Supporting Files/SignalTests-Info.plist b/Signal/test/Supporting Files/SignalTests-Info.plist new file mode 100644 index 000000000..c7147f2a8 --- /dev/null +++ b/Signal/test/Supporting Files/SignalTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.twistedoakstudios.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Signal/test/TestUtil.h b/Signal/test/TestUtil.h new file mode 100644 index 000000000..bab17b387 --- /dev/null +++ b/Signal/test/TestUtil.h @@ -0,0 +1,22 @@ +#import +#import "DiscardingLog.h" +#import "Release.h" + +NSObject* churnLock(void); +bool _testChurnHelper(int (^condition)(), NSTimeInterval delay); + +#define testPhoneNumber1 [PhoneNumber phoneNumberFromE164:@"+19027777777"] +#define testPhoneNumber2 [PhoneNumber phoneNumberFromE164:@"+19028888888"] + +#define test(expressionExpectedToBeTrue) STAssertTrue(expressionExpectedToBeTrue, @"") +#define testThrows(expressionExpectedToThrow) STAssertThrows(expressionExpectedToThrow, @"") +#define testDoesNotThrow(expressionExpectedToNotThrow) expressionExpectedToNotThrow +#define testEnv [Release unitTestEnvironment:@[]] +#define testEnvWith(options) [Release unitTestEnvironment:(@[options])] +#define testChurnUntil(condition, timeout) test(_testChurnHelper(^int{ return condition; }, timeout)) +#define testChurnAndConditionMustStayTrue(condition, timeout) test(!_testChurnHelper(^int{ return !(condition); }, timeout)) + +NSData* increasingData(NSUInteger n); +NSData* increasingDataFrom(NSUInteger offset, NSUInteger n); +NSData* sineWave(double frequency, double sampleRate, NSUInteger sampleCount); +NSData* generatePseudoRandomData(NSUInteger length); diff --git a/Signal/test/TestUtil.m b/Signal/test/TestUtil.m new file mode 100644 index 000000000..94e4fd4f9 --- /dev/null +++ b/Signal/test/TestUtil.m @@ -0,0 +1,47 @@ +#import "TestUtil.h" + +NSObject* churnLock(void) { + static NSObject* shared = nil; + if (shared == nil) { + shared = [NSObject new]; + } + return shared; +} +bool _testChurnHelper(int (^condition)(), NSTimeInterval delay) { + NSTimeInterval t = [TimeUtil time] + delay; + while ([TimeUtil time] < t) { + @synchronized(churnLock()) { + if (condition()) return true; + } + sleep(1); + } + @synchronized(churnLock()) { + return condition(); + } +} +NSData* increasingData(NSUInteger n) { + return increasingDataFrom(0, n); +} +NSData* increasingDataFrom(NSUInteger offset, NSUInteger n) { + uint8_t v[n]; + for (NSUInteger i = 0; i < n; i++) + v[i] = (uint8_t)((i+offset) & 0xFF); + return [NSData dataWithBytes:v length:n]; +} +NSData* sineWave(double frequency, double sampleRate, NSUInteger sampleCount) { + double tau = 6.283; + + int16_t samples[sampleCount]; + for (NSUInteger i = 0; i < sampleCount; i++) { + samples[i] = (int16_t)(sin(frequency/sampleRate*i*tau)*(1<<15)); + } + + return [NSData dataWithBytes:samples length:sizeof(samples)]; +} +NSData* generatePseudoRandomData(NSUInteger length) { + NSMutableData* r = [NSMutableData dataWithLength:length]; + for (int i = 0; i < 16; i++) { + ((uint8_t*)[r mutableBytes])[i] = (uint8_t)arc4random_uniform(256); + } + return r; +} diff --git a/Signal/test/async/AsyncUtilTest.h b/Signal/test/async/AsyncUtilTest.h new file mode 100644 index 000000000..deafebb00 --- /dev/null +++ b/Signal/test/async/AsyncUtilTest.h @@ -0,0 +1,5 @@ +#import + +@interface AsyncUtilTest : SenTestCase + +@end diff --git a/Signal/test/async/AsyncUtilTest.m b/Signal/test/async/AsyncUtilTest.m new file mode 100644 index 000000000..9054ded33 --- /dev/null +++ b/Signal/test/async/AsyncUtilTest.m @@ -0,0 +1,269 @@ +#import "AsyncUtilTest.h" +#import "TestUtil.h" +#import "AsyncUtil.h" +#import "FutureSource.h" +#import "CancelTokenSource.h" +#import "CancelledToken.h" +#import "ThreadManager.h" + +@implementation AsyncUtilTest + +-(void) testRaceCancellableOperations_Winner { + __block int f = 0; + __block int s = 0; + __block int i = 0; + CancellableOperationStarter (^makeStarter)(Future*) = ^(Future* future) { + return ^(id c) { + [c whenCancelled:^{ + if ([future hasFailed]) f += 1; + if ([future hasSucceeded]) s += 1; + if ([future isIncomplete]) i += 1; + }]; + return future; + }; + }; + + FutureSource* v1 = [FutureSource new]; + FutureSource* v2 = [FutureSource new]; + FutureSource* v3 = [FutureSource new]; + + Future* r = [AsyncUtil raceCancellableOperations:(@[makeStarter(v1),makeStarter(v2),makeStarter(v3)]) + untilCancelled:nil]; + + [v2 trySetFailure:@1]; + test([r isIncomplete]); + test(f == 0 && s == 0 && i == 0); + + [v3 trySetResult:@2]; + test([r hasSucceeded]); + test([[r forceGetResult] isEqual:@2]); + test(f == 1 && s == 0 && i == 1); + + [v1 trySetResult:@3]; + test(f == 1 && s == 0 && i == 1); +} +-(void) testRaceCancellableOperations_Cancel { + __block int i = 0; + CancellableOperationStarter (^makeStarter)(FutureSource*) = ^(FutureSource* future) { + return ^(id c) { + [c whenCancelled:^{ + i += 1; + [future trySetFailure:c]; + }]; + return future; + }; + }; + + Future* r = [AsyncUtil raceCancellableOperations:(@[ + makeStarter([FutureSource new]), + makeStarter([FutureSource new]), + makeStarter([FutureSource new])]) + untilCancelled:[CancelledToken cancelledToken]]; + + test(i == 3); + test([r hasFailed]); + test([(NSArray*)[r forceGetFailure] count] == 3); +} +-(void) testRaceCancellableOperations_Losers { + test([[AsyncUtil raceCancellableOperations:@[] + untilCancelled:nil] hasFailed]); + + CancellableOperationStarter s = ^(id c) { + return [Future failed:@1]; + }; + + Future* r = [AsyncUtil raceCancellableOperations:(@[s,s,s]) + untilCancelled:nil]; + test([r hasFailed]); + test([[r forceGetFailure] isEqual:(@[@1,@1,@1])]); +} + +-(void) testRaceCancellableOperationAgainstTimeout_WinFail { + test([[AsyncUtil raceCancellableOperations:@[] + untilCancelled:nil] hasFailed]); + CancelTokenSource* cts = [CancelTokenSource cancelTokenSource]; + + __block int n = 0; + CancellableOperationStarter s = ^(id c) { + [c whenCancelled:^{ + @synchronized(churnLock()) { + n += 1; + } + }]; + return [Future failed:@1]; + }; + + Future* f = [AsyncUtil raceCancellableOperation:s + againstTimeout:1.0 + untilCancelled:[cts getToken]]; + test([f hasFailed]); + test([[f forceGetFailure] isEqual:@1]); + test(n == 0); +} +-(void) testRaceCancellableOperationAgainstTimeout_Win { + test([[AsyncUtil raceCancellableOperations:@[] + untilCancelled:nil] hasFailed]); + CancelTokenSource* cts = [CancelTokenSource cancelTokenSource]; + + __block int n = 0; + CancellableOperationStarter s = ^(id c) { + [c whenCancelled:^{ + @synchronized(churnLock()) { + n += 1; + } + }]; + return [Future finished:@1]; + }; + + Future* f = [AsyncUtil raceCancellableOperation:s + againstTimeout:1.0 + untilCancelled:[cts getToken]]; + test([f hasSucceeded]); + test([[f forceGetResult] isEqual:@1]); + test(n == 0); +} +-(void) testRaceCancellableOperationAgainstTimeout_Timeout { + test([[AsyncUtil raceCancellableOperations:@[] + untilCancelled:nil] hasFailed]); + CancelTokenSource* cts = [CancelTokenSource cancelTokenSource]; + + __block int n = 0; + CancellableOperationStarter s = ^(id c) { + [c whenCancelled:^{ + @synchronized(churnLock()) { + n += 1; + } + }]; + return [FutureSource new]; + }; + + Future* f = [AsyncUtil raceCancellableOperation:s + againstTimeout:0.1 + untilCancelled:[cts getToken]]; + + test(n == 0); + testChurnUntil([f hasFailed], 1.0); + test(n == 1); + test([[f forceGetFailure] isKindOfClass:[TimeoutFailure class]]); +} +-(void) testRaceCancellableOperationAgainstTimeout_Cancel { + test([[AsyncUtil raceCancellableOperations:@[] + untilCancelled:nil] hasFailed]); + CancelTokenSource* cts = [CancelTokenSource cancelTokenSource]; + + __block int n = 0; + CancellableOperationStarter s = ^(id c) { + [c whenCancelled:^{ + @synchronized(churnLock()) { + n += 1; + } + }]; + return [FutureSource new]; + }; + + Future* f = [AsyncUtil raceCancellableOperation:s + againstTimeout:1.0 + untilCancelled:[cts getToken]]; + + test(n == 0); + [cts cancel]; + test(n == 1); + testChurnUntil([f hasFailed], 1.0); + test([[f forceGetFailure] conformsToProtocol:@protocol(CancelToken)]); +} + +-(void) testAsyncTryPass { + __block NSUInteger repeat = 0; + __block NSUInteger evalCount = 0; + CancellableOperationStarter op = ^(id c) { + repeat += 1; + return [TimeUtil scheduleEvaluate:^id{ evalCount++; return @YES; } + afterDelay:0.5 + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:c]; + }; + Future* f = [AsyncUtil asyncTry:op + upToNTimes:4 + withBaseTimeout:0.5/8 + andRetryFactor:2 + untilCancelled:nil]; + testChurnUntil(![f isIncomplete], 5.0); + + test(repeat == 3 || repeat == 4); + test(evalCount == 1); + test([f hasSucceeded]); + test([[f forceGetResult] isEqual:@YES]); +} +-(void) testAsyncTryFail { + __block NSUInteger repeat = 0; + __block NSUInteger evalCount = 0; + CancellableOperationStarter op = ^(id c) { + repeat += 1; + return [TimeUtil scheduleEvaluate:^id{ evalCount++; return [Future failed:@13]; } + afterDelay:0.1 + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:c]; + }; + Future* f = [AsyncUtil asyncTry:op + upToNTimes:4 + withBaseTimeout:0.5/8 + andRetryFactor:2 + untilCancelled:nil]; + testChurnUntil(![f isIncomplete], 5.0); + + test(repeat >= 1); + test(evalCount >= 1); + test([f hasFailed]); + test([[f forceGetFailure] isEqual:@13]); +} +-(void) testAsyncTryTimeout { + __block NSUInteger repeat = 0; + __block NSUInteger evalCount = 0; + CancellableOperationStarter op = ^(id c) { + repeat += 1; + return [TimeUtil scheduleEvaluate:^id{ evalCount++; return @YES; } + afterDelay:0.5 + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:c]; + }; + Future* f = [AsyncUtil asyncTry:op + upToNTimes:2 + withBaseTimeout:0.5/8 + andRetryFactor:2 + untilCancelled:nil]; + testChurnUntil(![f isIncomplete], 5.0); + + test(repeat == 2); + test(evalCount == 0); + test([f hasFailed]); + test([[f forceGetFailure] isKindOfClass:[TimeoutFailure class]]); +} +-(void) testAsyncTryCancel { + CancelTokenSource* s = [CancelTokenSource cancelTokenSource]; + __block NSUInteger repeat = 0; + __block NSUInteger evalCount = 0; + CancellableOperationStarter op = ^(id c) { + repeat += 1; + [TimeUtil scheduleRun:^{ [s cancel]; } + afterDelay:0.1 + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:nil]; + return [TimeUtil scheduleEvaluate:^id{ evalCount++; return @YES; } + afterDelay:0.5 + onRunLoop:[ThreadManager normalLatencyThreadRunLoop] + unlessCancelled:c]; + }; + Future* f = [AsyncUtil asyncTry:op + upToNTimes:2 + withBaseTimeout:0.5/8 + andRetryFactor:2 + untilCancelled:[s getToken]]; + testChurnUntil(![f isIncomplete], 5.0); + + test(repeat == 2); + test(evalCount == 0); + test([f hasFailed]); + test([[f forceGetFailure] conformsToProtocol:@protocol(CancelToken)]); +} + +@end diff --git a/Signal/test/async/FutureSourceTest.h b/Signal/test/async/FutureSourceTest.h new file mode 100644 index 000000000..6b0fa12e9 --- /dev/null +++ b/Signal/test/async/FutureSourceTest.h @@ -0,0 +1,5 @@ +#import + +@interface FutureSourceTest : SenTestCase + +@end diff --git a/Signal/test/async/FutureSourceTest.m b/Signal/test/async/FutureSourceTest.m new file mode 100644 index 000000000..dcd31c7be --- /dev/null +++ b/Signal/test/async/FutureSourceTest.m @@ -0,0 +1,349 @@ +#import "FutureSourceTest.h" +#import "TestUtil.h" +#import "FutureSource.h" +#import "Util.h" + +@implementation FutureSourceTest + +-(void) testConstructors { + FutureSource* inc = [FutureSource new]; + test([inc isIncomplete]); + test(![inc hasSucceeded]); + test(![inc hasFailed]); + testThrows([inc forceGetResult]); + testThrows([inc forceGetFailure]); + + FutureSource* done = [FutureSource finished:@1]; + test(![done isIncomplete]); + test([done hasSucceeded]); + test(![done hasFailed]); + testDoesNotThrow([done forceGetResult]); + testThrows([done forceGetFailure]); + + Future* done2 = [Future finished:@2]; + test(![done2 isIncomplete]); + test([done2 hasSucceeded]); + test(![done2 hasFailed]); + testDoesNotThrow([done2 forceGetResult]); + testThrows([done2 forceGetFailure]); + + FutureSource* fail3 = [FutureSource failed:@3]; + test(![fail3 isIncomplete]); + test(![fail3 hasSucceeded]); + test([fail3 hasFailed]); + testThrows([fail3 forceGetResult]); + testDoesNotThrow([fail3 forceGetFailure]); + + Future* fail4 = [Future failed:@4]; + test(![fail4 isIncomplete]); + test(![fail4 hasSucceeded]); + test([fail4 hasFailed]); + testThrows([fail4 forceGetResult]); + testDoesNotThrow([fail4 forceGetFailure]); +} +-(void) testAutoUnwrap { + Future* f = [Future finished:[Future finished:[Future failed:@1]]]; + test([f hasFailed]); + test([[f forceGetFailure] isEqual:@1]); + + Future* f2 = [Future finished:[Future finished:[Future finished:@2]]]; + test([f2 hasSucceeded]); + test([[f2 forceGetResult] isEqual:@2]); + + test([[[[Future finished:@1] then:^id(id value) { + return [Future finished:@3]; + }] forceGetResult] isEqual:@3]); + + test([[[[Future failed:@1] catch:^id(id value) { + return [Future finished:@3]; + }] forceGetResult] isEqual:@3]); +} +-(void) testTrySet { + FutureSource* setR = [FutureSource new]; + FutureSource* setF = [FutureSource new]; + FutureSource* setWR = [FutureSource new]; + FutureSource* setWF = [FutureSource new]; + FutureSource* wr = [FutureSource new]; + FutureSource* wf = [FutureSource new]; + + // set result + test([setR trySetResult:@1]); + test([setR hasSucceeded]); + test(![setR trySetResult:@0]); + test(![setR trySetFailure:@0]); + test(![setR trySetResult:wr]); + test(![setR trySetResult:wf]); + test([[setR forceGetResult] isEqual:@1]); + + // set fail + test([setF trySetFailure:@2]); + test([setF hasFailed]); + test(![setF trySetResult:@0]); + test(![setF trySetFailure:@0]); + test(![setF trySetResult:wr]); + test(![setF trySetResult:wf]); + test([[setF forceGetFailure] isEqual:@2]); + + // wire result + test([setWR trySetResult:wr]); + test([setWR isIncomplete]); + test(![setWR trySetResult:@0]); + test(![setWR trySetFailure:@0]); + test(![setWR trySetResult:wf]); + test(![setWR trySetResult:wr]); + + // wire failure + test([setWF trySetResult:wf]); + test([setWF isIncomplete]); + test(![setWF trySetResult:@0]); + test(![setWF trySetFailure:@0]); + test(![setWF trySetResult:wf]); + test(![setWF trySetResult:wr]); + + // set result via wire + test([setWR isIncomplete]); + [wr trySetResult:@3]; + test([setWR hasSucceeded]); + test([[setWR forceGetResult] isEqual:@3]); + + // set failure via wire + test([setWF isIncomplete]); + [wf trySetFailure:@4]; + test([setWF hasFailed]); + test([[setWF forceGetFailure] isEqual:@4]); +} + +-(void) testThenDo_OnSuccess { + FutureSource* f = [FutureSource new]; + __block int ready = 0; + + // before completed, waits to run + [f thenDo:^(NSNumber* result) { + test(ready == 1); + ready = 2; + test([result isEqual:@1]); + }]; + test(ready == 0); + + ready = 1; + [f trySetResult:@1]; + test(ready == 2); + + // after completed, runs inline + [f thenDo:^(NSNumber* result) { + test(ready == 2); + ready = 3; + test([result isEqual:@1]); + }]; + test(ready == 3); +} +-(void) testThenDo_OnFail { + FutureSource* f = [FutureSource new]; + [f thenDo:^(NSNumber* result) { + test(false); + }]; + [f trySetFailure:@1]; + [f thenDo:^(NSNumber* result) { + test(false); + }]; +} + +-(void) testCatchDo_OnFail { + FutureSource* f = [FutureSource new]; + __block int ready = 0; + + // before completed, waits to run + [f catchDo:^(NSNumber* result) { + test(ready == 1); + ready = 2; + test([result isEqual:@1]); + }]; + test(ready == 0); + + ready = 1; + [f trySetFailure:@1]; + test(ready == 2); + + // after completed, runs inline + [f catchDo:^(NSNumber* result) { + test(ready == 2); + ready = 3; + test([result isEqual:@1]); + }]; + test(ready == 3); +} +-(void) testCatchDo_OnSuccess { + FutureSource* f = [FutureSource new]; + [f catchDo:^(NSNumber* result) { + test(false); + }]; + [f trySetResult:@1]; + [f catchDo:^(NSNumber* result) { + test(false); + }]; +} + +-(void) testThenOrCatchDo_OnSuccess { + FutureSource* f = [FutureSource new]; + __block int ready = 0; + + // before completed, waits to run + [f finallyDo:^(Future* completed) { + test(ready == 1); + ready = 2; + test([[completed forceGetResult] isEqual:@1]); + }]; + test(ready == 0); + + ready = 1; + [f trySetResult:@1]; + test(ready == 2); + + // after completed, runs inline + [f finallyDo:^(Future* completed) { + test(ready == 2); + ready = 3; + test([[completed forceGetResult] isEqual:@1]); + }]; + test(ready == 3); +} +-(void) testThenOrCatchDo_OnFail { + FutureSource* f = [FutureSource new]; + __block int ready = 0; + + // before completed, waits to run + [f finallyDo:^(Future* completed) { + test(ready == 1); + ready = 2; + test([[completed forceGetResult] isEqual:@1]); + }]; + test(ready == 0); + + ready = 1; + [f trySetResult:@1]; + test(ready == 2); + + // after completed, runs inline + [f finallyDo:^(Future* completed) { + test(ready == 2); + ready = 3; + test([[completed forceGetResult] isEqual:@1]); + }]; + test(ready == 3); +} + +-(void) testThen { + // pre-completed + bool b = [[[[Future finished:@3] then:^id(id value) { + test([value isEqual:@3]); + return @4; + }] forceGetResult] isEqual:@4]; + test(b); + + // pre-failed + bool b2 = [[[[Future failed:@-1] then:^id(id value) { + test(false); + return nil; + }] forceGetFailure] isEqual:@-1]; + test(b2); + + // post-completed + FutureSource* f = [FutureSource new]; + Future* f2 = [f then:^id(id value) { + test([value isEqual:@1]); + return @2; + }]; + test([f2 isIncomplete]); + [f trySetResult:@1]; + test([[f2 forceGetResult] isEqual:@2]); + + // exceptional + bool b3 = [[[[Future finished:nil] finally:^id(Future* completed) { + checkOperation(false); + return nil; + }] forceGetFailure] isKindOfClass:[OperationFailed class]]; + test(b3); +} + +-(void) testCatch { + // pre-failed + bool b = [[[[Future failed:@3] catch:^id(id value) { + test([value isEqual:@3]); + return @4; + }] forceGetResult] isEqual:@4]; + test(b); + + // pre-completed + bool b2 = [[[[Future finished:@-1] catch:^id(id value) { + test(false); + return nil; + }] forceGetResult] isEqual:@-1]; + test(b2); + + // post-failed + FutureSource* f = [FutureSource new]; + Future* f2 = [f catch:^id(id value) { + test([value isEqual:@1]); + return @2; + }]; + test([f2 isIncomplete]); + [f trySetFailure:@1]; + test([[f2 forceGetResult] isEqual:@2]); + + // exceptional + bool b3 = [[[[Future failed:nil] finally:^id(Future* completed) { + checkOperation(false); + return nil; + }] forceGetFailure] isKindOfClass:[OperationFailed class]]; + test(b3); +} + +-(void) testThenOrCatch { + // pre-completed + bool b = [[[[Future finished:@3] finally:^id(Future* completed) { + test([[completed forceGetResult] isEqual:@3]); + return @4; + }] forceGetResult] isEqual:@4]; + test(b); + + // pre-failed + bool b2 = [[[[Future failed:@-1] finally:^id(Future* completed) { + test([[completed forceGetFailure] isEqual:@-1]); + return @5; + }] forceGetResult] isEqual:@5]; + test(b2); + + // post-completed + FutureSource* f = [FutureSource new]; + Future* f2 = [f finally:^id(Future* completed) { + test([[completed forceGetResult] isEqual:@1]); + return @2; + }]; + test([f2 isIncomplete]); + [f trySetResult:@1]; + test([[f2 forceGetResult] isEqual:@2]); + + // exceptional + bool b3 = [[[[Future finished:nil] finally:^id(Future* completed) { + checkOperation(false); + return nil; + }] forceGetFailure] isKindOfClass:[OperationFailed class]]; + test(b3); +} + +-(void) completedAsCancelToken_OnSuccess { + FutureSource* f = [FutureSource new]; + id c = [f completionAsCancelToken]; + test(![c isAlreadyCancelled]); + [f trySetResult:nil]; + test([c isAlreadyCancelled]); +} +-(void) completedAsCancelToken_OnFailure { + FutureSource* f = [FutureSource new]; + id c = [f completionAsCancelToken]; + test(![c isAlreadyCancelled]); + [f trySetFailure:nil]; + test([c isAlreadyCancelled]); +} + +@end diff --git a/Signal/test/async/ObservableTest.h b/Signal/test/async/ObservableTest.h new file mode 100644 index 000000000..241cb9b04 --- /dev/null +++ b/Signal/test/async/ObservableTest.h @@ -0,0 +1,5 @@ +#import + +@interface ObservableTest : SenTestCase + +@end diff --git a/Signal/test/async/ObservableTest.m b/Signal/test/async/ObservableTest.m new file mode 100644 index 000000000..0e334a8a9 --- /dev/null +++ b/Signal/test/async/ObservableTest.m @@ -0,0 +1,132 @@ +#import "ObservableTest.h" +#import "ObservableValue.h" +#import "TestUtil.h" +#import "CancelTokenSource.h" + +@implementation ObservableTest + +-(void) testObservableAddRemove { + ObservableValueController* s = [ObservableValueController observableValueControllerWithInitialValue:@""]; + ObservableValue* t = s; + NSMutableArray* a = [NSMutableArray array]; + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + + [t watchLatestValueOnArbitraryThread:^(id value) {[a addObject:value];} + untilCancelled:[c getToken]]; + + test([a isEqualToArray:@[@""]]); + [s updateValue:@5]; + test([a isEqualToArray:(@[@"", @5])]); + [s updateValue:@7]; + test([a isEqualToArray:(@[@"", @5, @7])]); + [c cancel]; + [s updateValue:@11]; + test([a isEqualToArray:(@[@"", @5, @7])]); +} +-(void) testObservableAddAdd { + ObservableValueController* s = [ObservableValueController observableValueControllerWithInitialValue:@""]; + ObservableValue* t = s; + NSMutableArray* a = [NSMutableArray array]; + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + + [t watchLatestValueOnArbitraryThread:^(id value) {[a addObject:value];} + untilCancelled:[c getToken]]; + [t watchLatestValueOnArbitraryThread:^(id value) {[a addObject:value];} + untilCancelled:[c getToken]]; + [t watchLatestValueOnArbitraryThread:^(id value) {[a addObject:value];} + untilCancelled:[c getToken]]; + + test([a isEqualToArray:(@[@"", @"", @""])]); + [s updateValue:@5]; + test([a isEqualToArray:(@[@"", @"", @"", @5, @5, @5])]); +} +-(void) testObservableRedundantSetIgnored { + id v1 = @""; + id v2 = nil; + id v3 = @1; + + ObservableValueController* s = [ObservableValueController observableValueControllerWithInitialValue:v1]; + ObservableValue* t = s; + __block id latest = nil; + __block int count = 0; + [t watchLatestValueOnArbitraryThread:^(id value) {latest = value;count++;} + untilCancelled:nil]; + + test(latest == v1); + test(count == 1); + + [s updateValue:v1]; + test(latest == v1); + test(count == 1); + + [s updateValue:v2]; + test(latest == v2); + test(count == 2); + + [s updateValue:v2]; + test(latest == v2); + test(count == 2); + + [s updateValue:v1]; + test(latest == v1); + test(count == 3); + + [s updateValue:v3]; + test(latest == v3); + test(count == 4); +} +-(void) testObservableReentrantAdd { + ObservableValueController* s = [ObservableValueController observableValueControllerWithInitialValue:@""]; + ObservableValue* t = s; + NSMutableArray* a = [NSMutableArray array]; + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + + __block void(^registerSelf)() = nil; + void(^registerSelf_builder)() = ^{ + __block bool first = true; + [t watchLatestValueOnArbitraryThread:^(id value) { + if (!first) registerSelf(); + first = false; + [a addObject:value]; + } untilCancelled:[c getToken]]; + }; + registerSelf = [registerSelf_builder copy]; + registerSelf(); + + // adding during a callback counts as adding after the callback + // so we should see a doubling each time + test([a isEqualToArray:@[@""]]); + [s updateValue:@1]; + test([a isEqualToArray:(@[@"", @1, @1])]); + [s updateValue:@2]; + test([a isEqualToArray:(@[@"", @1, @1, @2, @2, @2, @2])]); + [s updateValue:@3]; + test([a isEqualToArray:(@[@"", @1, @1, @2, @2, @2, @2, @3, @3, @3, @3, @3, @3, @3, @3])]); +} +-(void) testObservableReentrantRemove { + ObservableValueController* s = [ObservableValueController observableValueControllerWithInitialValue:@""]; + ObservableValue* t = s; + NSMutableArray* a = [NSMutableArray array]; + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + + for (int i = 0; i < 3; i++) { + __block bool first = true; + [t watchLatestValueOnArbitraryThread:^(id value) { + if (!first) { + [c cancel]; + [a addObject:value]; + } + first = false; + } untilCancelled:[c getToken]]; + } + + // removing during a callback counts as removing after the callback + // so we should see all the callbacks run, then they're all cancelled + test([a isEqualToArray:(@[])]); + [s updateValue:@1]; + test([a isEqualToArray:(@[@1, @1, @1])]); + [s updateValue:@2]; + test([a isEqualToArray:(@[@1, @1, @1])]); +} + +@end diff --git a/Signal/test/audio/AudioFrameTest.h b/Signal/test/audio/AudioFrameTest.h new file mode 100644 index 000000000..93c9f6874 --- /dev/null +++ b/Signal/test/audio/AudioFrameTest.h @@ -0,0 +1,5 @@ +#import + +@interface AudioFrameTest : SenTestCase + +@end diff --git a/Signal/test/audio/AudioFrameTest.m b/Signal/test/audio/AudioFrameTest.m new file mode 100644 index 000000000..f3503f357 --- /dev/null +++ b/Signal/test/audio/AudioFrameTest.m @@ -0,0 +1,14 @@ +#import "AudioFrameTest.h" +#import "EncodedAudioPacket.h" +#import "TestUtil.h" + +@implementation AudioFrameTest +-(void) testTrivial { + NSData* d2 = [NSMutableData dataWithLength:6]; + + testThrows([EncodedAudioPacket encodedAudioPacketWithAudioData:nil andSequenceNumber:0]); + EncodedAudioPacket* p2 = [EncodedAudioPacket encodedAudioPacketWithAudioData:d2 andSequenceNumber:0xFF00]; + test([p2 audioData] == d2); + test([p2 sequenceNumber] == 0xFF00); +} +@end diff --git a/Signal/test/audio/AudioRemoteIOTest.h b/Signal/test/audio/AudioRemoteIOTest.h new file mode 100644 index 000000000..761893a79 --- /dev/null +++ b/Signal/test/audio/AudioRemoteIOTest.h @@ -0,0 +1,5 @@ +#import + +@interface AudioRemoteIOTest : SenTestCase + +@end diff --git a/Signal/test/audio/AudioRemoteIOTest.m b/Signal/test/audio/AudioRemoteIOTest.m new file mode 100644 index 000000000..71db7a75a --- /dev/null +++ b/Signal/test/audio/AudioRemoteIOTest.m @@ -0,0 +1,53 @@ +#import "AudioRemoteIOTest.h" +#import "RemoteIOAudio.h" +#import "AnonymousAudioCallbackHandler.h" +#import "TestUtil.h" +#import "CancelTokenSource.h" + +@implementation AudioRemoteIOTest + +-(void) ______REMOVE_THIS_PREFIX_TO_ENABLE_ACTUAL_AUDIO_TEST______testPlaysAndRecordsAudio { + __block RemoteIOAudio* a = nil; + + __block double t = 0; + id generateWhooOOOoooOOOOooOOOOoooSineWave = ^(NSUInteger requested, NSUInteger bytesRemaining) { + if (bytesRemaining < requested*10) { + int16_t wave[requested]; + for (NSUInteger i = 0; i < requested; i++) { + wave[i] = (int16_t)(sin(t)*INT16_MAX); + double curFrequency = (sin(t/400)+1)/2*500+200; + @synchronized(a) { + t += 2*3.14159*curFrequency/[a getSampleRateInHertz]; + } + } + [a populatePlaybackQueueWithData:[NSData dataWithBytesNoCopy:wave length:sizeof(wave) freeWhenDone:NO]]; + } + }; + + __block int recordCount = 0; + id countCalls = ^(CyclicalBuffer *data) { + @synchronized(a) { + recordCount += 1; + } + }; + + CancelTokenSource* life = [CancelTokenSource cancelTokenSource]; + a = [RemoteIOAudio remoteIOInterfaceStartedWithDelegate:[AnonymousAudioCallbackHandler anonymousAudioInterfaceDelegateWithRecordingCallback:countCalls + andPlaybackOccurredCallback:generateWhooOOOoooOOOOooOOOOoooSineWave] + untilCancelled:[life getToken]]; + + // churn the run loop, to allow the audio to play and be recorded + // YOU SHOULD HEAR A WOOOoooOOOOoooOOO TONE WHILE THIS IS HAPPENING (with the frequency going up and down) + testChurnAndConditionMustStayTrue(true, 10); + + @synchronized(a) { + // recorded something + test(recordCount > 0); + // played something + test(t > 0); + } + + [life cancel]; +} + +@end diff --git a/Signal/test/audio/AudioStretcherTest.h b/Signal/test/audio/AudioStretcherTest.h new file mode 100644 index 000000000..b6ae29274 --- /dev/null +++ b/Signal/test/audio/AudioStretcherTest.h @@ -0,0 +1,5 @@ +#import + +@interface AudioStretcherTest : SenTestCase + +@end diff --git a/Signal/test/audio/AudioStretcherTest.m b/Signal/test/audio/AudioStretcherTest.m new file mode 100644 index 000000000..50cf84489 --- /dev/null +++ b/Signal/test/audio/AudioStretcherTest.m @@ -0,0 +1,25 @@ +#import "AudioStretcherTest.h" +#import "TestUtil.h" +#import "AudioStretcher.h" + +@implementation AudioStretcherTest +-(void) testStretchAudioStretches { + for (NSNumber* s in @[@0.5, @1.0, @1.5]) { + NSUInteger inputSampleCount = 8000; + double stretch = [s doubleValue]; + double freq = 300; + + AudioStretcher* a = [AudioStretcher audioStretcher]; + + NSData* inputData = sineWave(freq, 8000, 8000); + NSData* outputData = [a stretchAudioData:inputData stretchFactor:stretch]; + NSUInteger outputSampleCount = [outputData length]/sizeof(int16_t); + if ([s doubleValue] == 1) { + test([inputData isEqualToData:outputData]); + } + + double ratio = outputSampleCount / (double)inputSampleCount / stretch; + test(ratio > 0.95 && ratio < 1.05); + } +} +@end diff --git a/Signal/test/audio/JitterQueueTest.h b/Signal/test/audio/JitterQueueTest.h new file mode 100644 index 000000000..53a1fea54 --- /dev/null +++ b/Signal/test/audio/JitterQueueTest.h @@ -0,0 +1,5 @@ +#import + +@interface JitterQueueTest : SenTestCase + +@end diff --git a/Signal/test/audio/JitterQueueTest.m b/Signal/test/audio/JitterQueueTest.m new file mode 100644 index 000000000..be5780c44 --- /dev/null +++ b/Signal/test/audio/JitterQueueTest.m @@ -0,0 +1,197 @@ +#import "JitterQueueTest.h" +#import "JitterQueue.h" +#import "TestUtil.h" +#import "EncodedAudioPacket.h" +#import "Util.h" +#import "DiscardingLog.h" +#import "Queue.h" + +#define testLoggedNothing(q) test([q->messageQueue count] == 0) +#define testLogged(q, s) test([q->messageQueue count] > 0 && [s isEqualToString:[q->messageQueue dequeue]]) +#define testLoggedArrival(q, n) testLogged(q, ([NSString stringWithFormat:@"%d", n])) +#define testLoggedBadArrival(q, sequenceNumber, arrivalType) testLogged(q, ([NSString stringWithFormat:@"bad +%d: %d", sequenceNumber, arrivalType])) +#define testLoggedBadDequeueOfType(q, type) testLogged(q, ([NSString stringWithFormat:@"-%d", type])) +#define testLoggedDequeue(q, sequenceNumber, remainingCount) testLogged(q, ([NSString stringWithFormat:@"bad -%d: %d", sequenceNumber, remainingCount])) +#define testLoggedDiscard(q, sequenceNumber, oldReadHeadSequenceNumber, newReadHeadSequenceNumber) testLogged(q, ([NSString stringWithFormat:@"discard %d,%d,%d", sequenceNumber, oldReadHeadSequenceNumber, newReadHeadSequenceNumber])) +#define testLoggedResync(q, oldReadHeadSequenceNumber, newReadHeadSequenceNumber) testLogged(q, ([NSString stringWithFormat:@"resync %d to %d", oldReadHeadSequenceNumber,newReadHeadSequenceNumber])) + +@implementation JitterQueueTest + +-(void) testJitterStartsFromAnyIndex { + JitterQueue* r1 = [JitterQueue jitterQueue]; + JitterQueue* r2 = [JitterQueue jitterQueue]; + + EncodedAudioPacket* q1 = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:100]; + test([r1 count] == 0); + test([r1 tryEnqueue:q1]); + test([r1 count] == 1); + test([r1 tryDequeue] == q1); + test([r1 count] == 0); + + EncodedAudioPacket* q2 = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0xFF00]; + test([r2 count] == 0); + test([r2 tryEnqueue:q2]); + test([r2 count] == 1); + test([r2 tryDequeue] == q2); + test([r2 count] == 0); +} +-(void) testJitterAdvances { + JitterQueue* r = [JitterQueue jitterQueue]; + + for (uint16_t i = 0; i < 10; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:i]; + test([r tryEnqueue:q]); + test([r count] == i+1); + } + + for (uint16_t i = 0; i < 10; i++) { + test([[r tryDequeue] sequenceNumber] == i); + test([r count] == 9-i); + } + test([r tryDequeue] == nil); +} +-(void) testJitterAdvancesWithHoles { + JitterQueue* r = [JitterQueue jitterQueue]; + + for (uint16_t i = 0; i < 10; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:i*2+1]; + test([r tryEnqueue:q]); + } + + for (uint16_t i = 0; i < 20; i++) { + EncodedAudioPacket* p = [r tryDequeue]; + test((p == nil) == (i%2==1)); + test(p == nil || [p sequenceNumber] == i+1); + } + test([r tryDequeue] == nil); +} +-(void) testJitterAdvancesIncrementally { + JitterQueue* r = [JitterQueue jitterQueue]; + + for (uint16_t i = 0; i < 20; i++) { + for (uint16_t j = 0; j < 2; j++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:i*2+j]; + test([r tryEnqueue:q]); + } + + test([[r tryDequeue] sequenceNumber] == i); + } + test([[r tryDequeue] sequenceNumber] == 20); +} +-(void) testJitterQueueRejectsDuplicateSequenceNumbers { + JitterQueue* r = [JitterQueue jitterQueue]; + + EncodedAudioPacket* p = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0]; + test([r tryEnqueue:p]); + + for (uint16_t i = 0; i < 10; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0]; + test(![r tryEnqueue:q]); + } + + test([r tryDequeue] == p); + test([r tryDequeue] == nil); +} +-(void) testJitterQueueRejectsOldSequenceNumbers { + JitterQueue* r = [JitterQueue jitterQueue]; + + EncodedAudioPacket* p = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:50]; + test([r tryEnqueue:p]); + + for (uint16_t i = 1; i < 10; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:50 - i]; + test(![r tryEnqueue:q]); + } + + test([r tryDequeue] == p); + test([r tryDequeue] == nil); +} +-(void) testJitterQueueRejectsFarOffSequenceNumbers { + JitterQueue* r = [JitterQueue jitterQueue]; + + EncodedAudioPacket* p = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0]; + test([r tryEnqueue:p]); + + for (uint16_t i = 0; i < 10; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0x7000+i]; + test(![r tryEnqueue:q]); + } + + test([r tryDequeue] == p); + test([r tryDequeue] == nil); +} +-(void) testJitterQueueResyncsSequenceNumber { + JitterQueue* r = [JitterQueue jitterQueue]; + + EncodedAudioPacket* p = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:1]; + test([r tryEnqueue:p]); + test([r tryDequeue] == p); + + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:0]; + test(![r tryEnqueue:q]); + + // cause desync to be detected + for (uint16_t i = 0; i < 5000; i++) { + test([r tryDequeue] == nil); + } + + // resync at q, before p's index + test([r tryEnqueue:q]); + test([r tryDequeue] == q); +} +-(void) testLoopAround { + JitterQueue* r = [JitterQueue jitterQueue]; + + for (int i = 0; i < 1 << 17; i++) { + EncodedAudioPacket* q = [EncodedAudioPacket encodedAudioPacketWithAudioData:[NSData dataWithLength:1] andSequenceNumber:(uint16_t)(i & 0xFFFF)]; + test([r tryEnqueue:q]); + test([r tryDequeue] == q); + } + test([r tryDequeue] == nil); +} +-(void) testJitterQueueAvoidsRacingAhead { + JitterQueue* r = [JitterQueue jitterQueue]; + EncodedAudioPacket* p1 = [EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(1) andSequenceNumber:0]; + EncodedAudioPacket* p2 = [EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(1) andSequenceNumber:1]; + + test([r tryEnqueue:p1]); + test([r tryDequeue] == p1); + + test([r tryDequeue] == nil); + + test([r tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(1) andSequenceNumber:10]]); + test([r tryDequeue] == nil); + + test([r tryEnqueue:p2]); + test([r tryDequeue] == p2); + + test([r tryDequeue] == nil); +} + +-(void) testJitterQueueMeasurement { + JitterQueue* q = [JitterQueue jitterQueue]; + [q tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(20) andSequenceNumber:1]]; + test([q currentBufferDepth] == 0); + [q tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(20) andSequenceNumber:2]]; + test([q currentBufferDepth] == 1); + [q tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(20) andSequenceNumber:4]]; + test([q currentBufferDepth] == 3); + [q tryDequeue]; + test([q currentBufferDepth] == 2); + [q tryDequeue]; + test([q currentBufferDepth] == 1); + [q tryDequeue]; + test([q currentBufferDepth] == 0); + [q tryDequeue]; + test([q currentBufferDepth] == -1); + [q tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(20) andSequenceNumber:8]]; + test([q currentBufferDepth] == 3); + + // resyncs to 0 + for (int i = 0; i < 500; i++) { + [q tryDequeue]; + } + [q tryEnqueue:[EncodedAudioPacket encodedAudioPacketWithAudioData:increasingData(20) andSequenceNumber:9000]]; + test([q currentBufferDepth] == 0); +} +@end diff --git a/Signal/test/audio/SpeexCodecTest.h b/Signal/test/audio/SpeexCodecTest.h new file mode 100644 index 000000000..b865bd046 --- /dev/null +++ b/Signal/test/audio/SpeexCodecTest.h @@ -0,0 +1,5 @@ +#import + +@interface SpeexCodecTest : SenTestCase + +@end diff --git a/Signal/test/audio/SpeexCodecTest.m b/Signal/test/audio/SpeexCodecTest.m new file mode 100644 index 000000000..915828c06 --- /dev/null +++ b/Signal/test/audio/SpeexCodecTest.m @@ -0,0 +1,35 @@ +#import "SpeexCodecTest.h" +#import "SpeexCodec.h" +#import "TestUtil.h" +#import "Util.h" + +@implementation SpeexCodecTest +-(void) testSpeexConstantBitRate { + NSMutableData* x1 = [NSMutableData dataWithLength:320]; + NSMutableData* x2 = [NSMutableData dataWithLength:320]; + for (NSUInteger i = 0; i < [x2 length]; i++) { + [x2 setUint8At:i to:(uint8_t)(i & 255)]; + } + + SpeexCodec* c = [SpeexCodec speexCodec]; + NSData* e100 = [c encode:x1]; + NSData* e200 = [c encode:x2]; + test([e200 length] == [e100 length]); +} + +-(void) testSpeexRoundTripMaintainsLength { + NSMutableData* x1 = [NSMutableData dataWithLength:320]; + NSMutableData* x2 = [NSMutableData dataWithLength:320]; + for (NSUInteger i = 0; i < [x2 length]; i++) { + [x2 setUint8At:i to:(uint8_t)(i & 255)]; + } + + SpeexCodec* c = [SpeexCodec speexCodec]; + NSData* e100 = [c encode:x1]; + NSData* e200 = [c encode:x2]; + NSData* d100 = [c decode:e100]; + NSData* d200 = [c decode:e200]; + test([d100 length] == [x1 length]); + test([d200 length] == [x2 length]); +} +@end diff --git a/Signal/test/contact/ContactManagerTest.m b/Signal/test/contact/ContactManagerTest.m new file mode 100644 index 000000000..bdfe03d9a --- /dev/null +++ b/Signal/test/contact/ContactManagerTest.m @@ -0,0 +1,29 @@ +#import +#import "TestUtil.h" +#import "ContactsManager.h" + +@interface ContactManagerTest : SenTestCase + +@end + +@implementation ContactManagerTest + +- (void)testQueryMatching { + test([ContactsManager name:@"big dave" matchesQuery:@"big dave"]); + test([ContactsManager name:@"big dave" matchesQuery:@"dave big"]); + test([ContactsManager name:@"big dave" matchesQuery:@"dave"]); + test([ContactsManager name:@"big dave" matchesQuery:@"big"]); + test([ContactsManager name:@"big dave" matchesQuery:@"big "]); + test([ContactsManager name:@"big dave" matchesQuery:@" big "]); + test([ContactsManager name:@"big dave" matchesQuery:@"dav"]); + test([ContactsManager name:@"big dave" matchesQuery:@"bi dav"]); + test([ContactsManager name:@"big dave" matchesQuery:@"big big big big big big big big big big dave dave dave dave dave"]); + + test(![ContactsManager name:@"big dave" matchesQuery:@"ave"]); + test(![ContactsManager name:@"big dave" matchesQuery:@"dare"]); + test(![ContactsManager name:@"big dave" matchesQuery:@"mike"]); + test(![ContactsManager name:@"big dave" matchesQuery:@"mike"]); + test(![ContactsManager name:@"dave" matchesQuery:@"big"]); +} + +@end diff --git a/Signal/test/fr.lproj/InfoPlist.strings b/Signal/test/fr.lproj/InfoPlist.strings new file mode 100644 index 000000000..477b28ff8 --- /dev/null +++ b/Signal/test/fr.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Signal/test/network/IpAddressTest.h b/Signal/test/network/IpAddressTest.h new file mode 100644 index 000000000..4a1225573 --- /dev/null +++ b/Signal/test/network/IpAddressTest.h @@ -0,0 +1,5 @@ +#import + +@interface IpAddressTest : SenTestCase + +@end diff --git a/Signal/test/network/IpAddressTest.m b/Signal/test/network/IpAddressTest.m new file mode 100644 index 000000000..88db391c0 --- /dev/null +++ b/Signal/test/network/IpAddressTest.m @@ -0,0 +1,101 @@ +#import "IpAddressTest.h" +#import "TestUtil.h" +#import "IpAddress.h" + +@implementation IpAddressTest +-(void) testFromString { + testThrows([IpAddress ipAddressFromString:nil]); + testThrows([IpAddress ipAddressFromString:@""]); + testThrows([IpAddress ipAddressFromString:@"^"]); + testThrows([IpAddress ipAddressFromString:@"127.6.5"]); + testThrows([IpAddress ipAddressFromString:@"127.6.5.8:80"]); + testThrows([IpAddress ipAddressFromString:@"2:5"]); + testThrows([IpAddress ipAddressFromString:@"256.256.256.256"]); + testThrows([IpAddress ipAddressFromString:@"0db8:85a3:0000:0000:8a2e:0370:7334"]); + testThrows([IpAddress ipAddressFromString:@"AAAA:2001:0db8:85a3:0000:0000:8a2e:0370:7334"]); + + [IpAddress ipAddressFromString:@"127.0.0.1"]; + [IpAddress ipAddressFromString:@"255.255.255.255"]; + [IpAddress ipAddressFromString:@"0.0.0.0"]; + + [IpAddress ipAddressFromString:@"ab01::"]; + [IpAddress ipAddressFromString:@"AB01::"]; + [IpAddress ipAddressFromString:@"::AB01"]; + [IpAddress ipAddressFromString:@"AB01::1001"]; + [IpAddress ipAddressFromString:@"2001:0db8:85a3:0000:0000:8a2e:0370:7334"]; +} +-(void) testFromIpv4String { + testThrows([IpAddress ipv4AddressFromString:nil]); + testThrows([IpAddress ipv4AddressFromString:@""]); + testThrows([IpAddress ipv4AddressFromString:@"^"]); + testThrows([IpAddress ipv4AddressFromString:@"127.6.5"]); + testThrows([IpAddress ipv4AddressFromString:@"127.6.5.8:80"]); + testThrows([IpAddress ipv4AddressFromString:@"2:5"]); + testThrows([IpAddress ipv4AddressFromString:@"256.256.256.256"]); + testThrows([IpAddress ipv4AddressFromString:@"0db8:85a3:0000:0000:8a2e:0370:7334"]); + testThrows([IpAddress ipv4AddressFromString:@"AAAA:2001:0db8:85a3:0000:0000:8a2e:0370:7334"]); + + [IpAddress ipv4AddressFromString:@"127.0.0.1"]; + [IpAddress ipv4AddressFromString:@"255.255.255.255"]; + [IpAddress ipv4AddressFromString:@"0.0.0.0"]; + + testThrows([IpAddress ipv4AddressFromString:@"AB01::"]); + testThrows([IpAddress ipv4AddressFromString:@"::AB01"]); + testThrows([IpAddress ipv4AddressFromString:@"AB01::1001"]); + testThrows([IpAddress ipv4AddressFromString:@"2001:0db8:85a3:0000:0000:8a2e:0370:7334"]); +} +-(void) testFromIpv6String { + testThrows([IpAddress ipv6AddressFromString:nil]); + testThrows([IpAddress ipv6AddressFromString:@""]); + testThrows([IpAddress ipv6AddressFromString:@"^"]); + testThrows([IpAddress ipv6AddressFromString:@"127.6.5"]); + testThrows([IpAddress ipv6AddressFromString:@"127.6.5.8:80"]); + testThrows([IpAddress ipv6AddressFromString:@"2:5"]); + testThrows([IpAddress ipv6AddressFromString:@"256.256.256.256"]); + testThrows([IpAddress ipv6AddressFromString:@"0db8:85a3:0000:0000:8a2e:0370:7336"]); + testThrows([IpAddress ipv6AddressFromString:@"AAAA:2001:0db8:85a3:0000:0000:8a2e:0370:7336"]); + + testThrows([IpAddress ipv6AddressFromString:@"127.0.0.1"]); + testThrows([IpAddress ipv6AddressFromString:@"255.255.255.255"]); + testThrows([IpAddress ipv6AddressFromString:@"0.0.0.0"]); + + [IpAddress ipv6AddressFromString:@"AB01::"]; + [IpAddress ipv6AddressFromString:@"ab01::"]; + [IpAddress ipv6AddressFromString:@"::AB01"]; + [IpAddress ipv6AddressFromString:@"AB01::1001"]; + [IpAddress ipv6AddressFromString:@"2001:0db8:85a3:0000:0000:8a2e:0370:7334"]; +} + +-(void) testDescription { + for (NSString* s in @[@"4.5.6.7", @"abcd:cdef:85a3:1234:2345:8a2e:6789:7334"]) { + test([[[IpAddress ipAddressFromString:s] description] isEqualToString:s]); + } +} +-(void) testSockaddrDataIpv4 { + NSData* d = [[IpAddress ipAddressFromString:@"4.5.6.7"] sockaddrDataWithPort:5]; + struct sockaddr_in s; + test([d length] >= sizeof(struct sockaddr_in)); + memcpy(&s, [d bytes], sizeof(struct sockaddr_in)); + test(s.sin_port == ntohs(5)); + test(s.sin_family == AF_INET); + test(s.sin_addr.s_addr == 0x07060504); +} +-(void) testSockaddrDataIpv6 { + NSData* d = [[IpAddress ipAddressFromString:@"2001:0db8:85a3:0000:0000:8a2e:0370:7334"] sockaddrDataWithPort:5]; + struct sockaddr_in6 s; + test([d length] >= sizeof(struct sockaddr_in6)); + memcpy(&s, [d bytes], sizeof(struct sockaddr_in6)); + test(s.sin6_port == ntohs(5)); + test(s.sin6_family == AF_INET6); + + uint16_t* x = s.sin6_addr.__u6_addr.__u6_addr16; + test(x[0] == ntohs(0x2001)); + test(x[1] == ntohs(0x0db8)); + test(x[2] == ntohs(0x85a3)); + test(x[3] == 0); + test(x[4] == 0); + test(x[5] == ntohs(0x8a2e)); + test(x[6] == ntohs(0x0370)); + test(x[7] == ntohs(0x7334)); +} +@end diff --git a/Signal/test/network/IpEndPointTest.h b/Signal/test/network/IpEndPointTest.h new file mode 100644 index 000000000..0f13701b4 --- /dev/null +++ b/Signal/test/network/IpEndPointTest.h @@ -0,0 +1,5 @@ +#import + +@interface IpEndPointTest : SenTestCase + +@end diff --git a/Signal/test/network/IpEndPointTest.m b/Signal/test/network/IpEndPointTest.m new file mode 100644 index 000000000..0a5692f07 --- /dev/null +++ b/Signal/test/network/IpEndPointTest.m @@ -0,0 +1,21 @@ +#import "IpEndPointTest.h" +#import "TestUtil.h" +#import "IpEndPoint.h" +#import "IpAddress.h" + +@implementation IpEndPointTest +-(void) testTrivial { + IpAddress* a = [IpAddress localhost]; + IpEndPoint* p = [IpEndPoint ipEndPointAtAddress:a onPort:2]; + test([p address] == a); + test([p port] == 2); +} +-(void) testFromSockaddrLoop { + for (NSString* s in @[@"4.5.6.7", @"2001:0db8:85a3:0001:0002:8a2e:0370:7334"]) { + IpAddress* a = [IpAddress ipAddressFromString:s]; + IpEndPoint* p = [IpEndPoint ipEndPointFromSockaddrData:[[IpEndPoint ipEndPointAtAddress:a onPort:6] sockaddrData]]; + test([[[p address] description] isEqualToString:[a description]]); + test([p port] == 6); + } +} +@end diff --git a/Signal/test/network/dns/DnsManagerTest.h b/Signal/test/network/dns/DnsManagerTest.h new file mode 100644 index 000000000..69c1ae25b --- /dev/null +++ b/Signal/test/network/dns/DnsManagerTest.h @@ -0,0 +1,5 @@ +#import + +@interface DnsManagerTest : SenTestCase + +@end diff --git a/Signal/test/network/dns/DnsManagerTest.m b/Signal/test/network/dns/DnsManagerTest.m new file mode 100644 index 000000000..6149bc9ef --- /dev/null +++ b/Signal/test/network/dns/DnsManagerTest.m @@ -0,0 +1,81 @@ +#import "DnsManagerTest.h" +#import "DnsManager.h" +#import "TestUtil.h" +#import "Util.h" +#import "IpAddress.h" +#import "ThreadManager.h" +#import "CancelTokenSource.h" +#import + +#define infrastructureTestHostName @"relay.whispersystems.org" +#define reliableHostName @"example.com" +#define invalidHostname @"∆©˙∆¨¥©©˜¨¥©˜†¥µ¬¬¨˙µ†¥∫®∂®†" +#define nonExistentHostname [NSString stringWithFormat:@"%@kfurmtludehntlgihmvnduyebntiinvbudydepqowudyfnrkt.com", \ + [[CryptoTools generateSecureRandomData:10] encodedAsBase64]] + +@implementation DnsManagerTest + +-(void) testQueryAddresses_Sequential { + Future* f1 = [DnsManager asyncQueryAddressesForDomainName:reliableHostName + unlessCancelled:nil]; + testChurnUntil([f1 hasSucceeded], 5.0); + test([(NSArray*)[f1 forceGetResult] count] > 0); + + Future* f2 = [DnsManager asyncQueryAddressesForDomainName:invalidHostname + unlessCancelled:nil]; + testChurnUntil([f2 hasFailed], 5.0); + + Future* f3 = [DnsManager asyncQueryAddressesForDomainName:nonExistentHostname + unlessCancelled:nil]; + testChurnUntil([f3 hasFailed], 5.0); + + Future* f4 = [DnsManager asyncQueryAddressesForDomainName:infrastructureTestHostName + unlessCancelled:nil]; + testChurnUntil([f4 hasSucceeded], 5.0); + test([f4 hasSucceeded] && [(NSArray*)[f4 forceGetResult] count] > 0); + +} + +-(void) testQueryAddresses_Concurrent { + Future* f1 = [DnsManager asyncQueryAddressesForDomainName:reliableHostName + unlessCancelled:nil]; + Future* f2 = [DnsManager asyncQueryAddressesForDomainName:invalidHostname + unlessCancelled:nil]; + Future* f3 = [DnsManager asyncQueryAddressesForDomainName:nonExistentHostname + unlessCancelled:nil]; + Future* f4 = [DnsManager asyncQueryAddressesForDomainName:infrastructureTestHostName + unlessCancelled:nil]; + + testChurnUntil([f1 hasSucceeded] && [f2 hasFailed] && [f3 hasFailed] && [f4 hasSucceeded], 5.0); + test([f1 hasSucceeded] && [(NSArray*)[f1 forceGetResult] count] > 0); + test([f4 hasSucceeded] && [(NSArray*)[f4 forceGetResult] count] > 0); +} + +-(void) testQueryAddresses_Cancel { + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + Future* f1 = [DnsManager asyncQueryAddressesForDomainName:reliableHostName + unlessCancelled:[c getToken]]; + Future* f2 = [DnsManager asyncQueryAddressesForDomainName:invalidHostname + unlessCancelled:[c getToken]]; + Future* f3 = [DnsManager asyncQueryAddressesForDomainName:nonExistentHostname + unlessCancelled:[c getToken]]; + Future* f4 = [DnsManager asyncQueryAddressesForDomainName:infrastructureTestHostName + unlessCancelled:[c getToken]]; + [c cancel]; + + testChurnUntil(![f1 isIncomplete] && [f2 hasFailed] && [f3 hasFailed] && ![f4 isIncomplete], 5.0); + test([f1 hasSucceeded] || [[f1 forceGetFailure] conformsToProtocol:@protocol(CancelToken)]); + test([f2 hasFailed]); + test([f3 hasFailed]); + test([f4 hasSucceeded] || [[f4 forceGetFailure] conformsToProtocol:@protocol(CancelToken)]); +} + +-(void)testQueryAddresses_FastCancel { + CancelTokenSource* c = [CancelTokenSource cancelTokenSource]; + Future* f = [DnsManager asyncQueryAddressesForDomainName:reliableHostName + unlessCancelled:[c getToken]]; + [c cancel]; + test(![f isIncomplete]); +} + +@end diff --git a/Signal/test/network/http/HttpRequestResponseTest.h b/Signal/test/network/http/HttpRequestResponseTest.h new file mode 100644 index 000000000..bd41b424d --- /dev/null +++ b/Signal/test/network/http/HttpRequestResponseTest.h @@ -0,0 +1,5 @@ +#import + +@interface HttpRequestResponseTest : SenTestCase + +@end diff --git a/Signal/test/network/http/HttpRequestResponseTest.m b/Signal/test/network/http/HttpRequestResponseTest.m new file mode 100644 index 000000000..a07a5b5cd --- /dev/null +++ b/Signal/test/network/http/HttpRequestResponseTest.m @@ -0,0 +1,126 @@ +#import "HttpRequestResponseTest.h" +#import "TestUtil.h" +#import "Util.h" +#import "HttpSocket.h" +#import "HttpRequestUtil.h" +#import "PreferencesUtil.h" +#import "SignalUtil.h" + +@implementation HttpRequestResponseTest +-(void) testRequestToInitiate { + [Environment setCurrent:testEnv]; + [[[Environment getCurrent] preferences] setLocalNumberTo:[PhoneNumber phoneNumberFromE164:@"+19027778888"]]; + [[[Environment getCurrent] preferences] setValueForKey:@"Password" toValue:@"shall_not_password"]; + [[[Environment getCurrent] preferences] setValueForKey:@"PasswordCounter" toValue:@2357]; + HttpRequest* h = [HttpRequest httpRequestToInitiateToRemoteNumber:[PhoneNumber phoneNumberFromE164:@"+19023334444"]]; + test([[h method] isEqualToString:@"GET"]); + test([[h location] isEqualToString:@"/session/1/+19023334444"]); + test([[h toHttp] isEqualToString:@"GET /session/1/+19023334444 HTTP/1.0\r\nAuthorization: OTP KzE5MDI3Nzc4ODg4OmluQ3lLcE1ZaFRQS0ZwN3BITlN3bUxVMVpCTT06MjM1Nw==\r\n\r\n"]); + test([h isEqualToHttpRequest:[HttpRequest httpRequestFromData:[h serialize]]]); +} +-(void) testRequestToOpenPort { + HttpRequest* h = [HttpRequest httpRequestToOpenPortWithSessionId:2357]; + test([[h method] isEqualToString:@"GET"]); + test([[h location] isEqualToString:@"/open/2357"]); + test([[h toHttp] isEqualToString:@"GET /open/2357 HTTP/1.0\r\n\r\n"]); + test([h isEqualToHttpRequest:[HttpRequest httpRequestFromData:[h serialize]]]); +} +-(void) testRequestToRing { + [Environment setCurrent:testEnv]; + [[[Environment getCurrent] preferences] setLocalNumberTo:[PhoneNumber phoneNumberFromE164:@"+19025555555"]]; + [[[Environment getCurrent] preferences] setValueForKey:@"Password" toValue:@"shall_not_password"]; + [[[Environment getCurrent] preferences] setValueForKey:@"PasswordCounter" toValue:@0]; + HttpRequest* h = [HttpRequest httpRequestToRingWithSessionId:458847238]; + test([[h method] isEqualToString:@"RING"]); + test([[h location] isEqualToString:@"/session/458847238"]); + test([[h toHttp] isEqualToString:@"RING /session/458847238 HTTP/1.0\r\nAuthorization: OTP KzE5MDI1NTU1NTU1OnpOV1owY3k3S3A5S3NNd0RXbnlHZFBNR2ZzTT06MA==\r\n\r\n"]); + test([h isEqualToHttpRequest:[HttpRequest httpRequestFromData:[h serialize]]]); +} +-(void) testRequestFromData { + HttpRequest* h0 = [HttpRequest httpRequestFromData:[@"GET /index.html HTTP/1.0\r\nContent-Length: 0\r\n\r\n" encodedAsUtf8]]; + test([[h0 method] isEqualToString:@"GET"]); + test([[h0 location] isEqualToString:@"/index.html"]); + test([[h0 headers] count] == 1); + test([[[h0 headers] objectForKey:@"Content-Length"] isEqualToString:@"0"]); + test([[h0 optionalBody] isEqualToString:@""]); + + HttpRequest* h1 = [HttpRequest httpRequestFromData:[@"GET /index.html HTTP/1.0\r\nContent-Length: 10\r\n\r\nabcdefghij" encodedAsUtf8]]; + test([[h1 method] isEqualToString:@"GET"]); + test([[h1 location] isEqualToString:@"/index.html"]); + test([[h1 headers] count] == 1); + test([[[h1 headers] objectForKey:@"Content-Length"] isEqualToString:@"10"]); + test([[h1 optionalBody] isEqualToString:@"abcdefghij"]); + + HttpRequest* h = [HttpRequest httpRequestFromData:[@"GET /index.html HTTP/1.0\r\n\r\n" encodedAsUtf8]]; + test([[h method] isEqualToString:@"GET"]); + test([[h location] isEqualToString:@"/index.html"]); + test([[h headers] count] == 0); + test([h optionalBody] == nil); + + testThrows([HttpRequest httpRequestFromData:[@"GET /index.html HTTP/1.0\r\n" encodedAsUtf8]]); + testThrows([HttpRequest httpRequestFromData:[@"GET /index.html HTTP/1.0\r\nContent-Length: 10\r\n\r\n" encodedAsUtf8]]); + testThrows([HttpRequest httpRequestFromData:[@"GET /index.html\r\n\r\n" encodedAsUtf8]]); +} +-(void) testResponseOk { + HttpResponse* h = [HttpResponse httpResponse200Ok]; + test([h getStatusCode] == 200); + test([h getOptionalBodyText] == nil); + test([[h getHeaders] count] == 0); +} +-(void) testResponseFromData { + HttpResponse* h = [HttpResponse httpResponseFromData:[@"HTTP/1.1 200 OK\r\n\r\n" encodedAsUtf8]]; + test([h isOkResponse]); + test([h getStatusCode] == 200); + test([[h getStatusText] isEqualToString: @"OK"]); + test([h getOptionalBodyText] == nil); + test([[h getHeaders] count] == 0); + + HttpResponse* h2 = [HttpResponse httpResponseFromData:[@"HTTP/1.1 404 Not Found\r\n\r\n" encodedAsUtf8]]; + test(![h2 isOkResponse]); + test([h2 getStatusCode] == 404); + test([[h2 getStatusText] isEqualToString:@"Not Found"]); + test([h2 getOptionalBodyText] == nil); + test([[h2 getHeaders] count] == 0); + + testThrows([HttpResponse httpResponseFromData:[@"HTTP/1.1 200 OK\r\n" encodedAsUtf8]]); + testThrows([HttpResponse httpResponseFromData:[@"HTTP/1.1 200\r\n\r\n" encodedAsUtf8]]); +} +-(void) testTryFromPartialData { + NSUInteger len; + HttpRequestOrResponse* h; + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200" encodedAsUtf8] usedLengthOut:&len]; + test(h == nil); + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200 OK" encodedAsUtf8] usedLengthOut:&len]; + test(h == nil); + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200 OK\r\n" encodedAsUtf8] usedLengthOut:&len]; + test(h == nil); + + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200 OK\r\n\r\n" encodedAsUtf8] usedLengthOut:&len]; + test([h isResponse]); + test([[h response] isOkResponse]); + test(len == 19); + + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200 OK\r\n\r\n*&DY*SWA(TD&(BTNGNSADN" encodedAsUtf8] usedLengthOut:&len]; + test([h isResponse]); + test([[h response] isOkResponse]); + test(len == 19); + + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"GET /index.html" encodedAsUtf8] usedLengthOut:&len]; + test(h == nil); + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"GET /index.html HTTP/1.0\r\n" encodedAsUtf8] usedLengthOut:&len]; + test(h == nil); + + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"GET /index.html HTTP/1.0\r\n\r\n" encodedAsUtf8] usedLengthOut:&len]; + test([h isRequest]); + test([[[h request] method] isEqualToString:@"GET"]); + test(len == 28); + + h = [HttpRequestOrResponse tryExtractFromPartialData:[@"GET /index.html HTTP/1.0\r\n\r\nU$%#*(NYVYAY*" encodedAsUtf8] usedLengthOut:&len]; + test([h isRequest]); + test([[[h request] method] isEqualToString:@"GET"]); + test(len == 28); + + testThrows([HttpRequestOrResponse tryExtractFromPartialData:[@"GET\r\n\r\n" encodedAsUtf8] usedLengthOut:&len]); + testThrows([HttpRequestOrResponse tryExtractFromPartialData:[@"HTTP/1.1 200\r\n\r\n" encodedAsUtf8] usedLengthOut:&len]); +} +@end diff --git a/Signal/test/network/rtp/RtpPacketTests.h b/Signal/test/network/rtp/RtpPacketTests.h new file mode 100644 index 000000000..97d7dcb2b --- /dev/null +++ b/Signal/test/network/rtp/RtpPacketTests.h @@ -0,0 +1,6 @@ +#import +#import "RtpPacket.h" + +@interface RtpPacketTests : SenTestCase + +@end diff --git a/Signal/test/network/rtp/RtpPacketTests.m b/Signal/test/network/rtp/RtpPacketTests.m new file mode 100644 index 000000000..ffb323337 --- /dev/null +++ b/Signal/test/network/rtp/RtpPacketTests.m @@ -0,0 +1,121 @@ +#import "RtpPacketTests.h" +#import "TestUtil.h" +#import "Util.h" + +@implementation RtpPacketTests + +-(void) testRawDataSimple { + RtpPacket* r = [RtpPacket rtpPacketWithVersion:2 + andPadding:0 + andContributingSourceIdentifiers:[NSArray array] + andSynchronizationSourceIdentifier:0 + andMarkerBit:false + andPayloadtype:0 + andSequenceNumber:5 + andTimeStamp:0 + andPayload:increasingData(5)]; + + // values were retained + test([r version] == 2); + test([r padding] == 0); + test([r hasExtensionHeader] == false); + test([[r contributingSourceIdentifiers] count] == 0); + test([r synchronizationSourceIdentifier] == 0); + test([r isMarkerBitSet] == false); + test([r payloadType] == 0); + test([r sequenceNumber] == 5); + test([r timeStamp] == 0); + test([[r payload] isEqualToData:increasingData(5)]); + + // equivalent to simplified constructor + test([r isEqualToRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:5 andPayload:increasingData(5)]]); + + // packed correctly + NSData* expectedData = [@[ + @0x80,@0,@0,@5, + @0,@0,@0,@0, + @0,@0,@0,@0, + @0,@1,@2,@3,@4] toUint8Data]; + test([[r rawPacketDataUsingInteropOptions:@[]] isEqualToData:expectedData]); + + // reparsing packed data gives same packet + test([r isEqualToRtpPacket:[RtpPacket rtpPacketParsedFromPacketData:expectedData]]); + test(![r isEqualToRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:0 andPayload:[NSData data]]]); +} +-(void) testRawData { + RtpPacket* r = [RtpPacket rtpPacketWithVersion:2 + andPadding:3 + andContributingSourceIdentifiers:@[@101, @102] + andSynchronizationSourceIdentifier:0x45645645 + andMarkerBit:true + andPayloadtype:0x77 + andSequenceNumber:0x2122 + andTimeStamp:0xABCDEFAB + andPayload:increasingData(6)]; + + // values were retained + test([r version] == 2); + test([r padding] == 3); + test([r hasExtensionHeader] == false); + test([[r contributingSourceIdentifiers] isEqualToArray:(@[@101, @102])]); + test([r synchronizationSourceIdentifier] == 0x45645645); + test([r isMarkerBitSet] == true); + test([r payloadType] == 0x77); + test([r sequenceNumber] == 0x2122); + test([r timeStamp] == 0xABCDEFAB); + test([[r payload] isEqualToData:increasingData(6)]); + + NSData* expectedData = [@[ + @0xA2,@0xF7,@0x21,@0x22, + @0xAB,@0xCD,@0xEF,@0xAB, + @0x45,@0x64,@0x56,@0x45, + @0,@0,@0,@101, + @0,@0,@0,@102, + @0,@1,@2,@3,@4,@5, + @0,@0,@3] toUint8Data]; + + test([[r rawPacketDataUsingInteropOptions:@[]] isEqualToData:expectedData]); + test([r isEqualToRtpPacket:[RtpPacket rtpPacketParsedFromPacketData:expectedData]]); + test(![r isEqualToRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:90 andPayload:[NSData data]]]); +} +-(void) testExtendedData { + RtpPacket* r = [RtpPacket rtpPacketWithVersion:2 + andPadding:0 + andContributingSourceIdentifiers:[NSArray array] + andSynchronizationSourceIdentifier:0 + andExtensionIdentifier:0xFEAB + andExtensionData:increasingDataFrom(10, 5) + andMarkerBit:false + andPayloadtype:0 + andSequenceNumber:5 + andTimeStamp:0 + andPayload:increasingData(5)]; + + // values were retained + test([r version] == 2); + test([r padding] == 0); + test([r hasExtensionHeader] == true); + test([r extensionHeaderIdentifier] == 0xFEAB); + test([[r extensionHeaderData] isEqualToData:increasingDataFrom(10, 5)]); + test([[r contributingSourceIdentifiers] count] == 0); + test([r synchronizationSourceIdentifier] == 0); + test([r isMarkerBitSet] == false); + test([r payloadType] == 0); + test([r sequenceNumber] == 5); + test([r timeStamp] == 0); + test([[r payload] isEqualToData:increasingData(5)]); + + NSData* expectedData = [@[ + @0x90,@0,@0,@5, + @0,@0,@0,@0, + @0,@0,@0,@0, + @0xFE,@0xAB, + @0, @5, + @10,@11,@12,@13,@14, + @0,@1,@2,@3,@4] toUint8Data]; + test([[r rawPacketDataUsingInteropOptions:@[]] isEqualToData:expectedData]); + test([r isEqualToRtpPacket:[RtpPacket rtpPacketParsedFromPacketData:expectedData]]); + test(![r isEqualToRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:0 andPayload:[NSData data]]]); +} + +@end diff --git a/Signal/test/network/rtp/srtp/SecureStreamTest.h b/Signal/test/network/rtp/srtp/SecureStreamTest.h new file mode 100644 index 000000000..d58307002 --- /dev/null +++ b/Signal/test/network/rtp/srtp/SecureStreamTest.h @@ -0,0 +1,5 @@ +#import + +@interface SecureStreamTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/srtp/SecureStreamTest.m b/Signal/test/network/rtp/srtp/SecureStreamTest.m new file mode 100644 index 000000000..74b5d13ef --- /dev/null +++ b/Signal/test/network/rtp/srtp/SecureStreamTest.m @@ -0,0 +1,61 @@ +#import "SecureStreamTest.h" +#import "SrtpStream.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation SecureStreamTest +-(void) testPerturbedRoundTrip { + for (int repeat = 0; repeat < 10; repeat++) { + NSData* key = generatePseudoRandomData(16); + NSData* macKey = generatePseudoRandomData(16); + NSData* salt = generatePseudoRandomData(14); + SrtpStream* ss = [SrtpStream srtpStreamWithCipherKey:key andMacKey:macKey andCipherIvSalt:salt]; + + for (uint64_t sequenceNumber = 0; sequenceNumber < 0x70000; sequenceNumber += 0x7000) { + RtpPacket* r = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:(uint16_t)(sequenceNumber & 0xFFFF) andPayload:generatePseudoRandomData(12)]; + RtpPacket* s = [ss encryptAndAuthenticateNormalRtpPacket:r]; + RtpPacket* r2 = [ss verifyAuthenticationAndDecryptSecuredRtpPacket:s]; + test(![r isEqualToRtpPacket:s]); + test([r isEqualToRtpPacket:r2]); + } + } +} +-(void) testReject { + NSData* key = generatePseudoRandomData(16); + NSData* macKey = generatePseudoRandomData(16); + NSData* salt = generatePseudoRandomData(14); + SrtpStream* ss = [SrtpStream srtpStreamWithCipherKey:key andMacKey:macKey andCipherIvSalt:salt]; + + // fuzzing + testThrows([ss verifyAuthenticationAndDecryptSecuredRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:0 andPayload:generatePseudoRandomData(0)]]); + testThrows([ss verifyAuthenticationAndDecryptSecuredRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:0 andPayload:generatePseudoRandomData(12)]]); + testThrows([ss verifyAuthenticationAndDecryptSecuredRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:0 andPayload:generatePseudoRandomData(100)]]); + + // authenticated then bit flip + RtpPacket* r = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:5 andPayload:generatePseudoRandomData(40)]; + RtpPacket* s = [ss encryptAndAuthenticateNormalRtpPacket:r]; + NSMutableData* m = [[s payload] mutableCopy]; + [m setUint8At:0 to:[m uint8At:0]^1]; + RtpPacket* sm = [r withPayload:m]; + testThrows([ss verifyAuthenticationAndDecryptSecuredRtpPacket:sm]); +} + +-(void) testCannotDesyncExtendedSequenceNumberWithInjectedSequenceNumbers { + NSData* key = generatePseudoRandomData(16); + NSData* macKey = generatePseudoRandomData(16); + NSData* salt = generatePseudoRandomData(14); + SrtpStream* s1 = [SrtpStream srtpStreamWithCipherKey:key andMacKey:macKey andCipherIvSalt:salt]; + SrtpStream* s2 = [SrtpStream srtpStreamWithCipherKey:key andMacKey:macKey andCipherIvSalt:salt]; + + for (NSUInteger i = 0; i < 0x20000; i+= 0x100) { + RtpPacket* m = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:(uint16_t)(i & 0xFFFF) andPayload:generatePseudoRandomData(40)]; + testThrows([s1 verifyAuthenticationAndDecryptSecuredRtpPacket:m]); + } + + RtpPacket* r = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:5 andPayload:generatePseudoRandomData(40)]; + RtpPacket* s = [s2 encryptAndAuthenticateNormalRtpPacket:r]; + RtpPacket* r2 = [s1 verifyAuthenticationAndDecryptSecuredRtpPacket:s]; + test([r isEqualToRtpPacket:r2]); +} + +@end diff --git a/Signal/test/network/rtp/srtp/SequenceCounterTest.h b/Signal/test/network/rtp/srtp/SequenceCounterTest.h new file mode 100644 index 000000000..813fc9ebd --- /dev/null +++ b/Signal/test/network/rtp/srtp/SequenceCounterTest.h @@ -0,0 +1,5 @@ +#import + +@interface SequenceCounterTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/srtp/SequenceCounterTest.m b/Signal/test/network/rtp/srtp/SequenceCounterTest.m new file mode 100644 index 000000000..dfe29aaf9 --- /dev/null +++ b/Signal/test/network/rtp/srtp/SequenceCounterTest.m @@ -0,0 +1,48 @@ +#import "SequenceCounterTest.h" +#import "SequenceCounter.h" +#import "TestUtil.h" + +@implementation SequenceCounterTest +-(void)testCountingForwards { + SequenceCounter* s = [SequenceCounter sequenceCounter]; + + test([s convertNext:1] == (int64_t)1); + test([s convertNext:2] == (int64_t)2); + test([s convertNext:6] == (int64_t)6); +} +-(void)testCountingBackwards { + SequenceCounter* s = [SequenceCounter sequenceCounter]; + + test([s convertNext:UINT16_MAX] == (int64_t)-1); + test([s convertNext:UINT16_MAX-1] == (int64_t)-2); + test([s convertNext:UINT16_MAX-5] == (int64_t)-6); +} +-(void)testCountingLimits { + SequenceCounter* s = [SequenceCounter sequenceCounter]; + + uint16_t signedMin = (uint16_t)((int32_t)INT16_MIN + (1 << 16)); + test([s convertNext:INT16_MAX] == (int64_t)INT16_MAX); + test([s convertNext:INT16_MAX] == (int64_t)INT16_MAX); + test([s convertNext:signedMin] == (int64_t)(INT16_MAX + 1)); + test([s convertNext:signedMin] == (int64_t)(INT16_MAX + 1)); +} +-(void)testCountingRandomizedDelta { + SequenceCounter* s = [SequenceCounter sequenceCounter]; + + int64_t prevLongId = 0; + uint16_t prevShortId = 0; + for (long i = 0; i < 1000; i++) { + int32_t delta = (int32_t)arc4random_uniform(1 << 16); + if (delta > INT16_MAX) delta -= 1 << 16; + int64_t nextLongId = prevLongId + delta; + uint16_t nextShortId = (uint16_t)(nextLongId & 0xFFFF); + int64_t actualNextLongId = [s convertNext:nextShortId]; + if (nextLongId != actualNextLongId) { + STFail(@"Bad transition: %lld, %lld + %lld -> %lld, %lld != %lld", (long long)prevShortId, (long long)prevLongId, (long long)delta, (long long)nextShortId, (long long)actualNextLongId, (long long)nextLongId); + return; + } + prevLongId = nextLongId; + prevShortId = nextShortId; + } +} +@end diff --git a/Signal/test/network/rtp/zrtp/DH3KAgreerTest.h b/Signal/test/network/rtp/zrtp/DH3KAgreerTest.h new file mode 100644 index 000000000..8a2de03ac --- /dev/null +++ b/Signal/test/network/rtp/zrtp/DH3KAgreerTest.h @@ -0,0 +1,13 @@ +// +// DH3KAgreerTest.h +// RedPhone +// +// Created by Twisted Oak Studios Mac Mini on 2013-03-21. +// Copyright (c) 2013 Twisted Oak Studios. All rights reserved. +// + +#import + +@interface DH3KAgreerTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/DH3KAgreerTest.m b/Signal/test/network/rtp/zrtp/DH3KAgreerTest.m new file mode 100644 index 000000000..942e3a0c8 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/DH3KAgreerTest.m @@ -0,0 +1,10 @@ +#import "DH3KAgreerTest.h" +#import "DH3KKeyAgreementProtocol.h" +#import "DH3KKeyAgreementParticipant.h" +#import "Conversions.h" +#import "CryptoTools.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation DH3KAgreerTest +@end diff --git a/Signal/test/network/rtp/zrtp/EC25AgreerTest.m b/Signal/test/network/rtp/zrtp/EC25AgreerTest.m new file mode 100644 index 000000000..7696ba4c0 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/EC25AgreerTest.m @@ -0,0 +1,36 @@ +#import +#import "TestUtil.h" +#import "EC25KeyAgreementParticipant.h" +#import "EC25KeyAgreementProtocol.h" + +@interface EC25AgreerTest : SenTestCase{ + + +} +@end + +@implementation EC25AgreerTest + + +-(void) testKeyExchange { + + EC25KeyAgreementProtocol* protocol = [EC25KeyAgreementProtocol protocol]; + + id ec1 = [protocol generateParticipantWithNewKeys]; + id ec2 = [protocol generateParticipantWithNewKeys]; + id ec3 = [protocol generateParticipantWithNewKeys]; + + NSData* pub_1 = [ec1 getPublicKeyData]; + NSData* pub_2 = [ec2 getPublicKeyData]; + + NSData* shared_1 = [ec1 calculateKeyAgreementAgainstRemotePublicKey:pub_2]; + NSData* shared_2 = [ec2 calculateKeyAgreementAgainstRemotePublicKey:pub_1]; + + test([shared_1 isEqualToData:shared_2]); + + NSData* shared_3 = [ec3 calculateKeyAgreementAgainstRemotePublicKey:pub_1]; + + test(![shared_3 isEqualToData:shared_1]); +} + +@end diff --git a/Signal/test/network/rtp/zrtp/HandshakePacketTest.h b/Signal/test/network/rtp/zrtp/HandshakePacketTest.h new file mode 100644 index 000000000..dd5b8b91b --- /dev/null +++ b/Signal/test/network/rtp/zrtp/HandshakePacketTest.h @@ -0,0 +1,5 @@ +#import + +@interface HandshakePacketTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/HandshakePacketTest.m b/Signal/test/network/rtp/zrtp/HandshakePacketTest.m new file mode 100644 index 000000000..3f35efb34 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/HandshakePacketTest.m @@ -0,0 +1,73 @@ +#import "HandshakePacketTest.h" +#import "HelloPacket.h" +#import "TestUtil.h" +#import "Util.h" +#import "Crc32.h" + +@implementation HandshakePacketTest +-(void) testHelloPacket { + [Environment setCurrent:testEnv]; + HashChain* h = [HashChain hashChainWithSeed:[NSData dataWithLength:32]]; + HelloPacket* p = [HelloPacket helloPacketWithVersion:[@"1.10" encodedAsUtf8] + andClientId:[@"RedPhone 019 " encodedAsAscii] + andHashChainH3:[h h3] + andZid:[Zid zidWithData:increasingData(12)] + andFlags0SMP:0 + andFlagsUnusedLow4:0 + andFlagsUnusedHigh4:0 + andHashSpecIds:@[] + andCipherSpecIds:@[] + andAuthSpecIds:@[] + andAgreeSpecIds:@[] + andSasSpecIds:@[] + authenticatedWithHmacKey:[h h2]]; + NSData* data = [[[p embeddedIntoHandshakePacket] embeddedIntoRtpPacketWithSequenceNumber:0x25 usingInteropOptions:@[]] rawPacketDataUsingInteropOptions:@[]]; + uint8_t expectedData[] = { + 0x10,0x0, + 0x00,0x25, // sequence number + 0x5a,0x52,0x54,0x50, //timestamp: 'ZRTP' + 0,0,0,0, // source identifier + 0x50,0x5A, // extension type = 'PZ', 01010000 01011010 + 0x00,88, // extension length = + 0x48,0x65,0x6C,0x6C,0x6F,0x20,0x20,0x20, //type: "Hello " + 0x31,0x2E,0x31,0x30, //version: "1.10" + 0x52,0x65,0x64,0x50,0x68,0x6F,0x6E,0x65,0x20,0x30,0x31,0x39,0x20,0x20,0x20,0x20, // client id: "RedPhone 019 " + 0x12,0x77,0x13,0x55,0xe4,0x6c,0xd4,0x7c,0x71,0xed,0x17,0x21,0xfd,0x53,0x19,0xb3,0x83,0xcc,0xa3,0xa1,0xf9,0xfc,0xe3,0xaa,0x1c,0x8c,0xd3,0xbd,0x37,0xaf,0x20,0xd7, // h3 + 0,1,2,3,4,5,6,7,8,9,10,11, // ZID + 0,0,0,0, // unused flags and counts + 0x4c,0xbd,0x3e,0xc7,0x2c,0x74,0xce,0xea, //mac + 0xd5,0xdd,0x9d,0x8e //crc + }; + test([data isEqualToData:[NSData dataWithBytes:expectedData length:sizeof(expectedData)]]); +} + +-(void) testLegacyHelloPacket { + [Environment setCurrent:testEnvWith(ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER)]; + HashChain* h = [HashChain hashChainWithSeed:[NSData dataWithLength:32]]; + uint8_t legacySpecifiedData_raw[] = { + 0x20,0x0, // <-- wrong flag + 0x00,0x25, // sequence number + 0x5a,0x52,0x54,0x50, //timestamp: 'ZRTP' + 0,0,0,0, // source identifier + 0,0,0,0,0,0,0,0,0,0,0,0, // <-- incorrect zeroes between header and message + 0x50,0x5A, // extension type = 'PZ', 01010000 01011010 + 0x00,88, // extension length = + 0x48,0x65,0x6C,0x6C,0x6F,0x20,0x20,0x20, //type: "Hello " + 0x31,0x2E,0x31,0x30, //version: "1.10" + 0x52,0x65,0x64,0x50,0x68,0x6F,0x6E,0x65,0x20,0x30,0x31,0x39,0x20,0x20,0x20,0x20, // client id: "RedPhone 019 " + 0x12,0x77,0x13,0x55,0xe4,0x6c,0xd4,0x7c,0x71,0xed,0x17,0x21,0xfd,0x53,0x19,0xb3,0x83,0xcc,0xa3,0xa1,0xf9,0xfc,0xe3,0xaa,0x1c,0x8c,0xd3,0xbd,0x37,0xaf,0x20,0xd7, // h3 + 0,1,2,3,4,5,6,7,8,9,10,11, // ZID + 0,0,0,0, // unused flags and counts + 0x4c,0xbd,0x3e,0xc7,0x2c,0x74,0xce,0xea, //mac + 0xf5,0xd9,0xea,0xdd //crc + }; + NSData* legacySpecifiedData = [NSData dataWithBytes:legacySpecifiedData_raw length:sizeof(legacySpecifiedData_raw)]; + + RtpPacket* rtp = [RtpPacket rtpPacketParsedFromPacketData:legacySpecifiedData]; + HelloPacket* p = [HelloPacket helloPacketParsedFromHandshakePacket:[HandshakePacket handshakePacketParsedFromRtpPacket:rtp]]; + [p verifyMacWithHashChainH2:h.h2]; + test(rtp.wasAdjustedDueToInteropIssues); + test([p.hashChainH3 isEqual:h.h3]); + test([p.clientId isEqual:[@"RedPhone 019 " encodedAsAscii]]); +} +@end diff --git a/Signal/test/network/rtp/zrtp/HashChainTest.h b/Signal/test/network/rtp/zrtp/HashChainTest.h new file mode 100644 index 000000000..188717f02 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/HashChainTest.h @@ -0,0 +1,5 @@ +#import + +@interface HashChainTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/HashChainTest.m b/Signal/test/network/rtp/zrtp/HashChainTest.m new file mode 100644 index 000000000..ac134e928 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/HashChainTest.m @@ -0,0 +1,28 @@ +#import "HashChainTest.h" +#import "HashChain.h" +#import "Util.h" +#import "CryptoTools.h" +#import "TestUtil.h" + +@implementation HashChainTest +-(void) testHashChainKnown { + testThrows([HashChain hashChainWithSeed:nil]); + NSData* d0 = [NSMutableData dataWithLength:32]; + NSData* d1 = [@[@0x66,@0x68,@0x7A,@0xAD,@0xF8,@0x62,@0xBD,@0x77,@0x6C,@0x8F,@0xC1,@0x8B,@0x8E,@0x9F,@0x8E,@0x20,@0x08,@0x97,@0x14,@0x85,@0x6E,@0xE2,@0x33,@0xB3,@0x90,@0x2A,@0x59,@0x1D,@0x0D,@0x5F,@0x29,@0x25] toUint8Data]; + NSData* d2 = [d1 hashWithSha256]; + NSData* d3 = [d2 hashWithSha256]; + + HashChain* h = [HashChain hashChainWithSeed:d0]; + test([[h h0] isEqualToData:d0]); + test([[h h1] isEqualToData:d1]); + test([[h h2] isEqualToData:d2]); + test([[h h3] isEqualToData:d3]); +} +-(void) testHashChainRandom { + HashChain* h = [HashChain hashChainWithSecureGeneratedData]; + + // maybe we'll find a collision! ... maybe not + test(![[h h1] isEqualToData:[h h3]]); + test(![[[HashChain hashChainWithSecureGeneratedData] h2] isEqualToData:[[HashChain hashChainWithSecureGeneratedData] h2]]); +} +@end diff --git a/Signal/test/network/rtp/zrtp/MasterSecretTest.h b/Signal/test/network/rtp/zrtp/MasterSecretTest.h new file mode 100644 index 000000000..30c25223c --- /dev/null +++ b/Signal/test/network/rtp/zrtp/MasterSecretTest.h @@ -0,0 +1,5 @@ +#import + +@interface MasterSecretTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/MasterSecretTest.m b/Signal/test/network/rtp/zrtp/MasterSecretTest.m new file mode 100644 index 000000000..1c6d681d6 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/MasterSecretTest.m @@ -0,0 +1,38 @@ +#import "MasterSecretTest.h" +#import "MasterSecret.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation MasterSecretTest +-(void) testKnownCalculateSharedSecret { + NSData* dhResult = [NSMutableData dataWithLength:384]; + NSData* totalHash = [NSMutableData dataWithLength:32]; + Zid* initiatorZid = [Zid zidWithData:[NSMutableData dataWithLength:12]]; + Zid* responderZid = [Zid zidWithData:[NSMutableData dataWithLength:12]]; + NSData* sharedSecret = [MasterSecret calculateSharedSecretFromDhResult:dhResult + andTotalHash:totalHash + andInitiatorZid:initiatorZid + andResponderZid:responderZid]; + // the expected data here was obtained from the android redphone implementation + NSData* expectedSharedSecret = [(@[@54,@78,@99,@226,@49,@17,@8,@135,@65,@33,@247,@134,@235,@29,@164,@217,@18,@44,@241,@18,@172,@63,@197,@178,@71,@42,@253,@150,@238,@173,@218,@131]) toUint8Data]; + test([sharedSecret isEqualToData:expectedSharedSecret]); +} +-(void) testKnownMasterSecret { + NSData* sharedSecret = [NSMutableData dataWithLength:32]; + NSData* totalHash = [NSMutableData dataWithLength:32]; + Zid* initiatorZid = [Zid zidWithData:[NSMutableData dataWithLength:12]]; + Zid* responderZid = [Zid zidWithData:[NSMutableData dataWithLength:12]]; + + // the expected data here was obtained from the android redphone implementation + MasterSecret* m = [MasterSecret masterSecretFromSharedSecret:sharedSecret andTotalHash:totalHash andInitiatorZid:initiatorZid andResponderZid:responderZid]; + test([[m shortAuthenticationStringData] isEqualToData:[(@[@241,@140,@246,@102]) toUint8Data]]); + test([[m initiatorSrtpKey] isEqualToData:[(@[@202,@139,@183,@119,@244,@164,@247,@11,@232,@161,@199,@120,@229,@49,@239,@141]) toUint8Data]]); + test([[m responderSrtpKey] isEqualToData:[(@[@35,@126,@130,@159,@156,@218,@64,@6,@59,@170,@139,@77,@250,@103,@84,@152]) toUint8Data]]); + test([[m initiatorSrtpSalt] isEqualToData:[(@[@92,@22,@129,@225,@169,@155,@6,@157,@34,@49,@76,@15,@196,@180]) toUint8Data]]); + test([[m responderSrtpSalt] isEqualToData:[(@[@151,@124,@181,@201,@203,@218,@192,@141,@244,@247,@249,@144,@213,@133]) toUint8Data]]); + test([[m initiatorMacKey] isEqualToData:[(@[@215,@167,@226,@196,@14,@124,@137,@75,@48,@110,@159,@47,@243,@238,@171,@213,@103,@181,@70,@206]) toUint8Data]]); + test([[m responderMacKey] isEqualToData:[(@[@215,@225,@180,@37,@18,@248,@122,@2,@24,@12,@149,@241,@8,@193,@103,@102,@117,@50,@27,@138]) toUint8Data]]); + test([[m initiatorZrtpKey] isEqualToData:[(@[@182,@239,@29,@23,@42,@7,@231,@48,@45,@244,@177,@84,@77,@62,@56,@48]) toUint8Data]]); + test([[m responderZrtpKey] isEqualToData:[(@[@59,@57,@33,@50,@121,@161,@218,@19,@255,@246,@98,@228,@68,@142,@50,@175]) toUint8Data]]); +} +@end diff --git a/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.h b/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.h new file mode 100644 index 000000000..f73eb5a81 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.h @@ -0,0 +1,5 @@ +#import + +@interface ShortAuthenticationStringGeneratorTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.m b/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.m new file mode 100644 index 000000000..9ea788d8e --- /dev/null +++ b/Signal/test/network/rtp/zrtp/ShortAuthenticationStringGeneratorTest.m @@ -0,0 +1,11 @@ +#import "ShortAuthenticationStringGeneratorTest.h" +#import "ShortAuthenticationStringGenerator.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation ShortAuthenticationStringGeneratorTest +-(void) testSAS { + test([[ShortAuthenticationStringGenerator generateFromData:[(@[@0,@0]) toUint8Data]] isEqualToString:@"aardvark adroitness"]); + test([[ShortAuthenticationStringGenerator generateFromData:[(@[@0xFF,@0xFF]) toUint8Data]] isEqualToString:@"Zulu Yucatan"]); +} +@end diff --git a/Signal/test/network/rtp/zrtp/ZrtpTest.h b/Signal/test/network/rtp/zrtp/ZrtpTest.h new file mode 100644 index 000000000..fbb1bbbcc --- /dev/null +++ b/Signal/test/network/rtp/zrtp/ZrtpTest.h @@ -0,0 +1,17 @@ +#import +#import "ZrtpManager.h" +#import "HelloPacket.h" +#import "ConfirmPacket.h" +#import "DhPacket.h" +#import "CommitPacket.h" +#import "HandshakePacket.h" +#import "Util.h" +#import "DH3KKeyAgreementProtocol.h" +#import "PregeneratedKeyAgreementParticipantProtocol.h" +#import "MasterSecret.h" +#import "ZrtpResponder.h" +#import "ZrtpInitiator.h" + +@interface ZrtpTest : SenTestCase + +@end diff --git a/Signal/test/network/rtp/zrtp/ZrtpTest.m b/Signal/test/network/rtp/zrtp/ZrtpTest.m new file mode 100644 index 000000000..6b03561a0 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/ZrtpTest.m @@ -0,0 +1,92 @@ +#import "ZrtpTest.h" +#import "ZrtpHandshakeResult.h" +#import "TestUtil.h" +#import "ThreadManager.h" +#import "ZrtpHandshakeResult.h" +#import "DiscardingLog.h" +#import "HelloAckPacket.h" +#import "ConfirmAckPacket.h" +#import "HostNameEndPoint.h" +#import "IpAddress.h" + +bool pm(HandshakePacket* p1, HandshakePacket* p2); +bool pm(HandshakePacket* p1, HandshakePacket* p2) { + return p1 != nil + && p2 != nil + && [p1 class] == [p2 class] + && [[p1 embeddedIntoRtpPacketWithSequenceNumber:0 usingInteropOptions:@[]] isEqualToRtpPacket:[p2 embeddedIntoRtpPacketWithSequenceNumber:0 usingInteropOptions:@[]]]; +} +#define AssertPacketsMatch(p1, p2) STAssertTrue(pm(p1, p2), @"") + +@implementation ZrtpTest + +-(void) testPerturbedZrtpHandshake { + IpEndPoint* receiver = [IpEndPoint ipEndPointAtAddress:[IpAddress localhost] + onPort:10000 + (in_port_t)arc4random_uniform(20000)]; + + [Environment setCurrent:testEnv]; + + UdpSocket* u1 = [UdpSocket udpSocketToFirstSenderOnLocalPort:receiver.port]; + CallController* cc1 = [CallController callControllerForCallInitiatedLocally:true + withRemoteNumber:testPhoneNumber1 + andOptionallySpecifiedContact:nil]; + Future* f1 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u1 interopOptions:@[]] + andCallController:cc1]; + + UdpSocket* u2 = [UdpSocket udpSocketTo:receiver]; + CallController* cc2 = [CallController callControllerForCallInitiatedLocally:false + withRemoteNumber:testPhoneNumber2 + andOptionallySpecifiedContact:nil]; + Future* f2 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u2 interopOptions:@[]] + andCallController:cc2]; + + testChurnUntil(![f1 isIncomplete] && ![f2 isIncomplete], 15.0); + test([f1 hasSucceeded]); + test([f2 hasSucceeded]); + + [cc1 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; + [cc2 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; +} + +-(void) testPerturbedZrtpHandshakeWithoutConfAck { + IpEndPoint* receiver = [IpEndPoint ipEndPointAtAddress:[IpAddress localhost] + onPort:10000 + (in_port_t)arc4random_uniform(20000)]; + [Environment setCurrent:testEnvWith(ENVIRONMENT_TESTING_OPTION_LOSE_CONF_ACK_ON_PURPOSE)]; + + UdpSocket* u1 = [UdpSocket udpSocketToFirstSenderOnLocalPort:receiver.port]; + CallController* cc1 = [CallController callControllerForCallInitiatedLocally:true + withRemoteNumber:testPhoneNumber1 + andOptionallySpecifiedContact:nil]; + Future* f1 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u1 interopOptions:@[]] + andCallController:cc1]; + + UdpSocket* u2 = [UdpSocket udpSocketTo:receiver]; + CallController* cc2 = [CallController callControllerForCallInitiatedLocally:false + withRemoteNumber:testPhoneNumber2 + andOptionallySpecifiedContact:nil]; + Future* f2 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u2 interopOptions:@[]] + andCallController:cc2]; + + testChurnUntil(![f2 isIncomplete], 15.0); + test([f2 hasSucceeded]); + test([f1 isIncomplete]); + + // send authenticated data to signal end of handshake + if ([f2 hasSucceeded]) { + ZrtpHandshakeResult* result = [f2 forceGetResult]; + SrtpSocket* socket = [result secureRtpSocket]; + [socket startWithHandler:[PacketHandler packetHandler:^(id packet) { test(false); } + withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { test(!causedTermination); }] + untilCancelled:[cc1 untilCancelledToken]]; + [socket secureAndSendRtpPacket:[RtpPacket rtpPacketWithDefaultsAndSequenceNumber:1 andPayload:[NSData data]]]; + } + + + testChurnUntil(![f1 isIncomplete], 5.0); + test([f1 hasSucceeded]); + + [cc1 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; + [cc2 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; +} + +@end diff --git a/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.h b/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.h new file mode 100644 index 000000000..f80a967d7 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.h @@ -0,0 +1,14 @@ +#import +#import "KeyAgreementProtocol.h" +#import "KeyAgreementParticipant.h" + +/// A mock key agreement protocol. +/// Used in testing to create key agreement participants with preset keys. +/// It would be very bad if one of these was used in non-testing code... + +@interface PregeneratedKeyAgreementParticipantProtocol : NSObject { +@private id participant; +} + ++(PregeneratedKeyAgreementParticipantProtocol*) pregeneratedWithParticipant:(id)participant; +@end diff --git a/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.m b/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.m new file mode 100644 index 000000000..c3805ff37 --- /dev/null +++ b/Signal/test/network/rtp/zrtp/utilities/PregeneratedKeyAgreementParticipantProtocol.m @@ -0,0 +1,18 @@ +#import "PregeneratedKeyAgreementParticipantProtocol.h" + +@implementation PregeneratedKeyAgreementParticipantProtocol + ++(PregeneratedKeyAgreementParticipantProtocol*) pregeneratedWithParticipant:(id)participant { + PregeneratedKeyAgreementParticipantProtocol* s = [PregeneratedKeyAgreementParticipantProtocol new]; + s->participant = participant; + return s; +} + +-(id) generateParticipantWithNewKeys { + return participant; +} +-(NSData*) getId { + return [[participant getProtocol] getId]; +} + +@end diff --git a/Signal/test/network/tcp/LowLatencyConnectorTest.h b/Signal/test/network/tcp/LowLatencyConnectorTest.h new file mode 100644 index 000000000..df5c79797 --- /dev/null +++ b/Signal/test/network/tcp/LowLatencyConnectorTest.h @@ -0,0 +1,5 @@ +#import + +@interface LowLatencyConnectorTest : SenTestCase + +@end diff --git a/Signal/test/network/tcp/LowLatencyConnectorTest.m b/Signal/test/network/tcp/LowLatencyConnectorTest.m new file mode 100644 index 000000000..50f8821ae --- /dev/null +++ b/Signal/test/network/tcp/LowLatencyConnectorTest.m @@ -0,0 +1,87 @@ +#import "LowLatencyConnectorTest.h" +#import "LowLatencyConnector.h" +#import "NetworkStream.h" +#import "Util.h" +#import "HostNameEndPoint.h" +#import "TestUtil.h" +#import "Future.h" +#import "CancelledToken.h" +#import "ThreadManager.h" + +@implementation LowLatencyConnectorTest + +-(void) testLowLatencyConnect_example { + [Environment setCurrent:testEnvWith(ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS)]; + + NSString* reliableHostName = @"example.com"; + + Future* f = [LowLatencyConnector asyncLowLatencyConnectToEndPoint:[HostNameEndPoint hostNameEndPointWithHostName:reliableHostName + andPort:80] + untilCancelled:nil]; + + testChurnUntil(![f isIncomplete], 5.0); + + LowLatencyCandidate* r = [f forceGetResult]; + NetworkStream* channel = [r networkStream]; + + // --- attempt to actually use the streams --- + __block NSString* response = nil; + PacketHandler* h = [PacketHandler packetHandler:^(id packet) { + @synchronized(churnLock()) { + response = [packet decodedAsUtf8]; + } + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }]; + [channel startWithHandler:h]; + [channel send:[@"HEAD /index.html HTTP/1.1\r\nHost: www.example.com\r\n\r\n" encodedAsUtf8]]; + + testChurnUntil(response != nil, 5.0); + test([response hasPrefix:@"HTTP"]); + + [channel terminate]; +} +-(void) testLowLatencyConnect_google { + [Environment setCurrent:testEnvWith(ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS)]; + + NSString* reliableHostNameKnownToHaveMultipleIps = @"google.com"; + + Future* f = [LowLatencyConnector asyncLowLatencyConnectToEndPoint:[HostNameEndPoint hostNameEndPointWithHostName:reliableHostNameKnownToHaveMultipleIps + andPort:80] + untilCancelled:nil]; + + testChurnUntil(![f isIncomplete], 5.0); + + LowLatencyCandidate* r = [f forceGetResult]; + NetworkStream* channel = [r networkStream]; + + // --- attempt to actually use the streams --- + __block NSString* response = nil; + PacketHandler* h = [PacketHandler packetHandler:^(id packet) { + @synchronized(churnLock()) { + response = [packet decodedAsUtf8]; + } + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }]; + [channel startWithHandler:h]; + [channel send:[@"HEAD /index.html HTTP/1.1\r\nHost: www.example.com\r\n\r\n" encodedAsUtf8]]; + + testChurnUntil(response != nil, 5.0); + test([response hasPrefix:@"HTTP"]); + + [channel terminate]; +} + +-(void) testCancelledLowLatencyConnect { + NSString* reliableHostName = @"example.com"; + + Future* f = [LowLatencyConnector asyncLowLatencyConnectToEndPoint:[HostNameEndPoint hostNameEndPointWithHostName:reliableHostName andPort:80] + untilCancelled:[CancelledToken cancelledToken]]; + + testChurnUntil(![f isIncomplete], 5.0); + + test([f hasFailed]); +} + +@end diff --git a/Signal/test/network/tcp/tls/NetworkStreamTest.h b/Signal/test/network/tcp/tls/NetworkStreamTest.h new file mode 100644 index 000000000..8a4b4c912 --- /dev/null +++ b/Signal/test/network/tcp/tls/NetworkStreamTest.h @@ -0,0 +1,5 @@ +#import + +@interface NetworkStreamTest : SenTestCase + +@end diff --git a/Signal/test/network/tcp/tls/NetworkStreamTest.m b/Signal/test/network/tcp/tls/NetworkStreamTest.m new file mode 100644 index 000000000..4e9d075ea --- /dev/null +++ b/Signal/test/network/tcp/tls/NetworkStreamTest.m @@ -0,0 +1,136 @@ +#import "NetworkStreamTest.h" +#import "NetworkStream.h" +#import "TestUtil.h" +#import "Util.h" +#import "HostNameEndPoint.h" +#import "SecureEndPoint.h" +#import "ThreadManager.h" + +#define TEST_SERVER_HOST @"testing.whispersystems.org" +#define TEST_SERVER_PORT 31337 +#define TEST_SERVER_CERT_PATH @"whisperReal" +#define TEST_SERVER_CERT_TYPE @"der" + +#define TEST_SERVER_INCORRECT_HOST_TO_SAME_IP @"96.126.120.52" +#define TEST_SERVER_INCORRECT_CERT_PATH @"whisperTest" +#define TEST_SERVER_INCORRECT_CERT_TYPE @"der" + +@implementation NetworkStreamTest + +-(void) testReplies { + [Environment setCurrent:testEnvWith(ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS)]; + + HostNameEndPoint* e = [HostNameEndPoint hostNameEndPointWithHostName:@"example.com" andPort:80]; + NetworkStream* s = [NetworkStream networkStreamToEndPoint:e]; + + __block bool receivedReply = false; + [s startWithHandler:[PacketHandler packetHandler:^(id packet){ + @synchronized(churnLock()) { + receivedReply = true; + } + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }]]; + + [s send:[@"HEAD /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n" encodedAsUtf8]]; + + testChurnUntil(receivedReply, 5); + + test(receivedReply); + + [s terminate]; +} + +-(void) testFailsOnClose { + [Environment setCurrent:testEnvWith(ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS)]; + + in_port_t unusedPort = 10000 + (in_port_t)arc4random_uniform(30000); + HostNameEndPoint* e = [HostNameEndPoint hostNameEndPointWithHostName:@"localhost" andPort:unusedPort]; + NetworkStream* s = [NetworkStream networkStreamToEndPoint:e]; + + __block bool errored = false; + [s startWithHandler:[PacketHandler packetHandler:^(id packet){ + test(false); + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + @synchronized(churnLock()) { + errored = true; + } + }]]; + + testChurnUntil(errored, 5); + + test(errored); + + [s terminate]; +} + +-(void) testAuthenticationPass { + [Environment setCurrent:testEnv]; + + SecureEndPoint* e = [SecureEndPoint secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:TEST_SERVER_HOST andPort:TEST_SERVER_PORT] + identifiedByCertificate:[Certificate certificateFromResourcePath:TEST_SERVER_CERT_PATH + ofType:TEST_SERVER_CERT_TYPE]]; + NetworkStream* s = [NetworkStream networkStreamToEndPoint:e]; + + [s startWithHandler:[PacketHandler packetHandler:^(id packet) { + test(false); + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }]]; + Future* f = [s asyncConnectionCompleted]; + + testChurnUntil(![f isIncomplete], 5.0); + + test([f hasSucceeded] && [[f forceGetResult] isEqual:@YES]); + + [s terminate]; +} +-(void) testAuthenticationFail_WrongCert { + [Environment setCurrent:testEnv]; + + SecureEndPoint* e = [SecureEndPoint secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:TEST_SERVER_HOST andPort:TEST_SERVER_PORT] + identifiedByCertificate:[Certificate certificateFromResourcePath:TEST_SERVER_INCORRECT_CERT_PATH + ofType:TEST_SERVER_INCORRECT_CERT_TYPE]]; + NetworkStream* s = [NetworkStream networkStreamToEndPoint:e]; + + __block bool terminated = false; + [s startWithHandler:[PacketHandler packetHandler:^(id packet) { + test(false); + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + @synchronized(churnLock()) { + terminated |= causedTermination; + } + }]]; + + testChurnUntil(terminated, 5.0); + + test([[s asyncConnectionCompleted] hasFailed] && [[[s asyncConnectionCompleted] forceGetFailure] isKindOfClass:[SecurityFailure class]]); + + [s terminate]; +} +-(void) testAuthenticationFail_WrongHostName { + [Environment setCurrent:testEnv]; + + SecureEndPoint* e = [SecureEndPoint secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:TEST_SERVER_INCORRECT_HOST_TO_SAME_IP + andPort:TEST_SERVER_PORT] + identifiedByCertificate:[Certificate certificateFromResourcePath:TEST_SERVER_CERT_PATH + ofType:TEST_SERVER_CERT_TYPE]]; + NetworkStream* s = [NetworkStream networkStreamToEndPoint:e]; + + __block bool terminated = false; + [s startWithHandler:[PacketHandler packetHandler:^(id packet) { + test(false); + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + @synchronized(churnLock()) { + terminated |= causedTermination; + } + }]]; + + testChurnUntil(terminated, 5.0); + + test([[s asyncConnectionCompleted] hasFailed] && [[[s asyncConnectionCompleted] forceGetFailure] isKindOfClass:[SecurityFailure class]]); + + [s terminate]; +} + +@end diff --git a/Signal/test/network/tcp/tls/SecureEndPointTest.h b/Signal/test/network/tcp/tls/SecureEndPointTest.h new file mode 100644 index 000000000..3d584d1f4 --- /dev/null +++ b/Signal/test/network/tcp/tls/SecureEndPointTest.h @@ -0,0 +1,5 @@ +#import + +@interface SecureEndPointTest : SenTestCase + +@end diff --git a/Signal/test/network/tcp/tls/SecureEndPointTest.m b/Signal/test/network/tcp/tls/SecureEndPointTest.m new file mode 100644 index 000000000..d3237afc0 --- /dev/null +++ b/Signal/test/network/tcp/tls/SecureEndPointTest.m @@ -0,0 +1,19 @@ +#import "SecureEndPointTest.h" +#import "SecureEndPoint.h" +#import "TestUtil.h" +#import "IpEndPoint.h" + +@implementation SecureEndPointTest + +-(void) testCert { + Certificate* r = [Certificate certificateFromResourcePath:@"whisperReal" + ofType:@"der"]; + test(r != nil); +} +-(void) testCert2 { + Certificate* r = [Certificate certificateFromResourcePath:@"whisperTest" + ofType:@"der"]; + test(r != nil); +} + +@end diff --git a/Signal/test/network/udp/UdpSocketTest.h b/Signal/test/network/udp/UdpSocketTest.h new file mode 100644 index 000000000..8c207e456 --- /dev/null +++ b/Signal/test/network/udp/UdpSocketTest.h @@ -0,0 +1,5 @@ +#import + +@interface UdpSocketTest : SenTestCase + +@end diff --git a/Signal/test/network/udp/UdpSocketTest.m b/Signal/test/network/udp/UdpSocketTest.m new file mode 100644 index 000000000..dbe2f9c27 --- /dev/null +++ b/Signal/test/network/udp/UdpSocketTest.m @@ -0,0 +1,187 @@ +#import "UdpSocketTest.h" +#import "UdpSocket.h" +#import "Util.h" +#import "IpAddress.h" +#import +#import "TestUtil.h" +#import "ThreadManager.h" +#import "CancelTokenSource.h" + +@implementation UdpSocketTest +-(void) testSpecifiedPortLocally { + CancelTokenSource* receiverLife = [CancelTokenSource cancelTokenSource]; + CancelTokenSource* senderLife = [CancelTokenSource cancelTokenSource]; + + __block NSData* received = nil; + __block bool senderReceivedData = false; + NSData* r1 = [@[@2,@3,@5] toUint8Data]; + NSData* r2 = [@[@7,@11,@13] toUint8Data]; + NSData* r3 = [@[@17,@19,@23] toUint8Data]; + + in_port_t port1 = (in_port_t)(arc4random_uniform(40000) + 10000); + in_port_t port2 = port1 + (in_port_t)1; + + UdpSocket* receiver = [UdpSocket udpSocketFromLocalPort:port1 toRemoteEndPoint:[IpEndPoint ipEndPointAtAddress:[IpAddress localhost] onPort:port2]]; + UdpSocket* sender = [UdpSocket udpSocketFromLocalPort:port2 toRemoteEndPoint:[IpEndPoint ipEndPointAtAddress:[IpAddress localhost] onPort:port1]]; + [receiver startWithHandler:[PacketHandler packetHandler:^(id packet) { + received = packet; + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }] untilCancelled:[receiverLife getToken]]; + __block bool failed = false; + [sender startWithHandler:[PacketHandler packetHandler:^(NSData* packet) { + // there's a length check here because when the destination is unreachable the sender sometimes gets a superfluous empty data callback... no idea why. + senderReceivedData |= [packet length] > 0; + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + failed = true; + }] untilCancelled:[senderLife getToken]]; + + test([receiver isLocalPortKnown]); + test([receiver localPort] == port1); + test([sender isLocalPortKnown]); + test([sender localPort] == port2); + + testChurnAndConditionMustStayTrue(received == nil, 0.1); + + [sender send:r1]; + testChurnUntil([received isEqualToData:r1], 1.0); + test([received isEqualToData:r1]); + + [sender send:r2]; + testChurnUntil([received isEqualToData:r2], 1.0); + + [receiverLife cancel]; + test(!failed); + [sender send:r3]; + testChurnUntil(failed, 1.0); + test([received isEqualToData:r2]); + + [senderLife cancel]; + test(!senderReceivedData); +} +-(void) testArbitraryPortLocally { + CancelTokenSource* receiverLife = [CancelTokenSource cancelTokenSource]; + CancelTokenSource* senderLife = [CancelTokenSource cancelTokenSource]; + + __block NSData* received = nil; + __block bool senderReceivedData = false; + NSData* r1 = [@[@2,@3,@5] toUint8Data]; + NSData* r2 = [@[@7,@11,@13] toUint8Data]; + NSData* r3 = [@[@17,@19,@23] toUint8Data]; + + in_port_t unusedPort = (in_port_t)(arc4random_uniform(40000) + 10000); + + UdpSocket* receiver = [UdpSocket udpSocketTo:[IpEndPoint ipEndPointAtAddress:[IpAddress localhost] + onPort:unusedPort]]; + [receiver startWithHandler:[PacketHandler packetHandler:^(id packet) { + @synchronized (churnLock()) { + received = packet; + } + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }] untilCancelled:[receiverLife getToken]]; + + __block bool failed = false; + UdpSocket* sender = [UdpSocket udpSocketFromLocalPort:unusedPort + toRemoteEndPoint:[IpEndPoint ipEndPointAtAddress:[IpAddress localhost] + onPort:[receiver localPort]]]; + [sender startWithHandler:[PacketHandler packetHandler:^(NSData* packet) { + // there's a length check here because when the destination is unreachable the sender sometimes gets a superfluous empty data callback... no idea why. + senderReceivedData |= [packet length] > 0; + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + failed = true; + }] untilCancelled:[senderLife getToken]]; + + + testChurnAndConditionMustStayTrue(received == nil, 0.1); + + [sender send:r1]; + testChurnUntil([received isEqualToData:r1], 1.0); + + [sender send:r2]; + testChurnUntil([received isEqualToData:r2], 1.0); + + [receiverLife cancel]; + test(!failed); + [sender send:r3]; + testChurnAndConditionMustStayTrue([received isEqualToData:r2], 0.1); + test([received isEqualToData:r2]); + + [senderLife cancel]; + test(!senderReceivedData); +} +-(void) testUdpListen { + CancelTokenSource* receiverLife = [CancelTokenSource cancelTokenSource]; + CancelTokenSource* senderLife = [CancelTokenSource cancelTokenSource]; + + __block NSUInteger listenerReceiveCount = 0; + __block NSUInteger listenerReceiveLength = 0; + __block NSData* listenerReceivedLast = nil; + __block NSUInteger clientReceiveCount = 0; + __block NSUInteger clientReceiveLength = 0; + __block NSData* clientReceivedLast = nil; + + in_port_t port = (in_port_t)(arc4random_uniform(40000) + 10000); + + UdpSocket* listener = [UdpSocket udpSocketToFirstSenderOnLocalPort:port]; + [listener startWithHandler:[PacketHandler packetHandler:^(NSData* packet) { + listenerReceiveCount += 1; + listenerReceiveLength += [packet length]; + listenerReceivedLast = packet; + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }] untilCancelled:[receiverLife getToken]]; + + IpEndPoint* e = [IpEndPoint ipEndPointAtAddress:[IpAddress localhost] onPort:port]; + UdpSocket* client = [UdpSocket udpSocketTo:e]; + [client startWithHandler:[PacketHandler packetHandler:^(NSData* packet) { + clientReceiveCount += 1; + clientReceiveLength += [packet length]; + clientReceivedLast = packet; + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + test(false); + }] untilCancelled:[senderLife getToken]]; + + test(![listener isRemoteEndPointKnown]); + testThrows([listener remoteEndPoint]); + test([client isRemoteEndPointKnown]); + test([client remoteEndPoint] == e); + test(listenerReceiveCount == 0); + test(clientReceiveCount == 0); + + [client send:increasingData(10)]; + testChurnUntil(listenerReceiveCount > 0, 1.0); + test(clientReceiveCount == 0); + test([listener isRemoteEndPointKnown]); + test([[[[listener remoteEndPoint] address] description] isEqualToString:@"127.0.0.1"]); + test(listenerReceiveLength == 10); + test([listenerReceivedLast isEqualToData:increasingData(10)]); + + [listener send:increasingData(20)]; + testChurnUntil(clientReceiveCount > 0, 1.0); + test(listenerReceiveCount == 1); + test(clientReceiveCount == 1); + test(clientReceiveLength == 20); + test([clientReceivedLast isEqualToData:increasingData(20)]); + + [receiverLife cancel]; + [senderLife cancel]; +} +-(void) testUdpFail { + CancelTokenSource* life = [CancelTokenSource cancelTokenSource]; + + in_port_t unusedPort = 10000 + (in_port_t)arc4random_uniform(30000); + UdpSocket* udp = [UdpSocket udpSocketTo:[IpEndPoint ipEndPointAtAddress:[IpAddress localhost] onPort:unusedPort]]; + __block bool failed = false; + [udp startWithHandler:[PacketHandler packetHandler:^(id packet) { + test(false); + } withErrorHandler:^(id error, id relatedInfo, bool causedTermination) { + failed = true; + }] untilCancelled:[life getToken]]; + + [udp send:increasingData(20)]; + testChurnUntil(failed, 1.0); + + [life cancel]; +} +@end diff --git a/Signal/test/phone/PhoneNumberTest.h b/Signal/test/phone/PhoneNumberTest.h new file mode 100644 index 000000000..d76202e96 --- /dev/null +++ b/Signal/test/phone/PhoneNumberTest.h @@ -0,0 +1,5 @@ +#import + +@interface PhoneNumberTest : SenTestCase + +@end diff --git a/Signal/test/phone/PhoneNumberTest.m b/Signal/test/phone/PhoneNumberTest.m new file mode 100644 index 000000000..a22b4011b --- /dev/null +++ b/Signal/test/phone/PhoneNumberTest.m @@ -0,0 +1,14 @@ +#import "PhoneNumberTest.h" +#import "TestUtil.h" +#import "PhoneNumber.h" + +@implementation PhoneNumberTest + +-(void) testE164 { + test([[[PhoneNumber tryParsePhoneNumberFromText:@"+1 (902) 555-5555" fromRegion:@"US"] toE164] isEqualToString:@"+19025555555"]); + test([[[PhoneNumber tryParsePhoneNumberFromText:@"1 (902) 555-5555" fromRegion:@"US"] toE164] isEqualToString:@"+19025555555"]); + test([[[PhoneNumber tryParsePhoneNumberFromText:@"1-902-555-5555" fromRegion:@"US"] toE164] isEqualToString:@"+19025555555"]); + test([[[PhoneNumber tryParsePhoneNumberFromText:@"1-902-555-5555" fromRegion:@"US"] toE164] isEqualToString:@"+19025555555"]); +} + +@end diff --git a/Signal/test/phone/signaling/SessionDescriptorTest.h b/Signal/test/phone/signaling/SessionDescriptorTest.h new file mode 100644 index 000000000..22825a760 --- /dev/null +++ b/Signal/test/phone/signaling/SessionDescriptorTest.h @@ -0,0 +1,5 @@ +#import + +@interface SessionDescriptorTest : SenTestCase + +@end diff --git a/Signal/test/phone/signaling/SessionDescriptorTest.m b/Signal/test/phone/signaling/SessionDescriptorTest.m new file mode 100644 index 000000000..772fa0a29 --- /dev/null +++ b/Signal/test/phone/signaling/SessionDescriptorTest.m @@ -0,0 +1,49 @@ +#import "SessionDescriptorTest.h" +#import "InitiatorSessionDescriptor.h" +#import "ResponderSessionDescriptor.h" +#import "TestUtil.h" +#import "Util.h" + +@implementation SessionDescriptorTest + +-(void) testInitiatorSessionDescriptionJson { + InitiatorSessionDescriptor* d = [InitiatorSessionDescriptor initiatorSessionDescriptorWithSessionId:5 + andRelayServerName:@"example.com" + andRelayPort:6]; + test([d sessionId] == 5); + test([d relayUdpPort] == 6); + test([[d relayServerName] isEqualToString:@"example.com"]); + + // roundtrip + InitiatorSessionDescriptor* d2 = [InitiatorSessionDescriptor initiatorSessionDescriptorFromJson:[d toJson]]; + test([d2 sessionId] == 5); + test([d2 relayUdpPort] == 6); + test([[d2 relayServerName] isEqualToString:@"example.com"]); + + // constant + InitiatorSessionDescriptor* d3 = [InitiatorSessionDescriptor initiatorSessionDescriptorFromJson:@"{\"sessionId\":5,\"serverName\":\"example.com\",\"relayPort\":6}"]; + test([d3 sessionId] == 5); + test([d3 relayUdpPort] == 6); + test([[d3 relayServerName] isEqualToString:@"example.com"]); +} + +-(void) testResponderSessionDescriptorFromEncryptedRemoteNotification2 { + NSDictionary* notification = @{ + @"aps":@{@"alert":@"Incoming Call!"}, + @"m":@"AJV74NzwSbZ1KeV4pRwPfMZQ3a5n0V0/HV7eABUUCJvRVqGe3qFO/2XHKv1nEDwNg2naQDmd/nLOlvk=" + }; + + [Environment setCurrent:testEnv]; + [[Environment preferences] setValueForKey:@"Signaling Mac Key" toValue:[@"0000000000000000000000000000000000000000" decodedAsHexString]]; + [[Environment preferences] setValueForKey:@"Signaling Cipher Key" toValue:[@"00000000000000000000000000000000" decodedAsHexString]]; + + ResponderSessionDescriptor* d = [ResponderSessionDescriptor responderSessionDescriptorFromEncryptedRemoteNotification:notification]; + + test(d.interopVersion == 1); + test(d.relayUdpPort == 11235); + test(d.sessionId == 2357); + test([d.relayServerName isEqualToString:@"Test"]); + test([[d.initiatorNumber toE164] isEqualToString:@"+19027777777"]); +} + +@end diff --git a/Signal/test/profiling/DecayingSampleEstimatorTest.h b/Signal/test/profiling/DecayingSampleEstimatorTest.h new file mode 100644 index 000000000..c9fbecb39 --- /dev/null +++ b/Signal/test/profiling/DecayingSampleEstimatorTest.h @@ -0,0 +1,5 @@ +#import + +@interface DecayingSampleEstimatorTest : SenTestCase + +@end diff --git a/Signal/test/profiling/DecayingSampleEstimatorTest.m b/Signal/test/profiling/DecayingSampleEstimatorTest.m new file mode 100644 index 000000000..69928249c --- /dev/null +++ b/Signal/test/profiling/DecayingSampleEstimatorTest.m @@ -0,0 +1,110 @@ +#import "DecayingSampleEstimatorTest.h" +#import "DecayingSampleEstimator.h" +#import "TestUtil.h" + +@implementation DecayingSampleEstimatorTest +-(void) testDecayingSampleEstimator { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:1.0 andDecayPerUnitSample:0.5]; + test([e currentEstimate] == 1.0); + test([e decayRatePerUnitSample] == 0.5); + + [e updateWithNextSample:2.0]; + test([e currentEstimate] == 1.5); + test([e decayRatePerUnitSample] == 0.5); + + [e updateWithNextSample:2.0]; + test([e currentEstimate] == 1.75); + test([e decayRatePerUnitSample] == 0.5); + + [e updateWithNextSample:1.75]; + test([e currentEstimate] == 1.75); + + [e updateWithNextSample:1.75]; + test([e currentEstimate] == 1.75); +} +-(void) testDecayingSampleEstimatorForce { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:1.0 andDecayPerUnitSample:0.5]; + test([e currentEstimate] == 1.0); + [e forceEstimateTo:5]; + test([e currentEstimate] == 5); + test([e decayRatePerUnitSample] == 0.5); +} +-(void) testDecayingSampleEstimatorQuarter { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:1.0 andDecayPerUnitSample:0.75]; + test([e currentEstimate] == 1.0); + test([e decayRatePerUnitSample] == 0.75); + [e updateWithNextSample:2.0]; + test([e currentEstimate] == 1.75); +} +-(void) testDecayingSampleEstimatorCustomDecayPeriod { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0 andDecayFactor:0.75 perNSamples:2]; + test([e decayRatePerUnitSample] == 0.5); + + [e updateWithNextSample:4]; + [e updateWithNextSample:4]; + test([e currentEstimate] == 3); +} +-(void) testDecayingSampleEstimatorWeighted { + DecayingSampleEstimator* e1 = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0.0 andDecayPerUnitSample:0.25]; + DecayingSampleEstimator* e2 = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0.0 andDecayPerUnitSample:0.25]; + + [e1 updateWithNextSample:2.0 withSampleWeight:0.5]; + [e1 updateWithNextSample:2.0 withSampleWeight:0.5]; + [e2 updateWithNextSample:2.0]; + test(ABS([e1 currentEstimate] - [e2 currentEstimate]) < 0.00001); + + [e1 updateWithNextSample:-1.0 withSampleWeight:2.0]; + [e2 updateWithNextSample:-1.0]; + [e2 updateWithNextSample:-1.0]; + test(ABS([e1 currentEstimate] - [e2 currentEstimate]) < 0.00001); +} +-(void) testDecayingSampleEstimatorCornerCase0 { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:1.0 andDecayPerUnitSample:0]; + test([e decayRatePerUnitSample] == 0); + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:5.0]; + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:535325.0]; + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:-535325.0]; + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:100.0 withSampleWeight:0]; + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:200.0 withSampleWeight:100]; + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:300.0 withSampleWeight:1]; + test([e currentEstimate] == 1.0); +} +-(void) testDecayingSampleEstimatorCornerCase1 { + DecayingSampleEstimator* e = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:1.0 andDecayPerUnitSample:1]; + test([e decayRatePerUnitSample] == 1); + test([e currentEstimate] == 1.0); + + [e updateWithNextSample:5.0]; + test([e currentEstimate] == 5.0); + + [e updateWithNextSample:535325.0]; + test([e currentEstimate] == 535325.0); + + [e updateWithNextSample:-535325.0]; + test([e currentEstimate] == -535325.0); + + [e updateWithNextSample:100.0 withSampleWeight:0.0001]; + test([e currentEstimate] == 100.0); + + [e updateWithNextSample:200.0 withSampleWeight:100]; + test([e currentEstimate] == 200.0); + + [e updateWithNextSample:300.0 withSampleWeight:1]; + test([e currentEstimate] == 300.0); + + [e updateWithNextSample:400.0 withSampleWeight:0]; + test([e currentEstimate] == 300.0); +} +@end diff --git a/Signal/test/profiling/EventWindowTest.h b/Signal/test/profiling/EventWindowTest.h new file mode 100644 index 000000000..e9b45a765 --- /dev/null +++ b/Signal/test/profiling/EventWindowTest.h @@ -0,0 +1,5 @@ +#import + +@interface EventWindowTest : SenTestCase + +@end diff --git a/Signal/test/profiling/EventWindowTest.m b/Signal/test/profiling/EventWindowTest.m new file mode 100644 index 000000000..b3954906f --- /dev/null +++ b/Signal/test/profiling/EventWindowTest.m @@ -0,0 +1,21 @@ +#import "EventWindowTest.h" +#import "EventWindow.h" +#import "TestUtil.h" + +@implementation EventWindowTest +-(void) testEventWindow { + EventWindow* w = [EventWindow eventWindowWithWindowDuration:5]; + test([w countAfterRemovingEventsBeforeWindowEndingAt:0] == 0); + [w addEventAtTime:4]; + [w addEventAtTime:6]; + [w addEventAtTime:8]; + + test([w countAfterRemovingEventsBeforeWindowEndingAt:8] == 3); + test([w countAfterRemovingEventsBeforeWindowEndingAt:10] == 2); + test([w countAfterRemovingEventsBeforeWindowEndingAt:12] == 1); + test([w countAfterRemovingEventsBeforeWindowEndingAt:14] == 0); + + // going backwards not allowed + testThrows([w countAfterRemovingEventsBeforeWindowEndingAt:8]); +} +@end diff --git a/Signal/test/util/BloomFilterTest.h b/Signal/test/util/BloomFilterTest.h new file mode 100644 index 000000000..1b8bc6b1a --- /dev/null +++ b/Signal/test/util/BloomFilterTest.h @@ -0,0 +1,5 @@ +#import + +@interface BloomFilterTest : SenTestCase + +@end diff --git a/Signal/test/util/BloomFilterTest.m b/Signal/test/util/BloomFilterTest.m new file mode 100644 index 000000000..74e550654 --- /dev/null +++ b/Signal/test/util/BloomFilterTest.m @@ -0,0 +1,27 @@ +#import "BloomFilterTest.h" +#import "BloomFilter.h" +#import "TestUtil.h" + +@implementation BloomFilterTest + +-(void) testEmptyBloomFilter { + NSMutableData* d = [NSMutableData dataWithLength:100]; + BloomFilter* b = [BloomFilter bloomFilterWithHashCount:5 andData:d]; + NSArray* keys = @[@"", @"a", @"wonder", @"b"]; + for (NSString* key in keys) { + test(![b contains:key]); + } +} +-(void) testFullBloomFilter { + NSMutableData* d = [NSMutableData dataWithLength:100]; + for (NSUInteger i = 0; i < 100; i++) { + [d setUint8At:i to:0xFF]; + } + BloomFilter* b = [BloomFilter bloomFilterWithHashCount:5 andData:d]; + NSArray* keys = @[@"", @"a", @"wonder", @"b"]; + for (NSString* key in keys) { + test([b contains:key]); + } +} + +@end diff --git a/Signal/test/util/CancelTokenTest.h b/Signal/test/util/CancelTokenTest.h new file mode 100644 index 000000000..6a6453995 --- /dev/null +++ b/Signal/test/util/CancelTokenTest.h @@ -0,0 +1,5 @@ +#import + +@interface CancelTokenTest : SenTestCase + +@end diff --git a/Signal/test/util/CancelTokenTest.m b/Signal/test/util/CancelTokenTest.m new file mode 100644 index 000000000..bb2b18909 --- /dev/null +++ b/Signal/test/util/CancelTokenTest.m @@ -0,0 +1,81 @@ +#import "CancelTokenTest.h" +#import "CancelTokenSource.h" +#import "CancelledToken.h" +#import "TestUtil.h" +#import "Util.h" + +@interface OnDealloc : NSObject { +@private void (^action)(); +} ++(OnDealloc*) onDealloc:(void(^)())action; +@end +@implementation OnDealloc ++(OnDealloc*) onDealloc:(void(^)())action { + OnDealloc* d = [OnDealloc new]; + d->action = [action copy]; + return d; +} +-(void) dealloc { + action(); +} +@end + +@implementation CancelTokenTest + +-(void) testCancelTokenSource { + CancelTokenSource* s = [CancelTokenSource cancelTokenSource]; + id c = [s getToken]; + __block int n = 0; + [c whenCancelled:^{n += 1;}]; + [c whenCancelled:^{n += 1;}]; + test(n == 0); + [s cancel]; + test(n == 2); + [c whenCancelled:^{n += 1;}]; + test(n == 3); +} +-(void) testCancelledToken { + __block int n = 0; + [[CancelledToken cancelledToken] whenCancelled:^{n += 1;}]; + test(n == 1); +} +-(void) testCallbacksDeallocWhenSourceDeallocs { + __block bool dealloced = false; + __block id c = nil; + NSObject* lock = [NSObject new]; + [Operation asyncRunOnNewThread:^{ + OnDealloc* d = [OnDealloc onDealloc:^{ + @synchronized(lock) { + dealloced = true; + } + }]; + CancelTokenSource* s = [CancelTokenSource cancelTokenSource]; + c = [s getToken]; + + // hold reference to 'd' in callback + [c whenCancelled:^{ + if (d != nil) { + @synchronized(lock) { + // should never run + test(false); + dealloced = true; + } + } + }]; + + // s goes out of scope, gets dealloced, cancellation becomes impossible + // local reference to d and callback reference should both go away, causing d to be dealloced + }]; + + // spin lock + while (true) { + @synchronized(lock) { + if (dealloced) break; + } + } + test(c != nil); + test(![c isAlreadyCancelled]); + test(dealloced); +} + +@end diff --git a/Signal/test/util/ConversionsTest.h b/Signal/test/util/ConversionsTest.h new file mode 100644 index 000000000..f1544d4f1 --- /dev/null +++ b/Signal/test/util/ConversionsTest.h @@ -0,0 +1,5 @@ +#import + +@interface ConversionsTest : SenTestCase + +@end diff --git a/Signal/test/util/ConversionsTest.m b/Signal/test/util/ConversionsTest.m new file mode 100644 index 000000000..a0b179ab6 --- /dev/null +++ b/Signal/test/util/ConversionsTest.m @@ -0,0 +1,32 @@ +#import "ConversionsTest.h" +#import "Conversions.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation ConversionsTest + +-(void) testDataWithBigEndianBytesOfUInt16 { + test([[NSData dataWithBigEndianBytesOfUInt16:0x1234u] isEqualToData:[(@[@0x12, @0x34]) toUint8Data]]); +} +-(void) testDataWithBigEndianBytesOfUInt32 { + test([[NSData dataWithBigEndianBytesOfUInt32:0x12345678u] isEqualToData:[(@[@0x12, @0x34, @0x56, @0x78]) toUint8Data]]); +} + +-(void) testBigEndianUInt16At { + NSData* d = [@[@0, @1, @2, @0xFF, @3, @4] toUint8Data]; + test(0x1 == [d bigEndianUInt16At:0]); + test(0x102 == [d bigEndianUInt16At:1]); + test(0x2FF == [d bigEndianUInt16At:2]); + test(0xFF03 == [d bigEndianUInt16At:3]); + test(0x304 == [d bigEndianUInt16At:4]); + testThrows([d bigEndianUInt16At:5]); +} +-(void) testBigEndianUInt32At { + NSData* d = [@[@0, @1, @2, @0xFF, @3, @4] toUint8Data]; + test(0x000102FFu == [d bigEndianUInt32At:0]); + test(0x0102FF03u == [d bigEndianUInt32At:1]); + test(0x02FF0304u == [d bigEndianUInt32At:2]); + testThrows([d bigEndianUInt32At:3]); +} + +@end diff --git a/Signal/test/util/Crc32Test.h b/Signal/test/util/Crc32Test.h new file mode 100644 index 000000000..98106e588 --- /dev/null +++ b/Signal/test/util/Crc32Test.h @@ -0,0 +1,5 @@ +#import + +@interface Crc32Test : SenTestCase + +@end diff --git a/Signal/test/util/Crc32Test.m b/Signal/test/util/Crc32Test.m new file mode 100644 index 000000000..f655a2ec7 --- /dev/null +++ b/Signal/test/util/Crc32Test.m @@ -0,0 +1,12 @@ +#import "Crc32Test.h" +#import "Crc32.h" +#import "TestUtil.h" + +@implementation Crc32Test +-(void) testKnownCrc32 { + char* valText = "The quick brown fox jumps over the lazy dog"; + NSData* val = [NSMutableData dataWithBytes:valText length:strlen(valText)]; + test(0x414fa339 == [val crc32]); +} + +@end diff --git a/Signal/test/util/CryptoUtilTest.h b/Signal/test/util/CryptoUtilTest.h new file mode 100644 index 000000000..2e162054c --- /dev/null +++ b/Signal/test/util/CryptoUtilTest.h @@ -0,0 +1,5 @@ +#import + +@interface CryptoUtilTest : SenTestCase + +@end diff --git a/Signal/test/util/CryptoUtilTest.m b/Signal/test/util/CryptoUtilTest.m new file mode 100644 index 000000000..022cd3768 --- /dev/null +++ b/Signal/test/util/CryptoUtilTest.m @@ -0,0 +1,119 @@ +#import "CryptoUtilTest.h" +#import "Util.h" +#import "CryptoTools.h" +#import "TestUtil.h" + +@implementation CryptoUtilTest +-(void) testIsEqualToData_TimingSafe { + test([[NSMutableData dataWithLength:0] isEqualToData_TimingSafe:[NSMutableData dataWithLength:0]]); + test([[NSMutableData dataWithLength:1] isEqualToData_TimingSafe:[NSMutableData dataWithLength:1]]); + test(![[NSMutableData dataWithLength:1] isEqualToData_TimingSafe:[NSMutableData dataWithLength:0]]); + test([[@"01020304" decodedAsHexString] isEqualToData_TimingSafe:[@"01020304" decodedAsHexString]]); + test(![[@"01020305" decodedAsHexString] isEqualToData_TimingSafe:[@"01020304" decodedAsHexString]]); + test(![[@"05020305" decodedAsHexString] isEqualToData_TimingSafe:[@"01020304" decodedAsHexString]]); + test(![[@"05020304" decodedAsHexString] isEqualToData_TimingSafe:[@"01020304" decodedAsHexString]]); + test(![[@"01050304" decodedAsHexString] isEqualToData_TimingSafe:[@"01020304" decodedAsHexString]]); +} +-(void) testKnownHmacSha1 { + char* keyText = "key"; + char* valText = "The quick brown fox jumps over the lazy dog"; + NSData* key = [NSMutableData dataWithBytes:keyText length:strlen(keyText)]; + NSData* val = [NSMutableData dataWithBytes:valText length:strlen(valText)]; + NSData* expected = [@"de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" decodedAsHexString]; + NSData* actual = [val hmacWithSha1WithKey:key]; + test([actual isEqualToData:expected]); +} +-(void) testKnownHmacSha256 { + char* keyText = "key"; + char* valText = "The quick brown fox jumps over the lazy dog"; + NSData* key = [NSMutableData dataWithBytes:keyText length:strlen(keyText)]; + NSData* val = [NSMutableData dataWithBytes:valText length:strlen(valText)]; + NSData* expected = [@"f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8" decodedAsHexString]; + NSData* actual = [val hmacWithSha256WithKey:key]; + test([actual isEqualToData:expected]); +} +-(void) testAesCipherBlockChainingPadding { + NSData* iv = [@"000102030405060708090A0B0C0D0E0F" decodedAsHexString]; + NSData* plain =[@"10b80d8098f0283a820e" decodedAsHexString]; + NSData* key =[@"2b7e151628aed2a6abf7158809cf4f3c" decodedAsHexString]; + + NSData* cipher = [plain encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:key andIv:iv]; + test([cipher length] % 16 == 0); + NSData* replain = [cipher decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:key andIv:iv]; + test([plain length] == [replain length]); +} +-(void) testKnownSha256 { + char* valText = "The quick brown fox jumps over the lazy dog"; + NSData* val = [NSMutableData dataWithBytes:valText length:strlen(valText)]; + NSData* expected = [@"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592" decodedAsHexString]; + NSData* actual = [val hashWithSha256]; + test([actual isEqualToData:expected]); +} +-(void) testRandom { + NSData* d = [CryptoTools generateSecureRandomData:8]; + NSData* d2 = [CryptoTools generateSecureRandomData:8]; + + test(5 == [[CryptoTools generateSecureRandomData:5] length]); + test(8 == [d length]); + + // extremely unlikely to fail if any reasonable amount of entropy is going into d and d2 + test(![d isEqualToData:d2]); +} +-(void) testKnownAesCipherFeedback { + NSData* iv = [@"000102030405060708090a0b0c0d0e0f" decodedAsHexString]; + NSData* plain =[@"6bc1bee22e409f96e93d7e117393172a" decodedAsHexString]; + NSData* cipher =[@"3b3fd92eb72dad20333449f8e83cfb4a" decodedAsHexString]; + NSData* key =[@"2b7e151628aed2a6abf7158809cf4f3c" decodedAsHexString]; + + test([[plain encryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:cipher]); + test([[cipher decryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:plain]); +} +-(void) testPerturbedAesCipherFeedbackInverts { + for (int repeat = 0; repeat < 100; repeat++) { + NSData* iv = generatePseudoRandomData(16); + NSData* input = generatePseudoRandomData(16); + NSData* key = generatePseudoRandomData(16); + + test([[[input encryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] decryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:input]); + test([[[input decryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] encryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:input]); + } +} + +-(void) testKnownAesCounter { + NSData* iv = [@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" decodedAsHexString]; + NSData* plain =[@"6bc1bee22e409f96e93d7e117393172a" decodedAsHexString]; + NSData* cipher =[@"874d6191b620e3261bef6864990db6ce" decodedAsHexString]; + NSData* key =[@"2b7e151628aed2a6abf7158809cf4f3c" decodedAsHexString]; + + test([[plain encryptWithAesInCounterModeWithKey:key andIv:iv] isEqualToData:cipher]); + test([[cipher decryptWithAesInCounterModeWithKey:key andIv:iv] isEqualToData:plain]); +} +-(void) testAesCounterEndianness { + NSData* iv = [@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" decodedAsHexString]; + NSData* plain =[@"6bc1bee22e409f96e93d7e117393172ab1661dadd153b245034f1fb3655dc560" decodedAsHexString]; + NSData* cipher =[@"874d6191b620e3261bef6864990db6ce874d6191b620e3261bef6864990db6ce" decodedAsHexString]; + NSData* key =[@"2b7e151628aed2a6abf7158809cf4f3c" decodedAsHexString]; + + test([[plain encryptWithAesInCounterModeWithKey:key andIv:iv] isEqualToData:cipher]); + test([[cipher decryptWithAesInCounterModeWithKey:key andIv:iv] isEqualToData:plain]); +} +-(void) testPerturbedAesCounterInverts { + for (int repeat = 0; repeat < 100; repeat++) { + NSData* iv = generatePseudoRandomData(16); + NSData* input = generatePseudoRandomData(16); + NSData* key = generatePseudoRandomData(16); + + test([[[input encryptWithAesInCounterModeWithKey:key andIv:iv] decryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:input]); + test([[[input decryptWithAesInCounterModeWithKey:key andIv:iv] encryptWithAesInCipherFeedbackModeWithKey:key andIv:iv] isEqualToData:input]); + } +} +-(void) testComputeKnownOtp { + test([[CryptoTools computeOtpWithPassword:@"password" andCounter:123] isEqualToString:@"SiYZc8Xg6KSmCECSImVSmjnRNfc="]); +} +-(void) testMacAuthenticationSucceeds{ + +} +-(void) testMacAuthenticationFails{ + +} +@end diff --git a/Signal/test/util/CyclicalBufferTest.h b/Signal/test/util/CyclicalBufferTest.h new file mode 100644 index 000000000..c85c68bcf --- /dev/null +++ b/Signal/test/util/CyclicalBufferTest.h @@ -0,0 +1,5 @@ +#import + +@interface CyclicalBufferTest : SenTestCase + +@end diff --git a/Signal/test/util/CyclicalBufferTest.m b/Signal/test/util/CyclicalBufferTest.m new file mode 100644 index 000000000..d57ceb1a5 --- /dev/null +++ b/Signal/test/util/CyclicalBufferTest.m @@ -0,0 +1,121 @@ +#import "CyclicalBufferTest.h" +#import "CyclicalBuffer.h" +#import "TestUtil.h" + +@implementation CyclicalBufferTest +-(void) testEnqueueData { + CyclicalBuffer* c = [CyclicalBuffer new]; + test([c enqueuedLength] == 0); + + [c enqueueData:increasingData(5)]; + test([c enqueuedLength] == 5); + + // empty enqueueData does nothing + [c enqueueData:[NSData data]]; + test([c enqueuedLength] == 5); + + // nil enqueueData fails without ruining everything + testThrows([c enqueueData:nil]); + test([c enqueuedLength] == 5); + + [c enqueueData:increasingData(5)]; + test([c enqueuedLength] == 10); + + [c enqueueData:increasingData(5000)]; + test([c enqueuedLength] == 5010); +} +-(void) testGrow { + CyclicalBuffer* c = [CyclicalBuffer new]; + NSUInteger n = 10000; + for (NSUInteger i = 0; i < n; i++) { + test([c enqueuedLength] == i*4); + [c enqueueData:increasingDataFrom(i*6, 6)]; + NSData* d = [c dequeueDataWithLength:2]; + test([d isEqualToData:increasingDataFrom(i*2, 2)]); + } + test([c enqueuedLength] == n*4); + test([[c dequeueDataWithLength:n*4] isEqualToData:increasingDataFrom(n*2, n*4)]); +} +-(void) testCycle { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:[NSMutableData dataWithLength:200]]; + for (int i = 0; i < 100; i++) { + [c enqueueData:[NSMutableData dataWithLength:11]]; + test([[c dequeueDataWithLength:13] isEqualToData:[NSMutableData dataWithLength:13]]); + } +} +-(void) testDequeueStable { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(20)]; + [c enqueueData:[NSMutableData dataWithLength:200]]; + NSData* d = [c dequeueDataWithLength:20]; + for (int i = 0; i < 100; i++) { + [c enqueueData:[NSMutableData dataWithLength:11]]; + test([[c dequeueDataWithLength:13] isEqualToData:[NSMutableData dataWithLength:13]]); + } + test([d isEqualToData:increasingData(20)]); +} +-(void) testCycleVolatile { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(200)]; + for (NSUInteger i = 0; i < 100; i++) { + [c enqueueData:increasingDataFrom(200+i*11, 11)]; + test([[c dequeuePotentialyVolatileDataWithLength:13] isEqualToData:increasingDataFrom(i*13, 13)]); + } +} +-(void) testDequeue { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(5000)]; + + test([[c dequeueDataWithLength:0] length] == 0); + test([c enqueuedLength] == 5000); + test([[c dequeueDataWithLength:5] isEqualToData:increasingData(5)]); + test([c enqueuedLength] == 4995); + test([[c dequeueDataWithLength:1] isEqualToData:increasingDataFrom(5, 1)]); + test([c enqueuedLength] == 4994); + testThrows([c dequeueDataWithLength:4995]); + test([c enqueuedLength] == 4994); + test([[c dequeueDataWithLength:4994] isEqualToData:increasingDataFrom(6, 4994)]); + test([c enqueuedLength] == 0); +} +-(void) testDiscard { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(5000)]; + + [c discard:4000]; + test([c enqueuedLength] == 1000); + test([[c dequeueDataWithLength:100] isEqualToData:increasingDataFrom(4000, 100)]); + testThrows([c discard:4325663]); + testThrows([c discard:901]); + [c discard:0]; + testThrows([c discard:901]); + [c discard:900]; + test([c enqueuedLength] == 0); + testThrows([c discard:1]); + [c discard:0]; +} +-(void) testDiscardStable { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(200)]; + for (NSUInteger i = 1; i <= 100; i++) { + [c enqueueData:increasingData(11)]; + [c discard:13]; + test([c enqueuedLength] == 200-2*i); + } +} +-(void) testDequeueVolatile { + CyclicalBuffer* c = [CyclicalBuffer new]; + [c enqueueData:increasingData(5000)]; + + test([[c dequeuePotentialyVolatileDataWithLength:0] length] == 0); + test([c enqueuedLength] == 5000); + test([[c dequeuePotentialyVolatileDataWithLength:5] isEqualToData:increasingData(5)]); + test([c enqueuedLength] == 4995); + test([[c dequeuePotentialyVolatileDataWithLength:1] isEqualToData:increasingDataFrom(5, 1)]); + test([c enqueuedLength] == 4994); + testThrows([c dequeuePotentialyVolatileDataWithLength:4995]); + test([c enqueuedLength] == 4994); + test([[c dequeuePotentialyVolatileDataWithLength:4994] isEqualToData:increasingDataFrom(6, 4994)]); + test([c enqueuedLength] == 0); +} +@end diff --git a/Signal/test/util/ExceptionsTest.h b/Signal/test/util/ExceptionsTest.h new file mode 100644 index 000000000..a0c6ac115 --- /dev/null +++ b/Signal/test/util/ExceptionsTest.h @@ -0,0 +1,5 @@ +#import + +@interface ExceptionsTest : SenTestCase + +@end diff --git a/Signal/test/util/ExceptionsTest.m b/Signal/test/util/ExceptionsTest.m new file mode 100644 index 000000000..77128eba6 --- /dev/null +++ b/Signal/test/util/ExceptionsTest.m @@ -0,0 +1,40 @@ +#import "ExceptionsTest.h" +#import "Constraints.h" +#import "TestUtil.h" + +@implementation ExceptionsTest +-(void) testContracts { + require(1 + 1 == 2); + @try { + require(1 + 1 == 3); + STFail(@""); + } @catch (BadArgument* ex) { + test([[ex reason] hasPrefix:@"require 1 + 1 == 3"]); + } + + requireState(1 + 1 == 2); + @try { + requireState(1 + 1 == 3); + STFail(@""); + } @catch (BadState* ex) { + test([[ex reason] hasPrefix:@"required state: 1 + 1 == 3"]); + } + + checkOperationDescribe(1 + 1 == 2, @"addition."); + @try { + checkOperationDescribe(1 + 1 == 3, @"addition."); + STFail(@""); + } @catch (OperationFailed* ex) { + test([[ex reason] hasPrefix:@"Operation failed: addition. Expected: 1 + 1 == 3"]); + } + + checkOperation(1 + 1 == 2); + @try { + checkOperation(1 + 1 == 3); + STFail(@""); + } @catch (OperationFailed* ex) { + test([[ex reason] hasPrefix:@"Operation failed. Expected: 1 + 1 == 3"]); + } +} + +@end diff --git a/Signal/test/util/FunctionalUtilTest.h b/Signal/test/util/FunctionalUtilTest.h new file mode 100644 index 000000000..512b891ea --- /dev/null +++ b/Signal/test/util/FunctionalUtilTest.h @@ -0,0 +1,5 @@ +#import + +@interface FunctionalUtilTest : SenTestCase + +@end diff --git a/Signal/test/util/FunctionalUtilTest.m b/Signal/test/util/FunctionalUtilTest.m new file mode 100644 index 000000000..9308f49ff --- /dev/null +++ b/Signal/test/util/FunctionalUtilTest.m @@ -0,0 +1,55 @@ +#import "FunctionalUtilTest.h" +#import "FunctionalUtil.h" +#import "TestUtil.h" + +@implementation FunctionalUtilTest +-(void) testAny { + test(![@[] any:^(id x) { return false; }]); + test(![@[] any:^(id x) { return true; }]); + test(![@[@1] any:^(id x) { return false; }]); + test([@[@1] any:^(id x) { return true; }]); + + test([(@[@2, @3, @5]) any:^(id x) { return [x intValue] == 3; }]); + test(![(@[@2, @4, @5]) any:^(NSNumber* x) { return [x intValue] == 3; }]); +} +-(void) testMap { + test([[@[] map:^(id x) { return x; }] isEqualToArray:@[]]); + test([[(@[@1,@2]) map:^(id x) { return x; }] isEqualToArray:(@[@1,@2])]); + test([[(@[@1,@2]) map:^(NSNumber* x) { return [NSNumber numberWithInt:[x intValue] + 1]; }] isEqualToArray:(@[@2,@3])]); +} +-(void) testFilter { + test([[@[] filter:^(id x) { return true; }] isEqualToArray:@[]]); + test([[(@[@1,@2]) filter:^(NSNumber* x) { return true; }] isEqualToArray:(@[@1,@2])]); + test([[(@[@1,@2]) filter:^(NSNumber* x) { return false; }] isEqualToArray:(@[])]); + test([[(@[@1,@2]) filter:^(NSNumber* x) { return [x intValue] == 1; }] isEqualToArray:(@[@1])]); + test([[(@[@1,@2]) filter:^(NSNumber* x) { return [x intValue] == 2; }] isEqualToArray:(@[@2])]); +} +-(void) testSum { + test([(@[]) sumDouble] == 0); + test([(@[]) sumNSUInteger] == 0); + test([(@[]) sumNSInteger] == 0); + + test([(@[@1]) sumDouble] == 1); + test([(@[@2]) sumNSUInteger] == 2); + test([(@[@3]) sumNSInteger] == 3); + + test([(@[@1.5, @2.75]) sumDouble] == 4.25); + test([(@[@1, @3]) sumNSUInteger] == 4); + test([(@[@-1, @4]) sumNSInteger] == 3); +} +-(void) testKeyedBy { + test([[@[] keyedBy:^id(id value) { return @true; }] isEqual:@{}]); + test([[@[@1] keyedBy:^id(id value) { return @true; }] isEqual:@{@true : @1}]); + testThrows(([(@[@1, @2]) keyedBy:^id(id value) { return @true; }])); + test([[(@[@1, @2]) keyedBy:^id(id value) { return value; }] isEqual:(@{@1 : @1, @2 : @2})]); + testThrows([(@[@1, @1, @2, @3, @5]) keyedBy:^id(id value) { return [NSNumber numberWithInt:[value intValue]/2]; }]); + test([[(@[@3, @5, @7, @11, @13]) keyedBy:^id(id value) { return [NSNumber numberWithInt:[value intValue]/2]; }] isEqual:(@{@1 : @3, @2 : @5, @3 : @7, @5 : @11, @6 : @13})]); +} +-(void) testGroupBy { + test([[@[] groupBy:^id(id value) { return @true; }] isEqual:@{}]); + test([[@[@1] groupBy:^id(id value) { return @true; }] isEqual:@{@true : @[@1]}]); + test([[(@[@1, @2]) groupBy:^id(id value) { return @true; }] isEqual:@{@true : (@[@1, @2])}]); + test([[(@[@1, @2]) groupBy:^id(id value) { return value; }] isEqual:(@{@1 : @[@1], @2 : @[@2]})]); + test([[(@[@1, @1, @2, @3, @5]) groupBy:^id(id value) { return [NSNumber numberWithInt:[value intValue]/2]; }] isEqual:(@{@0 : @[@1, @1], @1 : @[@2, @3], @2 : @[@5]})]); +} +@end diff --git a/Signal/test/util/PriorityQueueTest.h b/Signal/test/util/PriorityQueueTest.h new file mode 100644 index 000000000..15198a1c1 --- /dev/null +++ b/Signal/test/util/PriorityQueueTest.h @@ -0,0 +1,5 @@ +#import + +@interface PriorityQueueTest : SenTestCase + +@end diff --git a/Signal/test/util/PriorityQueueTest.m b/Signal/test/util/PriorityQueueTest.m new file mode 100644 index 000000000..afe9711a0 --- /dev/null +++ b/Signal/test/util/PriorityQueueTest.m @@ -0,0 +1,110 @@ +#import "PriorityQueueTest.h" +#import "PriorityQueue.h" +#import "Util.h" +#import "TestUtil.h" + +NSArray* RandomPermutation(NSUInteger count); +NSArray* Permutations(NSUInteger count); + +NSArray* RandomPermutation(NSUInteger count) { + NSUInteger d[count]; + for (NSUInteger i = 0; i < count; i++) + d[i] = i; + for (NSUInteger i = 0; i < count; i++) { + NSUInteger j = arc4random_uniform(count - i) + i; + NSUInteger t = d[i]; + d[i] = d[j]; + d[j] = t; + } + NSMutableArray* r = [NSMutableArray array]; + for (NSUInteger i = 0; i < count; i++) + [r addObject:[NSNumber numberWithUnsignedInteger:d[i]]]; + return r; +} +NSArray* Permutations(NSUInteger count) { + if (count == 0) return [NSArray array]; + NSMutableArray* r = [NSMutableArray array]; + for (NSArray* s in Permutations(count - 1)) { + for (NSUInteger e = 0; e < count; e++) { + NSMutableArray* a = [NSMutableArray array]; + [a addObject:[NSNumber numberWithUnsignedInteger:e]]; + for (NSNumber* x in s) { + [a addObject:[x unsignedIntegerValue] < e ? x : [NSNumber numberWithUnsignedInteger:[x unsignedIntegerValue] + 1]]; + } + [r addObject:a]; + } + } + return r; +} + +@implementation PriorityQueueTest + +-(void) testTrivialPrioritizing { + PriorityQueue* q = [PriorityQueue priorityQueueAscendingWithComparator:^(id obj1, id obj2){ return + [obj1 intValue] - [obj2 intValue]; }]; + test([q count] == 0); + testThrows([q peek]); + testThrows([q dequeue]); + + [q enqueue:@1]; + [q enqueue:@2]; + [q enqueue:@3]; + test([q count] == 3); + test([[q peek] intValue] == 1); + test([[q dequeue] intValue] == 1); + test([[q peek] intValue] == 2); + test([[q dequeue] intValue] == 2); + test([[q peek] intValue] == 3); + test([[q dequeue] intValue] == 3); + testThrows([q peek]); + testThrows([q dequeue]); +} +-(void) testOrdersByComparator { + PriorityQueue* q = [PriorityQueue priorityQueueAscendingWithComparator:^(id obj1, id obj2){ return + [obj2 intValue] - [obj1 intValue]; }]; + [q enqueue:@1]; + [q enqueue:@2]; + [q enqueue:@3]; + test([[q dequeue] intValue] == 3); + test([[q dequeue] intValue] == 2); + test([[q dequeue] intValue] == 1); +} +-(void) testSortsAllSmallPermutations { + const NSUInteger N = 7; + for (NSArray* permutation in Permutations(N)) { + PriorityQueue* q = [PriorityQueue priorityQueueAscendingWithComparator:^(id obj1, id obj2){ return + [obj1 intValue] - [obj2 intValue]; }]; + for (NSNumber* e in permutation) { + [q enqueue:e]; + } + + // dequeues in order + for (NSUInteger i = 0; i < N; i++) { + test([q count] == N - i); + test([[q dequeue] unsignedIntegerValue] == i); + } + test([q count] == 0); + } +} +-(void) testSortsRandomLargePermutations { + const NSUInteger Size = 500; + const NSUInteger Repetitions = 50; + for (NSUInteger repeat = 0; repeat < Repetitions; repeat++) { + PriorityQueue* q = [PriorityQueue priorityQueueAscendingWithComparator:^(id obj1, id obj2){ return + [obj1 intValue] - [obj2 intValue]; }]; + NSArray* permutation = RandomPermutation(Size); + for (NSNumber* e in permutation) { + [q enqueue:e]; + } + + // dequeues in order + for (NSUInteger i = 0; i < Size; i++) { + test([q count] == Size - i); + test([[q dequeue] unsignedIntegerValue] == i); + } + test([q count] == 0); + } +} + + +@end diff --git a/Signal/test/util/QueueTest.h b/Signal/test/util/QueueTest.h new file mode 100644 index 000000000..a152d768c --- /dev/null +++ b/Signal/test/util/QueueTest.h @@ -0,0 +1,5 @@ +#import + +@interface QueueTest : SenTestCase + +@end diff --git a/Signal/test/util/QueueTest.m b/Signal/test/util/QueueTest.m new file mode 100644 index 000000000..cafa6d094 --- /dev/null +++ b/Signal/test/util/QueueTest.m @@ -0,0 +1,29 @@ +#import "QueueTest.h" +#import "Queue.h" +#import "TestUtil.h" + +@implementation QueueTest +-(void) queueTest { + Queue* q = [Queue new]; + test([q count] == 0); + testThrows([q peek]); + testThrows([q dequeue]); + + [q enqueue:@5]; + test([q count] == 1); + test([[q peek] isEqualToNumber:@5]); + + [q enqueue:@23]; + test([q count] == 2); + test([[q peek] isEqualToNumber:@5]); + + test([[q dequeue] isEqualToNumber:@5]); + test([q count] == 1); + test([[q peek] isEqualToNumber:@23]); + + test([[q dequeue] isEqualToNumber:@23]); + test([q count] == 0); + testThrows([q peek]); + testThrows([q dequeue]); +} +@end diff --git a/Signal/test/util/UtilTest.h b/Signal/test/util/UtilTest.h new file mode 100644 index 000000000..2b543438a --- /dev/null +++ b/Signal/test/util/UtilTest.h @@ -0,0 +1,5 @@ +#import + +@interface UtilTest : SenTestCase + +@end diff --git a/Signal/test/util/UtilTest.m b/Signal/test/util/UtilTest.m new file mode 100644 index 000000000..822521239 --- /dev/null +++ b/Signal/test/util/UtilTest.m @@ -0,0 +1,398 @@ +#import "UtilTest.h" +#import "Util.h" +#import "TestUtil.h" + +@implementation UtilTest +-(void) testFloorMultiple { + test([NumberUtil largestIntegerThatIsAtMost:0 andIsAMultipleOf:20] == 0); + test([NumberUtil largestIntegerThatIsAtMost:1 andIsAMultipleOf:20] == 0); + test([NumberUtil largestIntegerThatIsAtMost:5 andIsAMultipleOf:20] == 0); + test([NumberUtil largestIntegerThatIsAtMost:15 andIsAMultipleOf:20] == 0); + test([NumberUtil largestIntegerThatIsAtMost:19 andIsAMultipleOf:20] == 0); + test([NumberUtil largestIntegerThatIsAtMost:20 andIsAMultipleOf:20] == 20); + test([NumberUtil largestIntegerThatIsAtMost:21 andIsAMultipleOf:20] == 20); +} +-(void) testCeilingMultiple { + test([NumberUtil smallestIntegerThatIsAtLeast:0 andIsAMultipleOf:20] == 0); + test([NumberUtil smallestIntegerThatIsAtLeast:1 andIsAMultipleOf:20] == 20); + test([NumberUtil smallestIntegerThatIsAtLeast:5 andIsAMultipleOf:20] == 20); + test([NumberUtil smallestIntegerThatIsAtLeast:15 andIsAMultipleOf:20] == 20); + test([NumberUtil smallestIntegerThatIsAtLeast:19 andIsAMultipleOf:20] == 20); + test([NumberUtil smallestIntegerThatIsAtLeast:20 andIsAMultipleOf:20] == 20); + test([NumberUtil smallestIntegerThatIsAtLeast:21 andIsAMultipleOf:20] == 40); +} + +-(void) testArrayToUint8Data { + test([[(@[]) toUint8Data] length] == 0); + + NSData* d = [@[@0, @1] toUint8Data]; + test([d length] == 2); + test(((uint8_t*)[d bytes])[0] == 0); + test(((uint8_t*)[d bytes])[1] == 1); +} +-(void) testArrayConcatDatas { + NSData* d1 = [@[@0, @1] toUint8Data]; + NSData* d2 = [@[@3, @4] toUint8Data]; + NSData* d3 = [@[@6, @7] toUint8Data]; + test([[@[] concatDatas] isEqualToData:[(@[]) toUint8Data]]); + test([[@[d1] concatDatas] isEqualToData:d1]); + test([[(@[d1, d2, d3]) concatDatas] isEqualToData:[(@[@0, @1, @3, @4, @6, @7]) toUint8Data]]); +} + +-(void) testDatadDecodedAsUtf8 { + testThrows([[(@[@0xC3, @0x28]) toUint8Data] decodedAsUtf8]); + + NSString* ab = [[(@[@97, @98]) toUint8Data] decodedAsUtf8]; + NSString* ab0 = [[(@[@97, @98, @0]) toUint8Data] decodedAsUtf8]; + test([ab isEqualToString:@"ab"]); + test([ab0 isEqualToString:@"ab\0"]); + test(![ab0 isEqualToString:ab]); +} +-(void) testTryFindFirstIndexOf { + NSData* d = [@[@0, @1, @2, @3, @4, @5] toUint8Data]; + NSData* d34 = [@[@3, @4] toUint8Data]; + NSData* d67 = [@[@6, @7] toUint8Data]; + NSData* d01 = [@[@0, @1] toUint8Data]; + NSData* d02 = [@[@0, @2] toUint8Data]; + + test([[d tryFindIndexOf:[NSData data]] intValue] == 0); + test([[d tryFindIndexOf:d] intValue] == 0); + + test([[d tryFindIndexOf:d01] intValue] == 0); + test([d tryFindIndexOf:d02] == nil); + test([[d tryFindIndexOf:d34] intValue] == 3); + test([d34 tryFindIndexOf:d] == nil); + test([d tryFindIndexOf:d67] == nil); +} +-(void) testDatadDecodedAsAscii { + testThrows([[(@[@97, @0xAA]) toUint8Data] decodedAsAscii]); + + NSString* ab = [[(@[@97, @98]) toUint8Data] decodedAsAscii]; + NSString* ab0 = [[(@[@97, @98, @0]) toUint8Data] decodedAsAscii]; + test([ab isEqualToString:@"ab"]); + test([ab0 isEqualToString:@"ab\0"]); + test(![ab0 isEqualToString:ab]); +} +-(void) testDatadDecodedAsAsciiReplacingErrorsWithDots { + test([[[(@[@97, @98]) toUint8Data] decodedAsAsciiReplacingErrorsWithDots] isEqualToString:@"ab"]); + test([[[(@[@97, @98, @0, @127, @250]) toUint8Data] decodedAsAsciiReplacingErrorsWithDots] isEqualToString:@"ab..."]); +} +-(void) testDataSkip { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d skip:0] isEqualToData:d]); + test([[d skip:1] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]); + test([[d skip:3] isEqualToData:[@[@3] toUint8Data]]); + test([[d skip:4] length] == 0); + testThrows([d skip:5]); + + // stable + NSMutableData* m = [NSMutableData dataWithLength:2]; + NSData* b = [m skip:1]; + NSData* b2 = [m skip:0]; + [m setUint8At:0 to:1]; + [m setUint8At:1 to:1]; + test([b uint8At:0] == 0); + test([b2 uint8At:0] == 0); +} +-(void) testDataTake { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d take:0] length] == 0); + test([[d take:1] isEqualToData:[(@[@0]) toUint8Data]]); + test([[d take:3] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]); + test([[d take:4] isEqualToData:d]); + testThrows([d take:5]); + + // stable + NSMutableData* m = [NSMutableData dataWithLength:2]; + NSData* b = [m take:1]; + NSData* b2 = [m take:2]; + [m setUint8At:0 to:1]; + [m setUint8At:1 to:1]; + test([b uint8At:0] == 0); + test([b2 uint8At:0] == 0); +} +-(void) testDataSkipLast { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d skipLast:0] isEqualToData:d]); + test([[d skipLast:1] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]); + test([[d skipLast:3] isEqualToData:[@[@0] toUint8Data]]); + test([[d skipLast:4] length] == 0); + testThrows([d skipLast:5]); + + // stable + NSMutableData* m = [NSMutableData dataWithLength:2]; + NSData* b = [m skipLast:1]; + NSData* b2 = [m skipLast:0]; + [m setUint8At:0 to:1]; + [m setUint8At:1 to:1]; + test([b uint8At:0] == 0); + test([b2 uint8At:0] == 0); +} +-(void) testDataTakeLast { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d takeLast:0] length] == 0); + test([[d takeLast:1] isEqualToData:[(@[@3]) toUint8Data]]); + test([[d takeLast:3] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]); + test([[d takeLast:4] isEqualToData:d]); + testThrows([d takeLast:5]); + + // stable + NSMutableData* m = [NSMutableData dataWithLength:2]; + NSData* b = [m takeLast:1]; + NSData* b2 = [m takeLast:2]; + [m setUint8At:0 to:1]; + [m setUint8At:1 to:1]; + test([b uint8At:0] == 0); + test([b2 uint8At:0] == 0); +} + +-(void) testCongruentDifferenceMod2ToThe16 { + test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:0xFFFF] == -2); + test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:10] == 9); + test([NumberUtil congruentDifferenceMod2ToThe16From:0xFFFF to:1] == 2); + test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0x8000] == -0x8000); + test([NumberUtil congruentDifferenceMod2ToThe16From:0x8000 to:0] == -0x8000); + test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0] == 0); +} +-(void) testSubdataVolatileAgainstReference { + NSData* d = increasingData(10); + for (NSUInteger i = 0; i < 10; i++) { + for (NSUInteger j = 0; j < 10-i; j++) { + NSData* s1 = [d subdataVolatileWithRange:NSMakeRange(i, j)]; + NSData* s2 = [d subdataWithRange:NSMakeRange(i, j)]; + test([s1 isEqualToData:s2]); + } + } +} +-(void) testSubdataVolatileErrorCases { + NSData* d = increasingData(10); + + [d subdataVolatileWithRange:NSMakeRange(0, 0)]; + [d subdataVolatileWithRange:NSMakeRange(0, 10)]; + [d subdataVolatileWithRange:NSMakeRange(10, 0)]; + testThrows([d subdataVolatileWithRange:NSMakeRange(0, 11)]); + testThrows([d subdataVolatileWithRange:NSMakeRange(11, 0)]); + testThrows([d subdataVolatileWithRange:NSMakeRange(1, 10)]); + testThrows([d subdataVolatileWithRange:NSMakeRange(10, 1)]); + + // potential wraparound cases + testThrows([d subdataVolatileWithRange:NSMakeRange(NSUIntegerMax, 1)]); + testThrows([d subdataVolatileWithRange:NSMakeRange(1, NSUIntegerMax)]); +} +-(void) testDataSkipVolatile { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d skipVolatile:0] isEqualToData:d]); + test([[d skipVolatile:1] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]); + test([[d skipVolatile:3] isEqualToData:[@[@3] toUint8Data]]); + test([[d skipVolatile:4] length] == 0); + testThrows([d skipVolatile:5]); +} +-(void) testDataTakeVolatile { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d takeVolatile:0] length] == 0); + test([[d takeVolatile:1] isEqualToData:[(@[@0]) toUint8Data]]); + test([[d takeVolatile:3] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]); + test([[d takeVolatile:4] isEqualToData:d]); + testThrows([d takeVolatile:5]); +} +-(void) testDataSkipLastVolatile { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d skipLastVolatile:0] isEqualToData:d]); + test([[d skipLastVolatile:1] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]); + test([[d skipLastVolatile:3] isEqualToData:[@[@0] toUint8Data]]); + test([[d skipLastVolatile:4] length] == 0); + testThrows([d skipLastVolatile:5]); +} +-(void) testDataTakeLastVolatile { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([[d takeLastVolatile:0] length] == 0); + test([[d takeLastVolatile:1] isEqualToData:[(@[@3]) toUint8Data]]); + test([[d takeLastVolatile:3] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]); + test([[d takeLastVolatile:4] isEqualToData:d]); + testThrows([d takeLastVolatile:5]); +} + +-(void) testDataUint8At { + NSData* d = [@[@0, @1, @2, @3] toUint8Data]; + test([d uint8At:0] == 0); + test([d uint8At:1] == 1); + test([d uint8At:2] == 2); + test([d uint8At:3] == 3); + testThrows([d uint8At:4]); +} +-(void) testDataSetUint8At { + NSMutableData* d = [NSMutableData dataWithLength:4]; + [d setUint8At:0 to:11]; + [d setUint8At:1 to:12]; + [d setUint8At:2 to:13]; + [d setUint8At:3 to:14]; + testThrows([d setUint8At:4 to:15]); + test([d isEqualToData:[(@[@11, @12, @13, @14]) toUint8Data]]); +} +-(void) testMutableDataReplaceBytesStartingAt { + NSMutableData* d = [NSMutableData dataWithLength:6]; + NSData* d2 = [@[@1, @2, @3] toUint8Data]; + testThrows([d replaceBytesStartingAt:0 withData:nil]); + testThrows([d replaceBytesStartingAt:4 withData:d2]); + + [d replaceBytesStartingAt:0 withData:d2]; + test([d isEqualToData:[(@[@1, @2, @3, @0, @0, @0]) toUint8Data]]); + [d replaceBytesStartingAt:2 withData:d2]; + test([d isEqualToData:[(@[@1, @2, @1, @2, @3, @0]) toUint8Data]]); + [d replaceBytesStartingAt:3 withData:d2]; + test([d isEqualToData:[(@[@1, @2, @1, @1, @2, @3]) toUint8Data]]); +} +-(void) testStringEncodedAsUtf8 { + test([[@"ab" encodedAsUtf8] isEqualToData:[(@[@97, @98]) toUint8Data]]); +} +-(void) testStringEncodedAsAscii { + test([[@"ab" encodedAsAscii] isEqualToData:[(@[@97, @98]) toUint8Data]]); + testThrows([@"√" encodedAsAscii]); +} +-(void) testBase64EncodeKnown { + test([[[@"" encodedAsUtf8] encodedAsBase64] isEqualToString:@""]); + test([[[@"f" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zg=="]); + test([[[@"fo" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm8="]); + test([[[@"foo" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9v"]); + test([[[@"foob" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYg=="]); + test([[[@"fooba" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYmE="]); + test([[[@"foobar" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYmFy"]); +} +-(void) testBase64DecodeKnown { + test([[@"" encodedAsUtf8] isEqualToData:[@"" decodedAsBase64Data]]); + test([[@"f" encodedAsUtf8] isEqualToData:[@"Zg==" decodedAsBase64Data]]); + test([[@"fo" encodedAsUtf8] isEqualToData:[@"Zm8=" decodedAsBase64Data]]); + test([[@"foo" encodedAsUtf8] isEqualToData:[@"Zm9v" decodedAsBase64Data]]); + test([[@"foob" encodedAsUtf8] isEqualToData:[@"Zm9vYg==" decodedAsBase64Data]]); + test([[@"fooba" encodedAsUtf8] isEqualToData:[@"Zm9vYmE=" decodedAsBase64Data]]); + test([[@"foobar" encodedAsUtf8] isEqualToData:[@"Zm9vYmFy" decodedAsBase64Data]]); +} +-(void) testBase64Perturbed { + for (NSUInteger i = 0; i < 100; i++) { + uint32_t n = arc4random_uniform(10) + 10; + uint8_t data[n]; + arc4random_buf(data, sizeof(data)); + NSData* d = [NSData dataWithBytes:data length:sizeof(data)]; + NSString* b = [d encodedAsBase64]; + NSData* d2 = [b decodedAsBase64Data]; + if (![d isEqualToData:d2]) { + STFail([d description]); + } + } +} +-(void) testToRegex { + testThrows([@"(" toRegularExpression]); + NSRegularExpression* r = [@"a+b" toRegularExpression]; + test([r numberOfMatchesInString:@"a" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0); + test([r numberOfMatchesInString:@"b" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0); + test([r numberOfMatchesInString:@"ba" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0); + test([r numberOfMatchesInString:@"ab" options:NSMatchingAnchored range:NSMakeRange(0, 2)] == 1); + test([r numberOfMatchesInString:@"aab" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 1); + test([r numberOfMatchesInString:@"aabXBNSAUI" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 1); + test([r numberOfMatchesInString:@"aacb" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 0); +} +-(void) testWithMatchesAgainstReplacedBy { + test([[@"(555)-555-5555" withMatchesAgainst:[@"[^0-9+]" toRegularExpression] replacedBy:@""] isEqualToString:@"5555555555"]); + test([[@"aaaaaa" withMatchesAgainst:[@"a" toRegularExpression] replacedBy:@""] isEqualToString:@""]); + test([[@"aabaabaa" withMatchesAgainst:[@"b" toRegularExpression] replacedBy:@"wonder"] isEqualToString:@"aawonderaawonderaa"]); +} +-(void) testContainsAnyMatches { + NSRegularExpression* r = [@"^\\+[0-9]{10,}" toRegularExpression]; + test([@"+5555555555" containsAnyMatches:r]); + test([@"+6555595555" containsAnyMatches:r]); + test([@"+65555555557+/few,pf" containsAnyMatches:r]); + test(![@" +5555555555" containsAnyMatches:r]); + test(![@"+555KL55555" containsAnyMatches:r]); + test(![@"+1-555-555-5555" containsAnyMatches:r]); + test(![@"1-(555)-555-5555" containsAnyMatches:r]); +} +-(void) testWithPrefixRemovedElseNull { + test([[@"test" withPrefixRemovedElseNull:@""] isEqualToString:@"test"]); + test([[@"test" withPrefixRemovedElseNull:@"t"] isEqualToString:@"est"]); + test([[@"test" withPrefixRemovedElseNull:@"te"] isEqualToString:@"st"]); + test([[@"test" withPrefixRemovedElseNull:@"tes"] isEqualToString:@"t"]); + test([[@"test" withPrefixRemovedElseNull:@"test"] isEqualToString:@""]); + test([@"test" withPrefixRemovedElseNull:@"test2"] == nil); + test([@"test" withPrefixRemovedElseNull:@"a"] == nil); + testThrows([@"test" withPrefixRemovedElseNull:nil]); +} +-(void) testToJson { + test([[@{} encodedAsJson] isEqualToString:@"{}"]); + test([[@{@"a":@"b"} encodedAsJson] isEqualToString:@"{\"a\":\"b\"}"]); + test([[@{@"c":@5} encodedAsJson] isEqualToString:@"{\"c\":5}"]); + test([[(@{@"a":@5,@"b":@YES}) encodedAsJson] isEqualToString:@"{\"a\":5,\"b\":true}"]); + + testThrows([@{@"ev": [@"a+b" toRegularExpression]} encodedAsJson]); +} +-(void) testFromJson { + test([[@"{}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{}]); + test([[@"{\"a\":\"b\"}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{@"a":@"b"}]); + test([[@"{\"c\":5}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{@"c":@5}]); + test([[@"{\"a\":5,\"b\":true}" decodedAsJsonIntoDictionary] isEqualToDictionary:(@{@"a":@5,@"b":@YES})]); + + testThrows([@"" decodedAsJsonIntoDictionary]); + testThrows([@"}" decodedAsJsonIntoDictionary]); + testThrows([@"{{}" decodedAsJsonIntoDictionary]); +} +-(void) testRepresentedAsHexString { + test([[[NSData data] encodedAsHexString] isEqualToString:@""]); + test([[increasingData(17) encodedAsHexString] isEqualToString:@"000102030405060708090a0b0c0d0e0f10"]); + test([[increasingDataFrom(256-16,16) encodedAsHexString] isEqualToString:@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"]); +} +-(void) testDecodedAsHexData { + test([[@"" decodedAsHexString] isEqualToData:[NSData data]]); + test([[@"000102030405060708090a0b0c0d0e0f10" decodedAsHexString] isEqualToData:increasingData(17)]); + test([[@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" decodedAsHexString] isEqualToData:increasingDataFrom(256-16,16)]); + testThrows([@"gg" decodedAsHexString]); + testThrows([@"-1" decodedAsHexString]); + testThrows([@"a" decodedAsHexString]); + testThrows([@"-" decodedAsHexString]); + testThrows([@"0" decodedAsHexString]); +} +-(void) testHasUnsignedIntegerValue { + test([@0 hasUnsignedIntegerValue]); + test([@1 hasUnsignedIntegerValue]); + test([@0xFFFFFFFF hasUnsignedIntegerValue]); + test(![@0x100000000 hasUnsignedIntegerValue]); + test([[NSNumber numberWithDouble:pow(2, 31)] hasUnsignedIntegerValue]); + test(![[NSNumber numberWithDouble:pow(2, 32)] hasUnsignedIntegerValue]); + test(![@-1 hasUnsignedIntegerValue]); + test(![@0.5 hasUnsignedIntegerValue]); +} +-(void) testHasUnsignedLongLongValue { + test([@0 hasUnsignedLongLongValue]); + test([@1 hasUnsignedLongLongValue]); + test([@0xFFFFFFFFFFFFFFFF hasUnsignedLongLongValue]); + test([[NSNumber numberWithDouble:pow(2, 63)] hasUnsignedLongLongValue]); + test(![[NSNumber numberWithDouble:pow(2, 64)] hasUnsignedLongLongValue]); + test(![@-1 hasUnsignedLongLongValue]); + test(![@0.5 hasUnsignedLongLongValue]); +} +-(void) testHasLongLongValue { + test([@0 hasLongLongValue]); + test([@1 hasLongLongValue]); + test([@-11 hasLongLongValue]); + test([@LONG_LONG_MAX hasLongLongValue]); + test([@LONG_LONG_MIN hasLongLongValue]); + test(![@ULONG_LONG_MAX hasLongLongValue]); + test([[NSNumber numberWithDouble:pow(2, 62)] hasLongLongValue]); + test(![[NSNumber numberWithDouble:pow(2, 63)] hasLongLongValue]); + test(![[NSNumber numberWithDouble:-pow(2, 64)] hasLongLongValue]); + test(![@0.5 hasLongLongValue]); +} +-(void) tryParseAsUnsignedInteger { + test([@"" tryParseAsUnsignedInteger] == nil); + test([@"88ffhih" tryParseAsUnsignedInteger] == nil); + test([@"0xA" tryParseAsUnsignedInteger] == nil); + test([@"A" tryParseAsUnsignedInteger] == nil); + test([@"4294967297" tryParseAsUnsignedInteger] == nil); + test([@"123456789123456789123456789123456789" tryParseAsUnsignedInteger] == nil); + test([@"-1" tryParseAsUnsignedInteger] == nil); + test([@"-" tryParseAsUnsignedInteger] == nil); + + test([[@"0" tryParseAsUnsignedInteger] isEqual:@0]); + test([[@"1" tryParseAsUnsignedInteger] isEqual:@1]); + test([[@"25" tryParseAsUnsignedInteger] isEqual:@25]); + test([[@"4294967296" tryParseAsUnsignedInteger] isEqual:@4294967296]); +} +@end diff --git a/Signal/translations/ca-ES.lproj/Localizable.strings b/Signal/translations/ca-ES.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..58340ce39dbd68dcc70c812f1b875b59d2ff8218 GIT binary patch literal 14232 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 xoW*9?B;2Uao>B#x2VO$X%Va@*$@B# literal 0 HcmV?d00001 diff --git a/Signal/translations/de.lproj/Localizable.strings b/Signal/translations/de.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..196fa304f827f2849cf0c3748e1ff7d5ad3a86b5 GIT binary patch literal 14172 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 literal 0 HcmV?d00001 diff --git a/Signal/translations/nl.lproj/Localizable.strings b/Signal/translations/nl.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..2c56c9311f48155995b7eb3884bda9b5e8d0928d GIT binary patch literal 14032 zcmcJW*-~7|5r)sTpQ4Q~#~SZ@I2>$33`;XKi=-Pu2nit}AZU=ndPP5q-@*3()!#`~ zb)P}d;XbK`tN_1n&oD<88yevdb8i0G^={Ot)G)-r8#U4^fT1wC;E3& zzuWrOQZs1&uHPkn<4yCbxoYm3&&{=dZksc`bEQxI*8EI=mzzDU&EMbDXCL&vkLg={ zcc51{T6eAIn}>Ce<>9iqtoB`Po=v1!*Zv#g8M%hdT6$V6*HO~NX7f}s|4UkdO2`E5 zp?*dWR1fv(fxh*k*d@!n)m(_Tu_V1rx;krKOIl>l6w&a3I9}CX^u90p$CDj)_5M+M zcRAUo%?_)gh!tLm_T7{wYT9Ukoi>w&(aDZ}uF*{T-D#I|aeu4dbG`LYX?LMmZ zzt&zC=@(ngv&>={+vVFxJU?D4GD&t)%S6TR_t ztEZ7jNKV$BoH;eDb2u(ov#!mZ+US=x9xA$QX~$9>bQ%uc<+m3Q>Et|A++==G{1{7{+yM0v&adA;)P zW7QbY)K$V3x6P-AXKDadlz)x2P7_{wqgpyhHShfdw}DdL>6>@@_deCbu@J>OW&96% z*BEOc%C0NMZ>M_oTAx4BiW{wqPw#~ZYjk%hj?SA?{e7eFf3Hu=3Q&J)Za;|Tm45sR zWCI?6BRI@Rav)xj&U;>{Vz7=%{wsa5D$cL<&Y5_uxgCp(@DMQGnzUPD&*k(ER&Wh6 zLfaJIAmU@$=Ro=TM7~4zFlt;9A4bQc#P6r{chI~|Jlr)u@7%Eu;{JD%_Dp|4OBrp? zN&pH*s*$){mj8ex+P&vlvgw(0bSq7LkPIJ1e=N=Ms+()?vN7?_ZE43k%$hh9y?2T0 zb3K9hyYybu11au^CKkvPUy9oMf~q}a5oLJWNKrLU4|FK@MC~tvYC33`qZ?$e;>(3<3JG~~_x;z_+S0(YDe(_WK9NzmOy^n3nN?qm!^Y>Zw z?eFvgUEb5S6G(tA%6Zedwe+T;_`jQEE^DLI&#Cry9=uR2kU>Dxi+Cs{J5es57ZqgY zx5*B({qWbwUhj)GW~1j@^-IMh7h*N~RXX2Yy?QRYW`85eeKio) z08OpPW1h=)x*@$ViBcWY)&gI<` z(>e;u2%NQFy=OfmC*xDmh{mkrT}kq$`K2U7pUefB?%!3O{(4@0a;^G1o?STQj zBN(~N)%KA%X&M#&5w$d*AjJfzJ?`4}qDJ9)o{mtxL91m)Fr0XJXmK$WreO5!*^^fk zPjnIeX*@>i{#46Ths>Q%`^YC5e=e4?4clj$UHE-5ZTG{`b7$C?x*P9BwWcR#xH zV<3NbVWpLx%0~Ao?@@KBe}Ru(Df{c0Ou-qDb5H5B%xa9WsKL%@#6_l!_kmIBYSvay z*ChW(aapeSNuf`H-OYt>&sLQ6v~nO>ox9wN#0G%tpj8#n^&o%8kx-zO_@etnl_X6PVzu6O8D3#ek3X{w4A*#SgY(qqy|2k(x4 zaSXsitl^p8-42SY(lPf1_Voky_ZcwRAoq3i@cEIl1IRoqju%}fWC9L~n-bG_EXQ`q ztL5v%O=Yy;>lIZ9!PyBN0#S=?CCiSJqodeXAm#gkvzb^ z5m_Jy(5ew+%!g04h7K3(;~V-SII3-#Yv63u%|=+3)wMm-b4{iG!9nDPIRA~wy*~{r z&vFQNNR&Gz_ewDrKpwIOc_q+6G+TuhA zgy1_+f$pXwg7b!~#Us(iTbz~2EFr~wk8IxuWXnldrp>g#r*Oek8h{yxuGFsHNa>^n#`Ean4qV@iR&0n+x;z&RH8wXoIAVIB@l6C)P6`(8o_pH zb2Zgt-z4>2LQmZ}!JF>1x3zb^ui@G6Qv{Scvn0X%t|z17lx_s42&-i|ph&H6lh zkadVDd_N{O%yZBuXsYalS0!ma?RX-czD?DiIF4ywWLV;ZEFSoBzU2ALn$t_H7#VUX zZ#C{WpIP_#%!f$?j2~)HaEpNys|@E zhulV$)8yIdNolE_M6dMF`MM}^;=DUFMvWx~%^k`thup*2lqXX|$bY^o<+}jBqs|#u zneF!!H5aK8zi$4iN|IwLP6a_v-P(@%I2nv-eU6=#qzzlFD~Hov^M3?9XFf@ExJ~03 zM5cF;xQz}VS8}4Cj)N-VyW#mZ_^g)NcjKmnJG(Ns&)bnWpo*ax&{|y=xy|m|Y0ME8 zV~6btrCfK(X5l&HN{~L;6KQ-J;+b@g9p65RX$QG%c4A-S{^$!p5LA|&Z_C`#cX#eh zOr!UjgI#HfZoTV-ZxJt4i*@-&J%v9IW$=V2v{`TEx2G!)sEdk(eDw_s&mKUFfj!GE z*0_uLCpLoLr~^os5q$3IYrT?d-lxn%4Y^S!1qGvVdM)DddQ$1YdeOVkUy!SK5A;Xp z5q&k?37_Qpdyq~CiGS`GOiR9`wZ6~!ou2ASOLs=c;$F#^EbEgqN2ukq)Asq`vUntC z<#Xg|8u&3)$2a@(?JI86Y$v-AIkQOqW#Z2LSkT1tw)D&YF!Cv1jWDPNH3l7uJ938U y*iz0R<~S%HCmy+*;uAeue^tx? literal 0 HcmV?d00001 diff --git a/Signal/translations/ro-RO.lproj/Localizable.strings b/Signal/translations/ro-RO.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..fc54585fb2ce87286f332cb5db7c0aa22457dc82 GIT binary patch literal 13662 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 literal 0 HcmV?d00001 diff --git a/Signal/translations/sv-SE.lproj/Localizable.strings b/Signal/translations/sv-SE.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..4fd485e43767f0ccd7a4abdb5e1986e69730f8ab GIT binary patch literal 13494 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 R|GS}UjyaKCen0cx{{e#>3{n69 literal 0 HcmV?d00001 diff --git a/Signal/whisperReal.der b/Signal/whisperReal.der new file mode 100644 index 0000000000000000000000000000000000000000..d2c37c7aaae47a8b0a66406e1ccf077c5c1f1026 GIT binary patch literal 1026 zcmXqLV*Y2)#PnaO0F=#*i_P6X!KFHZV6d0bx_iC?MAqiEB_#giB=&q#-U6@h?ct zQ$TkXRGOOyuQW4{h?ZVC#7D&t59#F>r5iLcDj^3SBP#=Q6C*zZP@IdYiII`v;qQRo z+nP4iDx9D8aQglF8?|LE8Qdo)R5-6(RxN&;`!mJ4(^91n?Rx)rjTd5wWL&;9pv4gKV$&cBLanpk8tZ|CKW=e|eXId%GcB1ine z8|I5vo+;b#M_5rRt6O3BD$U=`8JCxR{be(Acdr7^wXNN!msyJ%&h`$pnr^buO!bj= zL2c#KD~g+oLnr9nOUjLZ-dor>K_}wWT9w&97Db(LUem7q*erQUbgNYVdgJZ8;@tOo z9bKgMSY;;5$;JaaU3F}d6+-9eHA#Gab@gTZmp{9=nQ*RJ<5B2;Z^i1jEw@t?|I}Ez zskj83H>zf0W@KPoTy0PZPEfMKEKCLr20Cn<32h#XZ9kkCd0FJeWI^!~9PFduo|>0h zlvt9QqTrlbRFavNnGEC@$O25B@*rtt z7GSz-5aBBqdL#6D?e*TZ$CsUN|I)vwT=p(<^Z-*gFnSmn)@Chz!uRjv*Tv8N>{NHU zf6igoucF&WVs)=86>1k<`So|jAA8sJm72>w=}#~BG4=X>Y<=IVm1o?WR4kW1+H$w8 zQgkL`yt|Wk%^T;{*FNg7sqNR8^J4P5eXC82Cj5xcTfy>j;Ub^K;T~@y?gmLRpKBM8 zWSTvLW%fbd-ikZ_p7qFh?v#7=wU7HT-}Z?TLH~mP-dN$R9hDW)q|>=;nw^+HQhez& z$wj+zUz>z+MK0)_#rkLet+Pk-4*Q$z__6a3chr?!y;F7<6*H{6R$1#@cbGfPr)SXv zSB13!o%{YLJTK=Dk{)!4u6&(gK$N0O=}= AH~;_u literal 0 HcmV?d00001