From 42b131a2b9d4e2ccfe3c37906ced2f7d9fd8dcf1 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Mon, 4 Feb 2019 15:17:36 +0100 Subject: [PATCH 01/17] FeedbackHandler enhancements (new term + API change) To avoid polluting the current printing flow of fuddly, the FeedbackHandler class gain the ability to create a new terminal emulator window as a new display for it. FeedbackHandler methods are now provided with a new parameter that refer to the current loaded DM. --- framework/data.py | 10 +-- framework/knowledge/feedback_collector.py | 1 + framework/knowledge/feedback_handler.py | 63 +++++++++++++---- framework/node.py | 83 ++++++++++++----------- framework/project.py | 10 ++- libs/utils.py | 54 +++++++++++++++ test/integration/test_integration.py | 2 +- 7 files changed, 161 insertions(+), 62 deletions(-) diff --git a/framework/data.py b/framework/data.py index 39ae93e..64ed51a 100644 --- a/framework/data.py +++ b/framework/data.py @@ -63,7 +63,7 @@ def to_str(self): def to_bytes(self): raise NotImplementedError - def show(self, raw_limit=200, log_func=lambda x: x): + def show(self, raw_limit=200, log_func=sys.stdout.write): raise NotImplementedError def get_content(self, do_copy=False, materialize=True): @@ -100,7 +100,7 @@ def to_str(self): def to_bytes(self): return self._node.to_bytes() - def show(self, raw_limit=200, log_func=lambda x: x): + def show(self, raw_limit=200, log_func=sys.stdout.write): self._node.show(raw_limit=raw_limit, log_func=log_func) def get_content(self, do_copy=False, materialize=True): @@ -152,8 +152,8 @@ def to_str(self): def to_bytes(self): return self._content - def show(self, raw_limit=200, log_func=lambda x: x): - print(self._content) + def show(self, raw_limit=200, log_func=sys.stdout.write): + log_func(self._content) def get_content(self, do_copy=False, materialize=True): return copy.copy(self._content) if do_copy else self._content @@ -408,7 +408,7 @@ def get_length(self): def get_content(self, do_copy=False): return self._backend.get_content(do_copy=do_copy) - def show(self, raw_limit=200, log_func=lambda x: x): + def show(self, raw_limit=200, log_func=sys.stdout.write): self._backend.show(raw_limit=raw_limit, log_func=log_func) pretty_print = show diff --git a/framework/knowledge/feedback_collector.py b/framework/knowledge/feedback_collector.py index 2bd5eb0..30064ea 100644 --- a/framework/knowledge/feedback_collector.py +++ b/framework/knowledge/feedback_collector.py @@ -29,6 +29,7 @@ class FeedbackSource(object): def __init__(self, src, subref=None, reliability=None, related_tg=None): + self._subref = subref self._name = str(src) if subref is None else str(src) + ' - ' + str(subref) self._obj = src self._reliability = reliability diff --git a/framework/knowledge/feedback_handler.py b/framework/knowledge/feedback_handler.py index 0ce905b..9d718b0 100644 --- a/framework/knowledge/feedback_handler.py +++ b/framework/knowledge/feedback_handler.py @@ -24,6 +24,8 @@ import functools from framework.knowledge.information import * +from libs.utils import Term + import libs.debug_facility as dbg if dbg.KNOW_DEBUG: @@ -56,29 +58,42 @@ def __add__(self, other): class FeedbackHandler(object): - ''' + """ A feedback handler extract information from binary data. - ''' + """ + + def __init__(self, new_window=False, xterm_prg_name='x-terminal-emulator'): + """ + Args: + new_window: If `True`, a new terminal emulator is created, enabling the decoder to use + it for display via the methods `print()` and `print_nl()` + + xterm_prg_name: name of the terminal emulator program to be started + """ + self._new_window = new_window + self._xterm_prg_name = xterm_prg_name - def notify_data_sending(self, data_list, timestamp, target): - ''' + def notify_data_sending(self, current_dm, data_list, timestamp, target): + """ *** To be overloaded *** This function is called when data have been sent. It enables to process feedback relatively to previously sent data. Args: + current_dm (:class:`framework.data_model.DataModel`): current loaded DataModel data_list (list): list of :class:`framework.data.Data` that were sent timestamp (datetime): date when data was sent target (Target): target to which data was sent - ''' + """ pass - def extract_info_from_feedback(self, source, timestamp, content, status): - ''' + def extract_info_from_feedback(self, current_dm, source, timestamp, content, status): + """ *** To be overloaded *** Args: + current_dm (:class:`framework.data_model.DataModel`): current loaded DataModel source (:class:`framework.knowledge.feedback_collector.FeedbackSource`): source of the feedback timestamp (datetime): date of reception of the feedback content (bytes): binary data to process @@ -86,11 +101,11 @@ def extract_info_from_feedback(self, source, timestamp, content, status): Returns: Info: a set of :class:`.information.Info` or only one - ''' + """ return None def estimate_last_data_impact_uniqueness(self): - ''' + """ *** To be overloaded *** Estimate the similarity of the consequences triggered by the current data sending @@ -99,10 +114,32 @@ def estimate_last_data_impact_uniqueness(self): Returns: SimilarityMeasure: provide an estimation of impact similarity - ''' + """ return UNIQUE - def process_feedback(self, source, timestamp, content, status): + def _start(self): + if self._new_window: + self.term = Term(name=self.__class__.__name__, xterm_prg_name=self._xterm_prg_name, + keepterm=True) + self.term.start() + + def _stop(self): + if self._new_window: + self.term.stop() + + def print(self, msg): + if self._new_window: + self.term.print(msg) + else: + print(msg) + + def print_nl(self, msg): + if self._new_window: + self.term.print_nl(msg) + else: + print(msg) + + def process_feedback(self, current_dm, source, timestamp, content, status): info_set = set() truncated_content = None if content is None else content[:60] @@ -113,7 +150,7 @@ def process_feedback(self, source, timestamp, content, status): ' content: {!r} ...\n' ' status: {!s}'.format(source, timestamp, truncated_content, status)) - info = self.extract_info_from_feedback(source, timestamp, content, status) + info = self.extract_info_from_feedback(current_dm, source, timestamp, content, status) if info is not None: if isinstance(info, list): for i in info: @@ -126,7 +163,7 @@ def process_feedback(self, source, timestamp, content, status): class TestFbkHandler(FeedbackHandler): - def extract_info_from_feedback(self, source, timestamp, content, status): + def extract_info_from_feedback(self, current_dm, source, timestamp, content, status): if content is None: return None elif b'Linux' in content: diff --git a/framework/node.py b/framework/node.py index 27da975..18ea8b6 100644 --- a/framework/node.py +++ b/framework/node.py @@ -5969,44 +5969,45 @@ def get_nodes_names(self, conf=None, verbose=False, terminal_only=False): @staticmethod - def _print(msg, rgb, style='', nl=True, log_func=lambda x: x): + def _print(msg, rgb, style='', nl=True, log_func=sys.stdout.write, pretty_print=True): end = '\n' if nl else '' - sys.stdout.write(style) - sys.stdout.write(colorize(msg, rgb=rgb)) - log_func(msg + end) - if style: - sys.stdout.write(FontStyle.END+end) + if pretty_print: + log_func(style) + log_func(colorize(msg, rgb=rgb)) + if style: + log_func(FontStyle.END + end) + else: + log_func(end) else: - sys.stdout.write(end) - sys.stdout.flush() + log_func(msg+end) @staticmethod - def _print_name(msg, style='', nl=True, log_func=lambda x: x): - Node._print(msg, rgb=Color.ND_NAME, style=style, nl=nl, log_func=log_func) + def _print_name(msg, style='', nl=True, log_func=sys.stdout.write, pretty_print=True): + Node._print(msg, rgb=Color.ND_NAME, style=style, nl=nl, log_func=log_func, pretty_print=pretty_print) @staticmethod - def _print_type(msg, style=FontStyle.BOLD, nl=True, log_func=lambda x: x): - Node._print(msg, rgb=Color.ND_TYPE, style=style, nl=nl, log_func=log_func) + def _print_type(msg, style=FontStyle.BOLD, nl=True, log_func=sys.stdout.write, pretty_print=True): + Node._print(msg, rgb=Color.ND_TYPE, style=style, nl=nl, log_func=log_func, pretty_print=pretty_print) @staticmethod - def _print_contents(msg, style='', nl=True, log_func=lambda x: x): - Node._print(msg, rgb=Color.ND_CONTENTS, style=style, nl=nl, log_func=log_func) + def _print_contents(msg, style='', nl=True, log_func=sys.stdout.write, pretty_print=True): + Node._print(msg, rgb=Color.ND_CONTENTS, style=style, nl=nl, log_func=log_func, pretty_print=pretty_print) @staticmethod - def _print_nonterm(msg, style=FontStyle.BOLD, nl=True, log_func=lambda x: x): - Node._print(msg, rgb=Color.ND_NONTERM, style=style, nl=nl, log_func=log_func) + def _print_nonterm(msg, style=FontStyle.BOLD, nl=True, log_func=sys.stdout.write, pretty_print=True): + Node._print(msg, rgb=Color.ND_NONTERM, style=style, nl=nl, log_func=log_func, pretty_print=pretty_print) @staticmethod - def _print_raw(msg, style='', nl=True, hlight=False, log_func=lambda x: x): + def _print_raw(msg, style='', nl=True, hlight=False, log_func=sys.stdout.write, pretty_print=True): if hlight: st = FontStyle.BOLD if style == '' else style - Node._print(msg, rgb=Color.ND_RAW_HLIGHT, style=st, nl=nl, log_func=log_func) + Node._print(msg, rgb=Color.ND_RAW_HLIGHT, style=st, nl=nl, log_func=log_func, pretty_print=pretty_print) else: - Node._print(msg, rgb=Color.ND_RAW, style=style, nl=nl, log_func=log_func) + Node._print(msg, rgb=Color.ND_RAW, style=style, nl=nl, log_func=log_func, pretty_print=pretty_print) def show(self, conf=None, verbose=True, print_name_func=None, print_contents_func=None, print_raw_func=None, print_nonterm_func=None, print_type_func=None, alpha_order=False, - raw_limit=None, log_func=lambda x: x): + raw_limit=None, log_func=sys.stdout.write, pretty_print=True): if print_name_func is None: print_name_func = self._print_name @@ -6079,8 +6080,8 @@ def get_all_smaller_depth(nodes_nb, i, depth, conf): l = sorted(l, key=lambda x: x[0]) name = '[' + self.name + ']' - print_name_func(name, log_func=log_func) - print_name_func('-' * len(name), log_func=log_func) + print_name_func(name, log_func=log_func, pretty_print=pretty_print) + print_name_func('-' * len(name), log_func=log_func, pretty_print=pretty_print) nodes_nb = len(l) @@ -6159,49 +6160,49 @@ def is_node_used_more_than_once(name): .format(node_type, args, raw_len) else: type_and_args = '[{:s}] size={:d}B'.format(node_type, raw_len) - print_nonterm_func(prefix, nl=False, log_func=log_func) - print_name_func('({:d}) {:s}'.format(depth, name), nl=False, log_func=log_func) - print_type_func(type_and_args, nl=False, log_func=log_func) + print_nonterm_func(prefix, nl=False, log_func=log_func, pretty_print=pretty_print) + print_name_func('({:d}) {:s}'.format(depth, name), nl=False, log_func=log_func, pretty_print=pretty_print) + print_type_func(type_and_args, nl=False, log_func=log_func, pretty_print=pretty_print) if node.is_attr_set(NodeInternals.Separator): self._print(sep_deco, rgb=Color.ND_SEPARATOR, style=FontStyle.BOLD, nl=False, - log_func=log_func) + log_func=log_func, pretty_print=pretty_print) self._print(graph_deco, rgb=Color.ND_DUPLICATED, style=FontStyle.BOLD, - log_func=log_func) + log_func=log_func, pretty_print=pretty_print) if val is not None: - print_nonterm_func("{:s} ".format(indent_spc) , nl=False, log_func=log_func) - print_contents_func("\_ {:s}".format(val), log_func=log_func) - print_nonterm_func("{:s} ".format(indent_spc) , nl=False, log_func=log_func) + print_nonterm_func("{:s} ".format(indent_spc), nl=False, log_func=log_func, pretty_print=pretty_print) + print_contents_func("\_ {:s}".format(val), log_func=log_func, pretty_print=pretty_print) + print_nonterm_func("{:s} ".format(indent_spc), nl=False, log_func=log_func, pretty_print=pretty_print) if raw_limit is not None and raw_len > raw_limit: print_raw_func("\_raw: {:s}".format(repr(raw[:raw_limit])), nl=False, - log_func=log_func) - print_raw_func(" ...", hlight=True, log_func=log_func) + log_func=log_func, pretty_print=pretty_print) + print_raw_func(" ...", hlight=True, log_func=log_func, pretty_print=pretty_print) else: - print_raw_func("\_raw: {:s}".format(repr(raw)), log_func=log_func) + print_raw_func("\_raw: {:s}".format(repr(raw)), log_func=log_func, pretty_print=pretty_print) else: print_nonterm_func("{:s}[{:d}] {:s}".format(indent_nonterm, depth, name), nl=False, - log_func=log_func) + log_func=log_func, pretty_print=pretty_print) if isinstance(node.c[conf_tmp], NodeInternals_GenFunc): args = get_args(node, conf_tmp) print_nonterm_func(' [{:s} | node_args: {:s}]'.format(node_type, args), - nl=False, log_func=log_func) + nl=False, log_func=log_func, pretty_print=pretty_print) self._print(graph_deco, rgb=Color.ND_DUPLICATED, style=FontStyle.BOLD, - log_func=log_func) + log_func=log_func, pretty_print=pretty_print) else: - print_nonterm_func(' [{:s}]'.format(node_type), nl=False, log_func=log_func) + print_nonterm_func(' [{:s}]'.format(node_type), nl=False, log_func=log_func, pretty_print=pretty_print) if node.is_nonterm(conf_tmp) and node.encoder is not None: self._print(' [Encoded by {:s}]'.format(node.encoder.__class__.__name__), rgb=Color.ND_ENCODED, style=FontStyle.BOLD, - nl=False, log_func=log_func) + nl=False, log_func=log_func, pretty_print=pretty_print) if node.is_nonterm(conf_tmp) and node.custo.collapse_padding_mode: self._print(' >Collapse Bitfields<', rgb=Color.ND_CUSTO, style=FontStyle.BOLD, - nl=False, log_func=log_func) + nl=False, log_func=log_func, pretty_print=pretty_print) self._print(graph_deco, rgb=Color.ND_DUPLICATED, style=FontStyle.BOLD, - log_func=log_func) + log_func=log_func, pretty_print=pretty_print) else: for name, node in l: - print_name_func("{:s} [{:d}]".format(name, node.depth), log_func=log_func) + print_name_func("{:s} [{:d}]".format(name, node.depth), log_func=log_func, pretty_print=pretty_print) def __lt__(self, other): diff --git a/framework/project.py b/framework/project.py index 9dfd83d..308a015 100644 --- a/framework/project.py +++ b/framework/project.py @@ -78,7 +78,7 @@ def register_feedback_handler(self, fbk_handler): def notify_data_sending(self, data_list, timestamp, target): for fh in self._fbk_handlers: - fh.notify_data_sending(data_list, timestamp, target) + fh.notify_data_sending(self.dm, data_list, timestamp, target) def trigger_feedback_handlers(self, source, timestamp, content, status): if not self._fbk_processing_enabled: @@ -96,7 +96,7 @@ def _feedback_processing(self): continue for fh in self._fbk_handlers: - info = fh.process_feedback(*fbk_tuple) + info = fh.process_feedback(self.dm, *fbk_tuple) if info: self.knowledge_source.add_information(info) @@ -159,6 +159,9 @@ def start(self): DataMaker.knowledge_source = self.knowledge_source ScenarioEnv.knowledge_source = self.knowledge_source + for fh in self._fbk_handlers: + fh._start() + if self._fbk_processing_enabled: self._run_fbk_handling_thread = True self._feedback_fifo = queue.Queue() @@ -180,6 +183,9 @@ def stop(self): self._feedback_processing_thread.join() self._feedback_fifo = None + for fh in self._fbk_handlers: + fh._stop() + def get_operator(self, name): try: ret = self.operators[name] diff --git a/libs/utils.py b/libs/utils.py index c5de18f..6f8d1be 100644 --- a/libs/utils.py +++ b/libs/utils.py @@ -26,6 +26,60 @@ import subprocess import re import inspect +import uuid + + +class Term(object): + + def __init__(self, name=None, keepterm=False, xterm_args=None, xterm_prg_name='x-terminal-emulator'): + self.name = name + self.keepterm = keepterm + self.xterm_args = xterm_args + self.xterm_prg_name = xterm_prg_name + + def start(self): + self._input_desc = None + self.pipe_path = os.sep + os.path.join('tmp', 'fuddly_term_'+str(uuid.uuid4())) + if not os.path.exists(self.pipe_path): + os.mkfifo(self.pipe_path) + self.cmd = [self.xterm_prg_name] + if self.name is not None: + self.cmd.extend(['-title',self.name]) + if self.xterm_args: + self.cmd.extend(self.xterm_args) + if self.keepterm: + self.cmd.append('--hold') + self.cmd.extend(['-e', 'tail -f {:s}'.format(self.pipe_path)]) + self._launch_term() + + def _launch_term(self): + self._p = subprocess.Popen(self.cmd) + if not self._input_desc or self._input_desc.closed: + self._input_desc = open(self.pipe_path, "w") + + def stop(self): + if not self.keepterm and self._p.poll() is None: + self._p.kill() + if self._input_desc: + self._input_desc.close() + self._input_desc = None + try: + os.remove(self.pipe_path) + except FileNotFoundError: + pass + + def print(self, s, newline=False): + s += '\n' if newline else '' + if self._p.poll() is not None: + self._launch_term() + if not self._input_desc or self._input_desc.closed: + self._input_desc = open(self.pipe_path, "w") + self._input_desc.write(s) + self._input_desc.close() + + def print_nl(self, s): + self.print(s, newline=True) + def ensure_dir(f): d = os.path.dirname(f) diff --git a/test/integration/test_integration.py b/test/integration/test_integration.py index 0fd6073..9c91e0e 100644 --- a/test/integration/test_integration.py +++ b/test/integration/test_integration.py @@ -2070,7 +2070,7 @@ def nint_10_helper(blob, constraints, node_internals): # print(repr(top)) print(top.get_value()) - def verif_val_and_print(arg, log_func=None): + def verif_val_and_print(arg): Node._print_contents(arg) if 'TBD' in arg: raise ValueError('Dissection Error!') From 2ca7fcf6266494e289f40bcec04aaaa02c69db64 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Thu, 7 Feb 2019 16:55:56 +0100 Subject: [PATCH 02/17] Fixes regarding feedback handling + vtg support in Periodic class - fix wrong reference for feedback_timeout in NetworkTarget - Simplify FmkPlumbing._collect_residual_feedback() and update NetworkTarget to make it work (as well as making things clearer) - vtg_ids are now supported at Periodic() level in scenario infrastructure - simplify and fix some races in Term() --- framework/data.py | 2 +- framework/knowledge/feedback_handler.py | 21 ++++++++-- framework/plumbing.py | 52 ++++++++++++------------- framework/project.py | 12 ++++-- framework/scenario.py | 3 +- framework/tactics_helpers.py | 2 +- framework/target_helpers.py | 2 +- framework/targets/network.py | 13 +++---- libs/utils.py | 15 +++---- test/integration/test_integration.py | 6 +-- 10 files changed, 70 insertions(+), 58 deletions(-) diff --git a/framework/data.py b/framework/data.py index 64ed51a..6053a6f 100644 --- a/framework/data.py +++ b/framework/data.py @@ -153,7 +153,7 @@ def to_bytes(self): return self._content def show(self, raw_limit=200, log_func=sys.stdout.write): - log_func(self._content) + log_func(unconvert_from_internal_repr(self._content)) def get_content(self, do_copy=False, materialize=True): return copy.copy(self._content) if do_copy else self._content diff --git a/framework/knowledge/feedback_handler.py b/framework/knowledge/feedback_handler.py index 9d718b0..5dc3209 100644 --- a/framework/knowledge/feedback_handler.py +++ b/framework/knowledge/feedback_handler.py @@ -21,6 +21,8 @@ # ################################################################################ +from __future__ import print_function + import functools from framework.knowledge.information import * @@ -62,7 +64,7 @@ class FeedbackHandler(object): A feedback handler extract information from binary data. """ - def __init__(self, new_window=False, xterm_prg_name='x-terminal-emulator'): + def __init__(self, new_window=False, new_window_title=None, xterm_prg_name='x-terminal-emulator'): """ Args: new_window: If `True`, a new terminal emulator is created, enabling the decoder to use @@ -71,7 +73,10 @@ def __init__(self, new_window=False, xterm_prg_name='x-terminal-emulator'): xterm_prg_name: name of the terminal emulator program to be started """ self._new_window = new_window + self._new_window_title = new_window_title self._xterm_prg_name = xterm_prg_name + self._s = None + self.term = None def notify_data_sending(self, current_dm, data_list, timestamp, target): """ @@ -118,13 +123,16 @@ def estimate_last_data_impact_uniqueness(self): return UNIQUE def _start(self): + self._s = '' if self._new_window: - self.term = Term(name=self.__class__.__name__, xterm_prg_name=self._xterm_prg_name, + nm = self.__class__.__name__ if self._new_window_title is None else self._new_window_title + self.term = Term(name=nm, xterm_prg_name=self._xterm_prg_name, keepterm=True) self.term.start() def _stop(self): - if self._new_window: + self._s = None + if self._new_window and self.term is not None: self.term.stop() def print(self, msg): @@ -139,6 +147,13 @@ def print_nl(self, msg): else: print(msg) + def collect_data(self, s): + self._s += s + + def flush_collector(self): + self.print(self._s) + self._s = '' + def process_feedback(self, current_dm, source, timestamp, content, status): info_set = set() truncated_content = None if content is None else content[:60] diff --git a/framework/plumbing.py b/framework/plumbing.py index f7a2516..89df8b1 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -179,7 +179,7 @@ def wrapped_func(*args, **kargs): class FmkTask(threading.Thread): def __init__(self, name, func, arg, period=None, - error_func=lambda x: x, cleanup_func=lambda x: None): + error_func=lambda x: x, cleanup_func=lambda: None): threading.Thread.__init__(self) self._name = name self._func = func @@ -1556,7 +1556,7 @@ def disable_wkspace(self): def set_fuzz_delay(self, delay, do_record=False): if delay >= 0 or delay == -1: self._delay = delay - self.lg.log_fmk_info('Fuzz delay = {:.1f}s'.format(self._delay), do_record=do_record) + self.lg.log_fmk_info('Fuzz delay = {:.2f}s'.format(self._delay), do_record=do_record) return True else: self.lg.log_fmk_info('Wrong delay value!', do_record=False) @@ -1610,9 +1610,12 @@ def set_feedback_timeout(self, timeout, tg_id=None, do_record=False, do_show=Tru # This case occurs in self._do_sending_and_logging_init() # if the Target has not defined a feedback_timeout (like the EmptyTarget) if tg_id is None: + for tg in self.targets.values(): + tg.set_feedback_timeout(None) self._recompute_health_check_timeout(timeout, max_sending_delay, do_show=do_show) else: tg = self.targets[tg_id] + tg.set_feedback_timeout(None) self._recompute_health_check_timeout(timeout, tg.sending_delay, target=tg, do_show=do_show) elif timeout >= 0: @@ -1756,8 +1759,10 @@ def _do_sending_and_logging_init(self, data_list): blocked_data = list(filter(lambda x: x.is_blocked(), data_list)) data_list = list(filter(lambda x: not x.is_blocked(), data_list)) - user_interrupt, go_on = self._collect_residual_feedback(cond1=(self._burst_countdown == self._burst), - cond2=(not blocked_data)) + if self._burst_countdown == self._burst: + user_interrupt, go_on = self._collect_residual_feedback(force_mode=False) + else: + user_interrupt, go_on = False, True if blocked_data: self._handle_data_callbacks(blocked_data, hook=HOOK.after_fbk) @@ -1771,36 +1776,28 @@ def _do_sending_and_logging_init(self, data_list): raise TargetFeedbackError def collect_residual_feedback(self): - if self._collect_residual_feedback(True, True)[0]: + if self._collect_residual_feedback(force_mode=True)[0]: raise UserInterruption - def _collect_residual_feedback(self, cond1, cond2): + def _collect_residual_feedback(self, force_mode=False): # If feedback_timeout = 0 then we don't consider residual feedback. # We try to avoid unnecessary latency in this case, as well as # to avoid retrieving some feedback that could be a trigger for sending the next data # (e.g., with a NetworkTarget in server_mode + wait_for_client) targets_to_retrieve_fbk = {} - do_residual_fbk_gathering = False + do_residual_tg_fbk_gathering = False for tg_id, tg in self.targets.items(): - cond = True if tg.feedback_timeout is None else tg.feedback_timeout > 0 + cond = True if tg.feedback_timeout is None or force_mode else tg.feedback_timeout > 0 if cond: - do_residual_fbk_gathering = True + do_residual_tg_fbk_gathering = True targets_to_retrieve_fbk[tg_id] = tg go_on = True fbk_timeout = {} user_interrupt = False - if cond1 and do_residual_fbk_gathering: + if do_residual_tg_fbk_gathering: # log residual just before sending new data to avoid # polluting feedback logs of the next emission - if cond2: - for tg in targets_to_retrieve_fbk.values(): - fbk_timeout[tg] = tg.feedback_timeout - # we change feedback timeout as the targets could use it to determine if they are - # ready to accept new data (check_target_readiness). For instance, the NetworkTarget - # launch a thread when collect_feedback_without_sending() is called for a duration - # of 'feedback_timeout'. - self.set_feedback_timeout(0, do_show=False) collected = False for tg in targets_to_retrieve_fbk.values(): @@ -1815,10 +1812,6 @@ def _collect_residual_feedback(self, cond1, cond2): go_on = self.log_target_residual_feedback() - if cond2: - for tg_id, tg in targets_to_retrieve_fbk.items(): - self.set_feedback_timeout(fbk_timeout[tg], tg_id=tg_id, do_show=False) - for tg in targets_to_retrieve_fbk.values(): tg.cleanup() @@ -1988,7 +1981,11 @@ def _handle_data_callbacks(self, data_list, hook, resolve_dataprocess=True): final_data_tg_ids = self._vtg_to_tg(data) for idx, obj in op[CallBackOps.Add_PeriodicData].items(): - data_desc, period = obj + periodic_obj, period = obj + data_desc = periodic_obj.data + if periodic_obj.vtg_ids_list: + final_data_tg_ids = self._vtg_to_tg(data, vtg_ids_list=periodic_obj.vtg_ids_list) + if isinstance(data_desc, DataProcess): # In this case each time we send the periodic we walk through the process # (thus, sending a new data each time) @@ -2020,16 +2017,17 @@ def _handle_data_callbacks(self, data_list, hook, resolve_dataprocess=True): return new_data_list - def _vtg_to_tg(self, data): + def _vtg_to_tg(self, data, vtg_ids_list=None): mapping = self.prj.scenario_target_mapping.get(data.scenario_dependence, None) - if data.tg_ids is None: + vtg_ids = data.tg_ids if vtg_ids_list is None else vtg_ids_list + if vtg_ids is None: tg_ids = mapping.get(None, self._tg_ids[0]) if mapping else self._tg_ids[0] tg_ids = [tg_ids] else: if mapping: - tg_ids = [mapping.get(tg_id, tg_id) for tg_id in data.tg_ids] + tg_ids = [mapping.get(tg_id, tg_id) for tg_id in vtg_ids] else: - tg_ids = data.tg_ids + tg_ids = vtg_ids valid_tg_ids = [] for i in tg_ids: diff --git a/framework/project.py b/framework/project.py index 308a015..01e163f 100644 --- a/framework/project.py +++ b/framework/project.py @@ -35,7 +35,7 @@ from framework.node import Env from framework.data_model import DataModel from framework.tactics_helpers import DataMaker -from framework.scenario import ScenarioEnv +from framework.scenario import Scenario, ScenarioEnv class Project(object): @@ -124,8 +124,14 @@ def set_monitor(self, monitor): def set_data_model(self, dm): self.dm = dm - def map_targets_to_scenario(self, scenario_name, target_mapping): - self.scenario_target_mapping[scenario_name] = target_mapping + def map_targets_to_scenario(self, scenario, target_mapping): + if isinstance(scenario, (list, tuple)): + for sc in scenario: + name = sc.name if isinstance(sc, Scenario) else sc + self.scenario_target_mapping[name] = target_mapping + else: + name = scenario.name if isinstance(scenario, Scenario) else scenario + self.scenario_target_mapping[name] = target_mapping def reset_target_mappings(self): self.scenario_target_mapping = {} diff --git a/framework/scenario.py b/framework/scenario.py index 1b9ed55..a9f6164 100644 --- a/framework/scenario.py +++ b/framework/scenario.py @@ -145,9 +145,10 @@ def __copy__(self): class Periodic(object): - def __init__(self, data, period=None): + def __init__(self, data, period=None, vtg_ids=None): self.data = data self.period = period + self.vtg_ids_list = None if vtg_ids is None else [vtg_ids] class Step(object): diff --git a/framework/tactics_helpers.py b/framework/tactics_helpers.py index 7cd06ea..258ec7d 100644 --- a/framework/tactics_helpers.py +++ b/framework/tactics_helpers.py @@ -986,7 +986,7 @@ def _callback_dispatcher_after_fbk(self, fbk): cbkops = CallBackOps() for desc in self.step.periodic_to_set: cbkops.add_operation(CallBackOps.Add_PeriodicData, id=id(desc), - param=desc.data, period=desc.period) + param=desc, period=desc.period) for periodic_id in self.step.periodic_to_clear: cbkops.add_operation(CallBackOps.Del_PeriodicData, id=periodic_id) diff --git a/framework/target_helpers.py b/framework/target_helpers.py index 7b94824..6334009 100644 --- a/framework/target_helpers.py +++ b/framework/target_helpers.py @@ -186,7 +186,7 @@ def set_feedback_timeout(self, fbk_timeout): fbk_timeout: maximum time duration for collecting the feedback ''' - assert fbk_timeout >= 0 + assert fbk_timeout is None or fbk_timeout >= 0 self.feedback_timeout = fbk_timeout self._set_feedback_timeout_specific(fbk_timeout) diff --git a/framework/targets/network.py b/framework/targets/network.py index a3843e2..f313a65 100644 --- a/framework/targets/network.py +++ b/framework/targets/network.py @@ -230,9 +230,6 @@ def set_timeout(self, fbk_timeout, sending_delay): self.set_sending_delay(sending_delay) self.set_feedback_timeout(fbk_timeout) - def _set_feedback_timeout_specific(self, fbk_timeout): - self._feedback_timeout = fbk_timeout - def initialize(self): ''' To be overloaded if some intial setup for the target is necessary. @@ -311,7 +308,7 @@ def _raw_listen_to(self, host, port, ref_id, chk_size=CHUNK_SZ, wait_time=None): if wait_time is None: - wait_time = self._feedback_timeout + wait_time = self.feedback_timeout initial_call = False if (host, port) not in self._server_sock2hp.values(): @@ -1062,7 +1059,7 @@ def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): assert from_fmk self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=pre_fbk) + pre_fbk=pre_fbk, timeout=0) return @@ -1121,21 +1118,21 @@ def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): if from_fmk: self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=pre_fbk) + pre_fbk=pre_fbk, timeout=self.feedback_timeout) else: raise TargetStuck("system not ready for sending data!") def _start_fbk_collector(self, fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=None): + pre_fbk=None, timeout=None): self._thread_cpt += 1 if from_fmk: self.feedback_thread_qty += 1 feedback_thread = threading.Thread(None, self._collect_feedback_from, name='FBK-' + repr(self._sending_id) + '#' + repr(self._thread_cpt), args=(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, - self._sending_id, self._feedback_timeout, from_fmk, + self._sending_id, timeout, from_fmk, pre_fbk)) feedback_thread.start() diff --git a/libs/utils.py b/libs/utils.py index 6f8d1be..8e3108c 100644 --- a/libs/utils.py +++ b/libs/utils.py @@ -21,6 +21,8 @@ # ################################################################################ +from __future__ import print_function + import os import sys import subprocess @@ -38,7 +40,6 @@ def __init__(self, name=None, keepterm=False, xterm_args=None, xterm_prg_name='x self.xterm_prg_name = xterm_prg_name def start(self): - self._input_desc = None self.pipe_path = os.sep + os.path.join('tmp', 'fuddly_term_'+str(uuid.uuid4())) if not os.path.exists(self.pipe_path): os.mkfifo(self.pipe_path) @@ -54,15 +55,10 @@ def start(self): def _launch_term(self): self._p = subprocess.Popen(self.cmd) - if not self._input_desc or self._input_desc.closed: - self._input_desc = open(self.pipe_path, "w") def stop(self): if not self.keepterm and self._p.poll() is None: self._p.kill() - if self._input_desc: - self._input_desc.close() - self._input_desc = None try: os.remove(self.pipe_path) except FileNotFoundError: @@ -72,10 +68,9 @@ def print(self, s, newline=False): s += '\n' if newline else '' if self._p.poll() is not None: self._launch_term() - if not self._input_desc or self._input_desc.closed: - self._input_desc = open(self.pipe_path, "w") - self._input_desc.write(s) - self._input_desc.close() + with open(self.pipe_path, "w") as input_desc: + input_desc.write(s) + def print_nl(self, s): self.print(s, newline=True) diff --git a/test/integration/test_integration.py b/test/integration/test_integration.py index 9c91e0e..3b09ea1 100644 --- a/test/integration/test_integration.py +++ b/test/integration/test_integration.py @@ -2070,8 +2070,8 @@ def nint_10_helper(blob, constraints, node_internals): # print(repr(top)) print(top.get_value()) - def verif_val_and_print(arg): - Node._print_contents(arg) + def verif_val_and_print(*arg, **kwargs): + Node._print_contents(*arg, **kwargs) if 'TBD' in arg: raise ValueError('Dissection Error!') @@ -3489,7 +3489,7 @@ def test_generic_disruptors_01(self): if d is not None: fmk._log_data(d) print("\n---[ Pretty Print ]---\n") - d.pretty_print() + d.show() fmk.cleanup_dmaker(dmaker_type=dmaker_type, reset_existing_seed=True) else: raise ValueError("\n***WARNING: the sequence {!r} returns {!r}!".format(act, d)) From 68f112848f6a6b0c7e243843c1724d85ffa8cd12 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Fri, 8 Feb 2019 12:11:19 +0100 Subject: [PATCH 03/17] Enhance MOD disruptor Add a new dictionary parameter (multi_mod) to the disruptor MOD in order to enable multiple change on the data at once. --- docs/source/disruptors.rst | 50 +++++++++++++++++------------ framework/generic_data_makers.py | 55 +++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 39 deletions(-) diff --git a/docs/source/disruptors.rst b/docs/source/disruptors.rst index 907c4d0..803bfab 100644 --- a/docs/source/disruptors.rst +++ b/docs/source/disruptors.rst @@ -317,10 +317,13 @@ MOD - Modify Node Contents -------------------------- Description: - Change the content of the nodes specified by the regexp path with - the value privided as a parameter (use *node absorption* - infrastructure). If no path is provided, the root node will be - used. + Perform modifications on the provided data. Two ways are possible: + + - Either the change is performed on the content of the nodes specified by the `path` + parameter with the new `value` provided, and the optional constraints for the + absorption (use *node absorption* infrastructure); + + - Or the changed is performed based on a dictionary provided through the parameter `multi_mod` Reference: :class:`framework.generic_data_makers.d_modify_nodes` @@ -328,23 +331,28 @@ Reference: Parameters: .. code-block:: none - parameters: - |_ path - | | desc: graph path regexp to select nodes on which the disruptor should - | | apply - | | default: None [type: str] - |_ clone_node - | | desc: if True the dmaker will always return a copy of the node. (for - | | stateless disruptors dealing with big data it can be useful - | | to it to False) - | | default: False [type: bool] - |_ value - | | desc: the new value to inject within the data - | | default: '' [type: str] - |_ constraints - | | desc: constraints for the absorption of the new value - | | default: AbsNoCsts() [type: AbsCsts] - + parameters: + |_ path + | | desc: Graph path regexp to select nodes on which the disruptor should + | | apply. + | | default: None [type: str] + |_ value + | | desc: The new value to inject within the data. + | | default: '' [type: str] + |_ constraints + | | desc: Constraints for the absorption of the new value. + | | default: AbsNoCsts() [type: AbsCsts] + |_ multi_mod + | | desc: Dictionary of : pairs to change multiple nodes with + | | diferent values. can be either only the new or + | | a tuple (,) if new constraint for absorption + | | is needed + | | default: None [type: dict] + |_ clone_node + | | desc: If True the dmaker will always return a copy of the node. (For + | | stateless disruptors dealing with big data it can be useful + | | to it to False.) + | | default: False [type: bool] NEXT - Next Node Content diff --git a/framework/generic_data_makers.py b/framework/generic_data_makers.py index fad3dbe..25f3f42 100755 --- a/framework/generic_data_makers.py +++ b/framework/generic_data_makers.py @@ -1322,18 +1322,24 @@ def _add_info(self, prev_data, n): 'the disruptor should apply.', None, str), 'value': ('The new value to inject within the data.', '', str), 'constraints': ('Constraints for the absorption of the new value.', AbsNoCsts(), AbsCsts), + 'multi_mod': ('Dictionary of : pairs to change multiple nodes with ' + 'diferent values. can be either only the new or a ' + 'tuple (,) if new constraint for absorption is ' + 'needed', None, dict), 'clone_node': ('If True the dmaker will always return a copy ' \ 'of the node. (For stateless disruptors dealing with ' \ 'big data it can be useful to it to False.)', False, bool)}) class d_modify_nodes(Disruptor): - ''' - Change the content of the nodes specified by the regexp path with - the value privided as a parameter (use *node absorption* - infrastructure). If no path is provided, the root node will be - used. + """ + Perform modifications on the provided data. Two ways are possible: - Constraints can also be provided for absorption of the new value. - ''' + - Either the change is performed on the content of the nodes specified by the `path` + parameter with the new `value` provided, and the optional constraints for the + absorption (use *node absorption* infrastructure); + + - Or the changed is performed based on a dictionary provided through the parameter `multi_mod` + + """ def setup(self, dm, user_input): return True @@ -1343,18 +1349,31 @@ def disrupt_data(self, dm, target, prev_data): prev_data.add_info('INVALID INPUT') return prev_data - if self.path: - l = prev_content.get_reachable_nodes(path_regexp=self.path) - if not l: - prev_data.add_info('INVALID INPUT') - return prev_data - - for n in l: - status, off, size, name = n.absorb(self.value, constraints=self.constraints) - self._add_info(prev_data, n, status, size) + if self.multi_mod: + change_dict = self.multi_mod else: - status, off, size, name = prev_content.absorb(self.value, constraints=self.constraints) - self._add_info(prev_data, prev_content, status, size) + change_dict = {self.path: (self.value, self.constraints)} + + for path, item in change_dict.items(): + if isinstance(item, (tuple, list)): + assert len(item) == 2 + new_value, new_csts = item + else: + new_value = item + new_csts = AbsNoCsts() + + if path: + l = prev_content.get_reachable_nodes(path_regexp=path) + if not l: + prev_data.add_info('INVALID INPUT') + return prev_data + + for n in l: + status, off, size, name = n.absorb(new_value, constraints=new_csts) + self._add_info(prev_data, n, status, size) + else: + status, off, size, name = prev_content.absorb(new_value, constraints=new_csts) + self._add_info(prev_data, prev_content, status, size) prev_content.freeze() From 04b211d9996eb3d713e230c5792ff0639d43dd88 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Fri, 8 Feb 2019 12:11:58 +0100 Subject: [PATCH 04/17] Use functools.wraps in EnforceOrder decorator --- framework/plumbing.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/plumbing.py b/framework/plumbing.py index 89df8b1..9b268ce 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -38,6 +38,8 @@ import time import signal +from functools import wraps + from framework.database import FeedbackGate from framework.knowledge.feedback_collector import FeedbackSource from framework.error_handling import * @@ -146,8 +148,9 @@ class EnforceOrder(object): current_state = None - def __init__(self, accepted_states=[], final_state=None, + def __init__(self, accepted_states=None, final_state=None, initial_func=False, always_callable=False, transition=None): + accepted_states = [] if accepted_states is None else accepted_states if initial_func: self.accepted_states = accepted_states + [None] else: @@ -157,7 +160,7 @@ def __init__(self, accepted_states=[], final_state=None, self.transition = transition def __call__(self, func): - + @wraps(func) def wrapped_func(*args, **kargs): if not self.always_callable and EnforceOrder.current_state not in self.accepted_states: print(colorize("[INVALID CALL] function '%s' cannot be called in the state '%r'!" \ From 50ad773d032b152b6dcd422cbaad0f07fc8b1f08 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Tue, 19 Feb 2019 13:24:30 +0100 Subject: [PATCH 05/17] New disruptor CALL + NetworkTarget fix/improvements - Add new disruptor CALL to call a function on input data - Fix NetworkTarget.is_target_ready_for_new_data() way of working - Feedback retrieval in NetworkTarget always walk now through all interfaces, even if no data are sent to them. - Improve handling of feedback retrieval in plumbing.py, especially for flushing feedback --- docs/source/disruptors.rst | 30 +++++ framework/generic_data_makers.py | 37 ++++++ framework/plumbing.py | 68 +++++------ framework/scenario.py | 6 +- framework/target_helpers.py | 11 +- framework/targets/network.py | 194 ++++++++++++++----------------- 6 files changed, 198 insertions(+), 148 deletions(-) diff --git a/docs/source/disruptors.rst b/docs/source/disruptors.rst index 803bfab..bb20845 100644 --- a/docs/source/disruptors.rst +++ b/docs/source/disruptors.rst @@ -355,6 +355,36 @@ Parameters: | | default: False [type: bool] +CALL - Call Function +-------------------- + +Description: + Call the function provided with the first parameter being the :class:`framework.data.Data` + object received as input of this disruptor, and optionally with additional parameters + if `params` is set. The function should return a :class:`framework.data.Data` object. + + The signature of the function should be compatible with: + + ``func(data, *args) --> Data()`` + +Reference: + :class:`framework.generic_data_makers.d_modify_nodes` + +Parameters: + .. code-block:: none + + parameters: + |_ func + | | desc: The function that will be called with a node as its first parameter, + | | and provided optionnaly with addtionnal parameters if @params + | | is set. + | | default: lambda x: x [type: method, function] + |_ params + | | desc: Tuple of parameters that will be provided to the function. + | | default: None [type: tuple] + + + NEXT - Next Node Content ------------------------ diff --git a/framework/generic_data_makers.py b/framework/generic_data_makers.py index 25f3f42..1e472f1 100755 --- a/framework/generic_data_makers.py +++ b/framework/generic_data_makers.py @@ -1399,6 +1399,43 @@ def _add_info(self, prev_data, n, status, size): prev_data.add_info("remaining: '{!s}'".format(remaining)) +@disruptor(tactics, dtype="CALL", weight=4, + args={'func': ('The function that will be called with a node as its first parameter, ' + 'and provided optionnaly with addtionnal parameters if @params is set.', + lambda x:x, + (types.MethodType, types.FunctionType)), # python3, python2 + 'params': ('Tuple of parameters that will be provided to the function.', + None, tuple) }) +class d_call_function(Disruptor): + """ + Call the function provided with the first parameter being the Data() object received as + input of this disruptor, and optionally with additional parameters if @params is set. The + function should return a Data() object. + + The signature of the function should be compatible with: + + `func(data, *args) --> Data()` + + """ + + def disrupt_data(self, dm, target, prev_data): + try: + if self.params: + new_data = self.func(prev_data, *self.params) + else: + new_data = self.func(prev_data) + except: + new_data = prev_data + new_data.add_info("an error occurred while executing the user function '{!r}'".format(self.func)) + else: + new_data.add_info("called function: {!r}".format(self.func)) + if self.params: + new_data.add_info("additional parameters provided: {:s}" + .format(', '.join((str(x) for x in self.params)))) + + return new_data + + @disruptor(tactics, dtype="COPY", weight=4, args=None) class d_shallow_copy(Disruptor): diff --git a/framework/plumbing.py b/framework/plumbing.py index 9b268ce..2cf54eb 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -1778,11 +1778,11 @@ def _do_sending_and_logging_init(self, data_list): else: raise TargetFeedbackError - def collect_residual_feedback(self): - if self._collect_residual_feedback(force_mode=True)[0]: + def collect_residual_feedback(self, timeout=0): + if self._collect_residual_feedback(force_mode=True, timeout=timeout)[0]: raise UserInterruption - def _collect_residual_feedback(self, force_mode=False): + def _collect_residual_feedback(self, force_mode=False, timeout=0): # If feedback_timeout = 0 then we don't consider residual feedback. # We try to avoid unnecessary latency in this case, as well as # to avoid retrieving some feedback that could be a trigger for sending the next data @@ -1804,13 +1804,14 @@ def _collect_residual_feedback(self, force_mode=False): collected = False for tg in targets_to_retrieve_fbk.values(): - if tg.collect_feedback_without_sending(): + if tg.collect_feedback_without_sending(timeout=timeout): collected = True if collected: # We have to make sure the targets are ready for sending data after # collecting feedback. - ret = self.check_target_readiness() + ftimeout = None if timeout == 0 else timeout + 0.1 + ret = self.check_target_readiness(forced_timeout=ftimeout) user_interrupt = ret == -2 go_on = self.log_target_residual_feedback() @@ -2035,10 +2036,16 @@ def _vtg_to_tg(self, data, vtg_ids_list=None): valid_tg_ids = [] for i in tg_ids: if i not in self._tg_ids: - self.set_error("WARNING: An access attempt occurs on a disabled target: '({:d}) {!s}' " - "It will be redirected to the first enabled target." - .format(i, self.get_available_targets()[i]), - code=Error.FmkWarning) + try: + requested_tg_id = self.get_available_targets()[i] + except IndexError: + self.set_error("WARNING: The provided target number '{:d}' does not exist ".format(i), + code=Error.FmkWarning) + else: + self.set_error("WARNING: An access attempt occurs on a disabled target: '({:d}) {!s}' " + "It will be redirected to the first enabled target." + .format(i, requested_tg_id), + code=Error.FmkWarning) i = self._tg_ids[0] if self._debug_mode: raise ValueError('Access attempt occurs on a disabled target') @@ -2050,7 +2057,7 @@ def _send_periodic(self, tg_ids, data_desc): data = self._handle_data_desc(data_desc) if data is not None: for tg in [self.targets[tg_id] for tg_id in tg_ids]: - tg.send_data_sync(data) + tg.send_data_sync(data, from_fmk=False) else: self.set_error(msg="Data descriptor handling returned 'None'!", code=Error.UserCodeError) raise DataProcessTermination @@ -2397,22 +2404,24 @@ def _setup_new_sending(self): self._current_sent_date = self.lg.start_new_log_entry(preamble=p) @EnforceOrder(accepted_states=['S2']) - def log_target_feedback(self): + def log_target_feedback(self, residual=False): collected_err, err_detected2 = None, False ok = True if self.__tg_enabled: - if self._burst > 1: - p = "::[ END BURST ]::\n" + if residual: + p = "*** RESIDUAL TARGET FEEDBACK ***" + e = "********************************" else: - p = None + p = "::[ END BURST ]::\n" if self._burst > 1 else None + e = None try: - collected_err = self.lg.log_collected_feedback(preamble=p) + collected_err = self.lg.log_collected_feedback(preamble=p, epilogue=e) except NotImplementedError: pass for tg in self.targets.values(): err_detected1 = collected_err.get(tg, False) if collected_err else False - err_detected2 = self._log_directly_retrieved_target_feedback(tg=tg, preamble=p) + err_detected2 = self._log_directly_retrieved_target_feedback(tg=tg, preamble=p, epilogue=e) go_on = self._recover_target(tg) if err_detected1 or err_detected2 else True if not go_on: ok = False @@ -2421,25 +2430,7 @@ def log_target_feedback(self): @EnforceOrder(accepted_states=['S2']) def log_target_residual_feedback(self): - collected_err, err_detected2 = None, False - ok = True - if self.__tg_enabled: - p = "*** RESIDUAL TARGET FEEDBACK ***" - e = "********************************" - try: - collected_err = self.lg.log_collected_feedback(preamble=p, epilogue=e) - except NotImplementedError: - pass - - for tg in self.targets.values(): - err_detected1 = collected_err.get(tg, False) if collected_err else False - err_detected2 = self._log_directly_retrieved_target_feedback(tg=tg, - preamble=p, epilogue=e) - go_on = self._recover_target(tg) if err_detected1 or err_detected2 else True - if not go_on: - ok = False - - return ok + return self.log_target_feedback(residual=True) def _log_directly_retrieved_target_feedback(self, tg, preamble=None, epilogue=None): """ @@ -2479,7 +2470,7 @@ def _log_directly_retrieved_target_feedback(self, tg, preamble=None, epilogue=No return err_detected @EnforceOrder(accepted_states=['S2']) - def check_target_readiness(self): + def check_target_readiness(self, forced_timeout=None): if self.__tg_enabled: t0 = datetime.datetime.now() @@ -2487,6 +2478,7 @@ def check_target_readiness(self): signal.signal(signal.SIGINT, sig_int_handler) ret = 0 tg = None + hc_timeout = self._hc_timeout_max if forced_timeout is None else forced_timeout # Wait until the target is ready or timeout expired try: @@ -2494,7 +2486,7 @@ def check_target_readiness(self): while not tg.is_target_ready_for_new_data(): time.sleep(0.005) now = datetime.datetime.now() - if (now - t0).total_seconds() > self._hc_timeout_max: + if (now - t0).total_seconds() > hc_timeout: self.lg.log_target_feedback_from( source=FeedbackSource(self), content='*** Timeout! The target {!s} does not seem to be ready.' @@ -4647,7 +4639,7 @@ def do_flush_feedback(self, line): ''' Collect the residual feedback (received by the target and the probes) ''' - self.fz.collect_residual_feedback() + self.fz.collect_residual_feedback(timeout=3) return False diff --git a/framework/scenario.py b/framework/scenario.py index a9f6164..c9c24ff 100644 --- a/framework/scenario.py +++ b/framework/scenario.py @@ -922,7 +922,11 @@ def graph_creation(init_step, node_list, edge_list): else: graph_creation(self._anchor, node_list=[], edge_list=[]) - rendered = f.render() + try: + rendered = f.render() + except: + print("\n*** The renderer has stopped! (because of an unexpected event) ***") + return if graph_filename != viewer_filename or viewer_app is None or viewer_app.poll() is not None: viewer_filename = graph_filename diff --git a/framework/target_helpers.py b/framework/target_helpers.py index 6334009..fe76cd3 100644 --- a/framework/target_helpers.py +++ b/framework/target_helpers.py @@ -168,11 +168,14 @@ def get_feedback(self): ''' return None - def collect_feedback_without_sending(self): + def collect_feedback_without_sending(self, timeout=0): """ If overloaded, it can be used by the framework to retrieve additional feedback from the target without sending any new data. + Args: + timeout: Maximum delay before returning from feedback collecting + Returns: bool: False if it is not possible, otherwise it should be True """ @@ -257,7 +260,8 @@ def send_data_sync(self, data, from_fmk=False): emits the message by itself. ''' with self._send_data_lock: - self._altered_data_queued = data.altered + if data is not None: + self._altered_data_queued = data.altered self.send_data(data, from_fmk=from_fmk) def send_multiple_data_sync(self, data_list, from_fmk=False): @@ -266,7 +270,8 @@ def send_multiple_data_sync(self, data_list, from_fmk=False): with the framework. ''' with self._send_data_lock: - self._altered_data_queued = data_list[0].altered + if data_list is not None: + self._altered_data_queued = data_list[0].altered self.send_multiple_data(data_list, from_fmk=from_fmk) def add_probe(self, probe): diff --git a/framework/targets/network.py b/framework/targets/network.py index f313a65..d81ac1a 100644 --- a/framework/targets/network.py +++ b/framework/targets/network.py @@ -146,11 +146,12 @@ def get_mac_addr(ifname): self._host = {} self._port = {} self._socket_type = {} - self.host = self._host[self.UNKNOWN_SEMANTIC] = self._host[data_semantics] = host - self.port = self._port[self.UNKNOWN_SEMANTIC] = self._port[data_semantics] = port - self._socket_type[self.UNKNOWN_SEMANTIC] = self._socket_type[data_semantics] = socket_type + self.host = self._host[data_semantics] = host + self.port = self._port[data_semantics] = port + self._socket_type[data_semantics] = socket_type self.known_semantics = {data_semantics} + self._semantics_to_intf = {data_semantics: (host, port, socket_type, server_mode)} self.sending_sockets = [] self.multiple_destination = False @@ -177,6 +178,7 @@ def get_mac_addr(ifname): self.stop_event = threading.Event() self._server_thread_lock = threading.Lock() + self._network_send_lock = threading.Lock() self._raw_server_private = None def _is_valid_socket_type(self, socket_type): @@ -205,6 +207,7 @@ def register_new_interface(self, host, port, socket_type, data_semantics, server self._socket_type[data_semantics] = socket_type assert data_semantics not in self.known_semantics self.known_semantics.add(data_semantics) + self._semantics_to_intf[data_semantics] = (host, port, socket_type, server_mode) self.server_mode[(host,port)] = server_mode self._server_mode_additional_info[(host, port)] = (target_address, wait_for_client, keep_first_client) self._default_fbk_id[(host, port)] = self._default_fbk_socket_id + ' - {:s}:{:d}'.format(host, port) @@ -284,12 +287,6 @@ def _feedback_handling(self, fbk, ref): return fbk, 0 def cleanup(self): - # print('\n***DBG:', self.feedback_complete_cpt, self.feedback_thread_qty, - # 'sending_id=', self._sending_id) - - self.feedback_thread_qty = 0 - self.feedback_complete_cpt = 0 - return True def listen_to(self, host, port, ref_id, @@ -438,17 +435,16 @@ def start(self): self._additional_fbk_ids = {} self._additional_fbk_lengths = {} self._dynamic_interfaces = {} - self._feedback_handled = True - self.feedback_thread_qty = 0 - self.feedback_complete_cpt = 0 - self._sending_id = 0 - self._initial_sending_id = -1 + self._feedback_thread_qty = 0 + self._fbk_collector_finished_cpt = 0 + self._fbk_collector_to_launch_cpt = 0 self._first_send_data_call = True - self._thread_cpt = 0 self._last_ack_date = None # Note that `self._last_ack_date` # could be updated many times if # self.send_multiple_data() is # used. + self._flush_feedback_delay = None + self._connect_to_additional_feedback_sockets() for k, mac_src in self._mac_src.items(): @@ -489,29 +485,7 @@ def stop(self): return self.terminate() def send_data(self, data, from_fmk=False): - assert data is not None - self._before_sending_data(data, from_fmk) - host, port, socket_type, server_mode = self._get_net_info_from(data) - - connected_client_event = None - if server_mode: - connected_client_event = threading.Event() - self._listen_to_target(host, port, socket_type, - self._handle_target_connection, - args=(data, host, port, connected_client_event, from_fmk)) - connected_client_event.wait(self.sending_delay) - if socket_type[1] == socket.SOCK_STREAM and not connected_client_event.is_set(): - err_msg = ">>> WARNING: unable to send data because the target did not connect" \ - " to us [{:s}:{:d}] <<<".format(host, port) - self._feedback.add_fbk_from(self._INTERNALS_ID, err_msg, status=-2) - else: - s = self._connect_to_target(host, port, socket_type) - if s is None: - err_msg = '>>> WARNING: unable to send data to {:s}:{:d} <<<'.format(host, port) - self._feedback.add_fbk_from(self._INTERNALS_ID, err_msg, status=-1) - else: - self._send_data([s], {s:(data, host, port, None)}, self._sending_id, from_fmk) - + self.send_multiple_data(data_list=[data], from_fmk=from_fmk) def send_multiple_data(self, data_list, from_fmk=False): self._before_sending_data(data_list, from_fmk) @@ -522,26 +496,25 @@ def send_multiple_data(self, data_list, from_fmk=False): sending_list = [] if data_list is None: + fbk_timeout = self.feedback_timeout if self._flush_feedback_delay is None else self._flush_feedback_delay # If data_list is None, it means that we want to collect feedback from every interface # without sending data. for key in self.known_semantics: - host = self._host[key] - port = self._port[key] - socket_type = self._socket_type[key] - server_mode = self.server_mode[(host, port)] - sending_list.append((None, host, port, socket_type, server_mode)) - # if self.hold_connection[(host, port)]: - # # Collecting feedback makes sense only if we keep the socket (thus, 'hold_connection' - # # has to be True) or if a data callback wait for feedback. - # sending_list.append((None, host, port, socket_type, server_mode)) + sending_list.append((None,) + self._semantics_to_intf[key]) else: + fbk_timeout = self.feedback_timeout + data_to_send = {intf: None for intf in self._semantics_to_intf.values()} for data in data_list: - host, port, socket_type, server_mode = self._get_net_info_from(data) - d = data.to_bytes() - sending_list.append((d, host, port, socket_type, server_mode)) + intf = self._get_net_info_from(data) + data_to_send[intf] = data.to_bytes() + for intf, data in data_to_send.items(): + sending_list.append((data,)+intf) + self._fbk_collector_to_launch_cpt = 0 for data, host, port, socket_type, server_mode in sending_list: if server_mode: + if from_fmk: + self._fbk_collector_to_launch_cpt += 1 connected_client_event[(host, port)] = threading.Event() self._listen_to_target(host, port, socket_type, self._handle_target_connection, @@ -558,7 +531,10 @@ def send_multiple_data(self, data_list, from_fmk=False): data_refs[s] = (data, host, port, None) if data_refs: - self._send_data(sockets, data_refs, self._sending_id, from_fmk) + if from_fmk: + self._fbk_collector_to_launch_cpt += 1 + with self._network_send_lock: + self._send_data(sockets, data_refs, fbk_timeout, from_fmk) else: # this case exist when data are only sent through 'server_mode'-configured interfaces # (because self._send_data() is called through self._handle_target_connection()) @@ -856,18 +832,22 @@ def _handle_connection_to_fbk_server(self, clientsocket, address, args, pre_fbk= self._additional_fbk_lengths[clientsocket] = fbk_length def _handle_target_connection(self, clientsocket, address, args, pre_fbk=None): - data, host, port, connected_client_event, from_fmk = args - if self.hold_connection[(host, port)]: - with self._server_thread_lock: - self._last_client_hp2sock[(host, port)] = (clientsocket, address) - self._last_client_sock2hp[clientsocket] = (host, port) - connected_client_event.set() - self._send_data([clientsocket], {clientsocket:(data, host, port, address)}, self._sending_id, - from_fmk=from_fmk, pre_fbk={clientsocket: pre_fbk}) + # can be called simultaneously by two different threads _raw_server_main() and _server_main() + # in the context of .send_multiple_data() with various interfaces. + with self._network_send_lock: + data, host, port, connected_client_event, from_fmk = args + if self.hold_connection[(host, port)]: + with self._server_thread_lock: + self._last_client_hp2sock[(host, port)] = (clientsocket, address) + self._last_client_sock2hp[clientsocket] = (host, port) + connected_client_event.set() + self._send_data([clientsocket], {clientsocket:(data, host, port, address)}, + fbk_timeout=self.feedback_timeout, from_fmk=from_fmk, + pre_fbk={clientsocket: pre_fbk}) - def _collect_feedback_from(self, fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, - send_id, fbk_timeout, from_fmk, pre_fbk): + def _collect_feedback_from(self, thread_id, fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, + fbk_timeout, flush_received_fbk, pre_fbk): def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): # print('\n*** NOTE: Remove obsolete socket {!r}'.format(socket)) @@ -904,6 +884,8 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): del self._additional_fbk_ids[skt] del self._additional_fbk_lengths[skt] + # print('\n*** DBG: start - collect_thread {:d}'.format(thread_id)) + chunks = collections.OrderedDict() t0 = datetime.datetime.now() duration = 0 @@ -923,7 +905,7 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): while dont_stop: ready_to_read = [] - for fd, ev in epobj.poll(timeout=0.05): + for fd, ev in epobj.poll(timeout=0.001): skt = fileno2fd[fd] if ev != select.EPOLLIN: _check_and_handle_obsolete_socket(skt, error=ev, error_list=socket_errors) @@ -934,6 +916,11 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): now = datetime.datetime.now() duration = (now - t0).total_seconds() + + if flush_received_fbk: + if not ready_to_read or duration > fbk_timeout: + break + if ready_to_read: if first_pass: first_pass = False @@ -951,6 +938,7 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): chunk = s.recv(sz) except socket.timeout: chunk = b'' + print('\n*** Socket timeout') socket_timed_out = True # for UDP we keep the socket break except socket.error as serr: @@ -979,7 +967,10 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): has_read = True - if fbk_sockets: + if flush_received_fbk: + dont_stop = True + + elif fbk_sockets: for s in fbk_sockets: if s in ready_to_read: s_fbk_len = fbk_lengths[s] @@ -992,7 +983,8 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): else: dont_stop = False - if duration > fbk_timeout or (has_read and not self.fbk_wait_full_time_slot_mode): + if duration > fbk_timeout or \ + (has_read and not self.fbk_wait_full_time_slot_mode): dont_stop = False else: @@ -1014,16 +1006,16 @@ def _check_and_handle_obsolete_socket(skt, error=None, error_list=None): for fbkid, ev in socket_errors: self._feedback_collect(">>> ERROR[{:d}]: unable to interact with '{:s}' " "<<<".format(ev,fbkid), fbkid, error=-ev) - if from_fmk: - self._feedback_complete(send_id) + self._feedback_complete() - return + # print('\n*** DBG: stop - collect_thread {:d}'.format(thread_id)) + return - def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): - if sid != self._initial_sending_id: - self._initial_sending_id = sid - # self._first_send_data_call = True + def _send_data(self, sockets, data_refs, fbk_timeout, from_fmk, pre_fbk=None): + # Should be called with the lock self_network_send_lock. + # Especially needed in the context of self.send_multiple_data() as different threads can reach + # this code simultaneously (_raw_server_main, _server_main and the main framework thread). epobj = select.epoll() fileno2fd = {} @@ -1058,16 +1050,15 @@ def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): fbk_lengths[s] = self.feedback_length assert from_fmk - self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=pre_fbk, timeout=0) + self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, + pre_fbk=pre_fbk, timeout=fbk_timeout, flush_received_fbk=True) return - ready_to_read, ready_to_write, in_error = select.select([], sockets, [], 1) + ready_to_read, ready_to_write, in_error = select.select([], sockets, [], self.sending_delay) if ready_to_write: for s in ready_to_write: - add_main_socket = True data, host, port, address = data_refs[s] epobj.register(s, select.EPOLLIN) fileno2fd[s.fileno()] = s @@ -1094,7 +1085,6 @@ def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): status=-1) break else: - # add_main_socket = False raise TargetStuck("system not ready for sending data! {!r}".format(serr)) else: if sent == 0: @@ -1111,28 +1101,27 @@ def _send_data(self, sockets, data_refs, sid, from_fmk, pre_fbk=None): # else: # assert(self._default_fbk_id[(host, port)] not in fbk_ids.values()) - if add_main_socket: - fbk_sockets.append(s) - fbk_ids[s] = self._default_fbk_id[(host, port)] - fbk_lengths[s] = self.feedback_length + fbk_sockets.append(s) + fbk_ids[s] = self._default_fbk_id[(host, port)] + fbk_lengths[s] = self.feedback_length if from_fmk: - self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=pre_fbk, timeout=self.feedback_timeout) + self._start_fbk_collector(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, + pre_fbk=pre_fbk, timeout=fbk_timeout) else: raise TargetStuck("system not ready for sending data!") - def _start_fbk_collector(self, fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, from_fmk, - pre_fbk=None, timeout=None): - self._thread_cpt += 1 - if from_fmk: - self.feedback_thread_qty += 1 + def _start_fbk_collector(self, fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, + pre_fbk=None, timeout=None, flush_received_fbk=False): + + self._feedback_thread_qty += 1 feedback_thread = threading.Thread(None, self._collect_feedback_from, - name='FBK-' + repr(self._sending_id) + '#' + repr(self._thread_cpt), - args=(fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, - self._sending_id, timeout, from_fmk, + name='FBK-#' + repr(self._feedback_thread_qty), + args=(self._feedback_thread_qty, + fbk_sockets, fbk_ids, fbk_lengths, epobj, fileno2fd, + timeout, flush_received_fbk, pre_fbk)) feedback_thread.start() @@ -1141,22 +1130,17 @@ def _feedback_collect(self, fbk, ref, error=0): self._feedback.set_error_code(error) self._feedback.add_fbk_from(ref, fbk, status=error) - def _feedback_complete(self, sid): - # print('\n***DBG1:', self.feedback_complete_cpt, self.feedback_thread_qty, - # 'sending_id=', self._sending_id, sid) - if sid == self._sending_id: - self.feedback_complete_cpt += 1 - if self.feedback_complete_cpt == self.feedback_thread_qty: - self._feedback_handled = True - # print('\n***DBG2:', self.feedback_complete_cpt, self.feedback_thread_qty) + def _feedback_complete(self): + self._fbk_collector_finished_cpt += 1 def _before_sending_data(self, data_list, from_fmk): if from_fmk: self._last_ack_date = None self._first_send_data_call = True # related to additional feedback with self._fbk_handling_lock: - self._sending_id += 1 - self._feedback_handled = False + assert self._fbk_collector_to_launch_cpt == self._fbk_collector_finished_cpt + self._fbk_collector_finished_cpt = 0 + self._fbk_collector_to_launch_cpt = 0 else: self._first_send_data_call = False # we ignore all additional feedback @@ -1188,18 +1172,16 @@ def _before_sending_data(self, data_list, from_fmk): self._custom_data_handling_before_emission(data_list) - def collect_feedback_without_sending(self): - self.send_multiple_data(None, from_fmk=True) + def collect_feedback_without_sending(self, timeout=0): + self._flush_feedback_delay = timeout + self.send_multiple_data_sync(None, from_fmk=True) return True def get_feedback(self): return self._feedback def is_target_ready_for_new_data(self): - # We answer we are ready if at least one receiver has - # terminated its job, either because the target answered to - # it, or because of the current specified timeout. - return self._feedback_handled + return self._fbk_collector_to_launch_cpt == self._fbk_collector_finished_cpt def _register_last_ack_date(self, ack_date): self._last_ack_date = ack_date From 69810ee732efc51d379299af7d60fb612ebde419 Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Fri, 22 Feb 2019 14:30:17 +0100 Subject: [PATCH 06/17] New Scenario configurability feature + various fixes - Add @user_context parameter to Scenario class, allowing to provide parameters to Steps and callbacks of the scenario (through the ScenarioEnv object shared between them). - Better handling of target recovery which can be triggered by the plumbing at some specific condition, namely: - after a data is sent, if the health check timeout then a recovery is attempted - when feedback is retrieved from the target, if an error occurred a recovery is attempted - Implement NetworkTarget.recover_target() method in order to wait for feedback collector thread to finish its job (with a configurable delay) - Fix project-level scenario visibility after DM reloading - UI class moved to global_resources.py --- framework/evolutionary_helpers.py | 1 + framework/global_resources.py | 60 +++++++++++++++++++++++++++++++ framework/plumbing.py | 22 ++++++++---- framework/scenario.py | 21 ++++++++++- framework/tactics_helpers.py | 52 --------------------------- framework/targets/network.py | 18 ++++++++-- projects/tuto_proj.py | 17 +++++++++ 7 files changed, 129 insertions(+), 62 deletions(-) diff --git a/framework/evolutionary_helpers.py b/framework/evolutionary_helpers.py index 0bb74e3..af203c0 100644 --- a/framework/evolutionary_helpers.py +++ b/framework/evolutionary_helpers.py @@ -25,6 +25,7 @@ from operator import attrgetter from framework.tactics_helpers import * +from framework.global_resources import UI from framework.scenario import * from framework.error_handling import ExtinctPopulationError, PopulationError diff --git a/framework/global_resources.py b/framework/global_resources.py index c148c09..1cb0a61 100644 --- a/framework/global_resources.py +++ b/framework/global_resources.py @@ -123,6 +123,66 @@ def is_string_compatible(val): else: return False +# Generic container for user inputs + +class UI(object): + """ + Once initialized, attributes cannot be modified + """ + def __init__(self, **kwargs): + self._inputs = {} + for k, v in kwargs.items(): + self._inputs[k] = v + + # for python2 compatibility + def __nonzero__(self): + return bool(self._inputs) + + # for python3 compatibility + def __bool__(self): + return bool(self._inputs) + + def get_inputs(self): + return self._inputs + + def is_attrs_defined(self, *names): + for n in names: + if n not in self._inputs: + return False + return True + + def set_user_inputs(self, user_inputs): + assert isinstance(user_inputs, dict) + self._inputs = user_inputs + + def check_conformity(self, valid_args): + for arg in self._inputs: + if arg not in valid_args: + return False, arg + return True, None + + def __getattr__(self, name): + if name in self._inputs: + return self._inputs[name] + else: + return None + + def __str__(self): + if self._inputs: + ui = '[' + for k, v in self._inputs.items(): + ui += "{:s}={!r},".format(k, v) + return ui[:-1]+']' + else: + return '[ ]' + + def __copy__(self): + new_ui = type(self)() + new_ui.__dict__.update(self.__dict__) + new_ui._inputs = copy.copy(self._inputs) + return new_ui + + ### Exports for Node Absorption ### class AbsorbStatus(Enum): diff --git a/framework/plumbing.py b/framework/plumbing.py index 2cf54eb..f00ed9f 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -443,7 +443,7 @@ def reload_dm(self): return self._reload_dm() @EnforceOrder(always_callable=True, transition=['25_load_dm','S1']) - def _reload_dm(self, dm_name=None): + def _reload_dm(self, dm_name=None, ignore_prj_scenarios=False): if dm_name is None: prefix = self.__dm_rld_args_dict[self.dm][0] name = self.__dm_rld_args_dict[self.dm][1] @@ -474,7 +474,7 @@ def _reload_dm(self, dm_name=None): name_list.append(dm_name) self.dm = dm_obj - ok = self._reload_dm() + ok = self._reload_dm(ignore_prj_scenarios=True) dm_list[idx] = self.dm if not ok: @@ -520,6 +520,9 @@ def _reload_dm(self, dm_name=None): self.mon.set_data_model(self.dm) self._fmkDB_insert_dm_and_dmakers(self.dm.name, dm_params['tactics']) + if not ignore_prj_scenarios and self.prj.project_scenarios: + self.__st_dict[self.dm].register_scenarios(*self.prj.project_scenarios) + return True def _cleanup_dm_attrs_from_fmk(self): @@ -1301,8 +1304,6 @@ def _init_fmk_internals_step1(self, prj, dm): self._tactics.clear_disruptor_clones() self._tactics = self.__st_dict[dm] - if self.prj.project_scenarios: - self._tactics.register_scenarios(*self.prj.project_scenarios) self.mon = self.__monitor_dict[prj] self.mon.set_targets(self.targets) @@ -1429,6 +1430,9 @@ def load_multiple_data_model(self, dm_list=None, name_list=None, reload_dm=False self.dm = new_dm self.prj.set_data_model(self.dm) + if self.prj.project_scenarios: + self.__st_dict[new_dm].register_scenarios(*self.prj.project_scenarios) + for tg in self.targets.values(): tg.set_data_model(self.dm) if self.mon: @@ -1742,6 +1746,7 @@ def _do_before_sending_data(self, data_list): return data_list def _do_after_sending_data(self, data_list): + self._recovered_tgs = None self._handle_data_callbacks(data_list, hook=HOOK.after_sending) self.prj.notify_data_sending(data_list, self._current_sent_date, self.targets) @@ -1795,7 +1800,8 @@ def _collect_residual_feedback(self, force_mode=False, timeout=0): do_residual_tg_fbk_gathering = True targets_to_retrieve_fbk[tg_id] = tg - go_on = True + tg_ready = True + log_no_error = True fbk_timeout = {} user_interrupt = False if do_residual_tg_fbk_gathering: @@ -1805,6 +1811,7 @@ def _collect_residual_feedback(self, force_mode=False, timeout=0): collected = False for tg in targets_to_retrieve_fbk.values(): if tg.collect_feedback_without_sending(timeout=timeout): + self._recovered_tgs = None collected = True if collected: @@ -1813,14 +1820,17 @@ def _collect_residual_feedback(self, force_mode=False, timeout=0): ftimeout = None if timeout == 0 else timeout + 0.1 ret = self.check_target_readiness(forced_timeout=ftimeout) user_interrupt = ret == -2 + tg_ready = ret >= 0 - go_on = self.log_target_residual_feedback() + log_no_error = self.log_target_residual_feedback() for tg in targets_to_retrieve_fbk.values(): tg.cleanup() self.monitor_probes(prefix='Probe Status Before Sending Data') + go_on = tg_ready and log_no_error + return user_interrupt, go_on diff --git a/framework/scenario.py b/framework/scenario.py index c9c24ff..1e8134f 100644 --- a/framework/scenario.py +++ b/framework/scenario.py @@ -649,6 +649,7 @@ def __init__(self): self._dm = None self._target = None self._scenario = None + self._context = None # self._knowledge_source = None @property @@ -675,6 +676,14 @@ def scenario(self): def scenario(self, val): self._scenario = val + @property + def user_context(self): + return self._context + + @user_context.setter + def user_context(self, val): + self._context = val + # @property # def knowledge_source(self): # return self._knowledge_source @@ -688,6 +697,7 @@ def __copy__(self): new_env.__dict__.update(self.__dict__) new_env._target = None new_env._scenario = None + new_env._context = copy.copy(self._context) # new_env._knowledge_source = None return new_env @@ -700,7 +710,7 @@ def __copy__(self): class Scenario(object): - def __init__(self, name, anchor=None, reinit_anchor=None): + def __init__(self, name, anchor=None, reinit_anchor=None, user_context=None): self.name = name self._steps = None self._reinit_steps = None @@ -709,6 +719,7 @@ def __init__(self, name, anchor=None, reinit_anchor=None): self._dm = None self._env = ScenarioEnv() self._env.scenario = self + self._env.user_context = user_context self._periodic_ids = set() self._current = None self._anchor = None @@ -721,9 +732,17 @@ def __init__(self, name, anchor=None, reinit_anchor=None): def __str__(self): return "Scenario '{:s}'".format(self.name) + def clone(self, new_name): + new_sc = copy.copy(self) + new_sc.name = new_name + return new_sc + def reset(self): self._current = self._anchor + def set_user_context(self, user_context): + self._env.user_context = user_context + def set_data_model(self, dm): self._dm = dm self._env.dm = dm diff --git a/framework/tactics_helpers.py b/framework/tactics_helpers.py index 258ec7d..827d384 100644 --- a/framework/tactics_helpers.py +++ b/framework/tactics_helpers.py @@ -386,58 +386,6 @@ def print_generator(self, dmaker_type, generator_name): ) -class UI(object): - ''' - Once initialized, attributes cannot be modified - ''' - def __init__(self, **kwargs): - self._inputs = {} - for k, v in kwargs.items(): - self._inputs[k] = v - - # for python2 compatibility - def __nonzero__(self): - return bool(self._inputs) - - # for python3 compatibility - def __bool__(self): - return bool(self._inputs) - - def get_inputs(self): - return self._inputs - - def is_attrs_defined(self, *names): - for n in names: - if n not in self._inputs: - return False - return True - - def set_user_inputs(self, user_inputs): - assert isinstance(user_inputs, dict) - self._inputs = user_inputs - - def check_conformity(self, valid_args): - for arg in self._inputs: - if arg not in valid_args: - return False, arg - return True, None - - def __getattr__(self, name): - if name in self._inputs: - return self._inputs[name] - else: - return None - - def __str__(self): - if self._inputs: - ui = '[' - for k, v in self._inputs.items(): - ui += "{:s}={!r},".format(k, v) - return ui[:-1]+']' - else: - return '[ ]' - - def _user_input_conformity(self, user_input, _args_desc): if not user_input: return True diff --git a/framework/targets/network.py b/framework/targets/network.py index d81ac1a..065ff08 100644 --- a/framework/targets/network.py +++ b/framework/targets/network.py @@ -58,7 +58,7 @@ class NetworkTarget(Target): def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, socket.SOCK_STREAM), data_semantics=UNKNOWN_SEMANTIC, server_mode=False, target_address=None, wait_for_client=True, hold_connection=False, keep_first_client=True, - mac_src=None, mac_dst=None): + mac_src=None, mac_dst=None, recover_timeout=0.5): """ Args: host (str): IP address of the target to connect to, or @@ -104,6 +104,9 @@ def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, so mac_dst (bytes): Only in conjunction with raw socket. For each data sent through this interface, and if this data contain nodes with the semantic ``'mac_dst'``, these nodes will be overwritten (through absorption) with this parameter. + recover_timeout (int): Allowed delay for recovering the target. (the recovering can be triggered + by the framework if the feedback threads did not terminate before the target health check) + Impact the behavior of self.recover_target() """ Target.__init__(self) @@ -181,6 +184,8 @@ def get_mac_addr(ifname): self._network_send_lock = threading.Lock() self._raw_server_private = None + self._recover_timeout = recover_timeout + def _is_valid_socket_type(self, socket_type): skt_sz = len(socket_type) if skt_sz == 3: @@ -484,6 +489,15 @@ def stop(self): return self.terminate() + def recover_target(self): + t0 = datetime.datetime.now() + while not self.is_target_ready_for_new_data(): + time.sleep(0.0001) + now = datetime.datetime.now() + if (now - t0).total_seconds() > self._recover_timeout: + return False + return True + def send_data(self, data, from_fmk=False): self.send_multiple_data(data_list=[data], from_fmk=from_fmk) @@ -510,7 +524,6 @@ def send_multiple_data(self, data_list, from_fmk=False): for intf, data in data_to_send.items(): sending_list.append((data,)+intf) - self._fbk_collector_to_launch_cpt = 0 for data, host, port, socket_type, server_mode in sending_list: if server_mode: if from_fmk: @@ -1171,7 +1184,6 @@ def _before_sending_data(self, data_list, from_fmk): self._custom_data_handling_before_emission(data_list) - def collect_feedback_without_sending(self, timeout=0): self._flush_feedback_delay = timeout self.send_multiple_data_sync(None, from_fmk=True) diff --git a/projects/tuto_proj.py b/projects/tuto_proj.py index f049a93..5aa71a2 100644 --- a/projects/tuto_proj.py +++ b/projects/tuto_proj.py @@ -28,6 +28,8 @@ from framework.targets.network import NetworkTarget from framework.knowledge.information import * from framework.knowledge.feedback_handler import TestFbkHandler +from framework.scenario import * +from framework.global_resources import UI project = Project() project.default_dm = ['mydf', 'myproto'] @@ -156,6 +158,21 @@ class probe_mem(ProbeMem): if serial_module: targets.append((TestTarget(), probe_pid, (probe_mem, 0.2))) +### PROJECT SCENARIOS DEFINITION ### + +def cbk_print(env, step): + print(env.user_context) + print(env.user_context.prj) + +open_step = Step('ex', do_before_sending=cbk_print) +open_step.connect_to(FinalStep()) + +sc_proj1 = Scenario('proj1', anchor=open_step, user_context=UI(prj='proj1')) +sc_proj2 = sc_proj1.clone('proj2') +sc_proj2.set_user_context(UI(prj='proj2')) + +project.register_scenarios(sc_proj1, sc_proj2) + ### OPERATOR DEFINITION ### @operator(project, From f8d9cb4078c53c83ef9b85edc48277ac3cda839c Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Mon, 25 Feb 2019 16:38:31 +0100 Subject: [PATCH 07/17] NetworkTarget update + various fixes [NetworkTarget] - Add new parameter (add_eth_header) to NetworkTarget for SOCK_RAW interface to prefix every message with an ethernet header. - Add fbk_timeout and sending_delay parameters to the constructor - change _custom_data_handling_before_emission() API - fix wrong value of self._fbk_collector_to_launch_cpt in specific situation - fix bug in register_new_interface [Plumbing] - Fix feedback collecting in multi target setup. Targets on which no data was sent could have their feedback retrieved in a late step (when collecting residual feedback) --- data_models/tutorial/myproto.py | 2 + framework/plumbing.py | 26 +++-- framework/target_helpers.py | 20 ++-- framework/targets/network.py | 165 +++++++++++++++++++++----------- projects/tuto_proj.py | 1 + 5 files changed, 138 insertions(+), 76 deletions(-) diff --git a/data_models/tutorial/myproto.py b/data_models/tutorial/myproto.py index e57b1c8..2bc13f8 100644 --- a/data_models/tutorial/myproto.py +++ b/data_models/tutorial/myproto.py @@ -1,3 +1,5 @@ +# -*- coding: utf8 -*- + ################################################################################ # # Copyright 2018 Eric Lacombe diff --git a/framework/plumbing.py b/framework/plumbing.py index f00ed9f..5081670 100644 --- a/framework/plumbing.py +++ b/framework/plumbing.py @@ -1810,7 +1810,7 @@ def _collect_residual_feedback(self, force_mode=False, timeout=0): collected = False for tg in targets_to_retrieve_fbk.values(): - if tg.collect_feedback_without_sending(timeout=timeout): + if tg.collect_pending_feedback(timeout=timeout): self._recovered_tgs = None collected = True @@ -2139,11 +2139,19 @@ def send_data_and_log(self, data_list, original_data=None, verbose=False): # means the previous feedback entries are obsolete. self.fmkDB.flush_current_feedback() - if len(data_list) > 1: - # the provided data_list can be changed after having called self._send_data() - multiple_data = True - else: - multiple_data = False + if self._burst_countdown == self._burst: + try: + max_fbk_timeout = max([tg.feedback_timeout for tg in self.targets.values() + if tg.feedback_timeout is not None]) + except ValueError: + # empty list + max_fbk_timeout = 0 + for tg in self.targets.values(): + if tg not in self._currently_used_targets: + tg.collect_pending_feedback(timeout=max_fbk_timeout) + + # the provided data_list can be changed after having called self._send_data() + multiple_data = len(data_list) > 1 if self._wkspace_enabled: for idx, dt in zip(range(len(data_list)), data_list): @@ -2245,10 +2253,10 @@ def _send_data(self, data_list): tg = self.targets[tg_id] tg.add_pending_data(d) - used_targets.append(tg) + if tg not in used_targets: + used_targets.append(tg) - seen = set() - self._currently_used_targets = [x for x in used_targets if not (x in seen or seen.add(x))] + self._currently_used_targets = used_targets for tg in self._currently_used_targets: try: diff --git a/framework/target_helpers.py b/framework/target_helpers.py index fe76cd3..f0b2226 100644 --- a/framework/target_helpers.py +++ b/framework/target_helpers.py @@ -168,7 +168,7 @@ def get_feedback(self): ''' return None - def collect_feedback_without_sending(self, timeout=0): + def collect_pending_feedback(self, timeout=0): """ If overloaded, it can be used by the framework to retrieve additional feedback from the target without sending any new data. @@ -182,25 +182,25 @@ def collect_feedback_without_sending(self, timeout=0): return True def set_feedback_timeout(self, fbk_timeout): - ''' + """ To set dynamically the feedback timeout. Args: - fbk_timeout: maximum time duration for collecting the feedback + fbk_timeout (float): maximum time duration for collecting the feedback - ''' + """ assert fbk_timeout is None or fbk_timeout >= 0 self.feedback_timeout = fbk_timeout self._set_feedback_timeout_specific(fbk_timeout) def _set_feedback_timeout_specific(self, fbk_timeout): - ''' + """ Overload this function to handle feedback specifics Args: - fbk_timeout: time duration for collecting the feedback + fbk_timeout (float): time duration for collecting the feedback - ''' + """ pass def set_feedback_mode(self, mode): @@ -215,13 +215,13 @@ def fbk_wait_full_time_slot_mode(self): return self._feedback_mode == Target.FBK_WAIT_FULL_TIME def set_sending_delay(self, sending_delay): - ''' + """ Set the sending delay. Args: - sending_delay (int): maximum time (in seconds) taken to send data + sending_delay (float): maximum time (in seconds) taken to send data once the method ``send_(multiple_)data()`` has been called. - ''' + """ assert sending_delay >= 0 self.sending_delay = sending_delay diff --git a/framework/targets/network.py b/framework/targets/network.py index 065ff08..9f993b5 100644 --- a/framework/targets/network.py +++ b/framework/targets/network.py @@ -39,6 +39,24 @@ from framework.target_helpers import Target, TargetStuck from framework.knowledge.feedback_collector import FeedbackCollector +from framework.value_types import * +from framework.node_builder import NodeBuilder + +eth_hdr_desc = \ + {'name': 'eth_hdr', + 'contents': [ + {'name': 'mac_dst', + 'semantics': 'mac_dst', + 'contents': String(size=6)}, + {'name': 'mac_src', + 'semantics': 'mac_src', + 'contents': String(size=6)}, + {'name': 'proto', + 'contents': UINT16_be(values=[0x0800])}, + ]} + +eth_hdr_node = NodeBuilder(add_env=True).create_graph_from_desc(eth_hdr_desc) + class NetworkTarget(Target): '''Generic target class for interacting with a network resource. Can @@ -58,7 +76,8 @@ class NetworkTarget(Target): def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, socket.SOCK_STREAM), data_semantics=UNKNOWN_SEMANTIC, server_mode=False, target_address=None, wait_for_client=True, hold_connection=False, keep_first_client=True, - mac_src=None, mac_dst=None, recover_timeout=0.5): + mac_src=None, mac_dst=None, add_eth_header=False, + fbk_timeout=2, sending_delay=1, recover_timeout=0.5): """ Args: host (str): IP address of the target to connect to, or @@ -104,9 +123,14 @@ def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, so mac_dst (bytes): Only in conjunction with raw socket. For each data sent through this interface, and if this data contain nodes with the semantic ``'mac_dst'``, these nodes will be overwritten (through absorption) with this parameter. + add_eth_header (bool): Add an ethernet header to the data to send. Only possible in + combination with a SOCK_RAW socket type. + fbk_timeout (float): maximum time duration for collecting the feedback + sending_delay (float): maximum time (in seconds) taken to send data + once the method ``send_(multiple_)data()`` has been called. recover_timeout (int): Allowed delay for recovering the target. (the recovering can be triggered by the framework if the feedback threads did not terminate before the target health check) - Impact the behavior of self.recover_target() + Impact the behavior of self.recover_target(). """ Target.__init__(self) @@ -114,14 +138,14 @@ def __init__(self, host='localhost', port=12345, socket_type=(socket.AF_INET, so if not self._is_valid_socket_type(socket_type): raise ValueError("Unrecognized socket type") - if sys.platform == 'linux': + if sys.platform in ['linux', 'linux2']: # python3, python2 def get_mac_addr(ifname): if sys.version_info[0] > 2: ifname = bytes(ifname, 'latin_1') s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15])) - except OSError: + except (OSError, IOError): ret = b'' else: info = bytearray(info) @@ -136,56 +160,52 @@ def get_mac_addr(ifname): self._mac_src_semantic = NodeSemanticsCriteria(mandatory_criteria=['mac_src']) self._mac_dst_semantic = NodeSemanticsCriteria(mandatory_criteria=['mac_dst']) - - self._mac_src = {(host, port): None} - self._mac_dst = {(host, port): None} - if socket_type[1] == socket.SOCK_RAW: - self._mac_src[(host, port)] = self.get_mac_addr(host) if mac_src is None else mac_src - self._mac_dst[(host, port)] = mac_dst - - self._server_mode_additional_info = {} - self._server_mode_additional_info[(host, port)] = (target_address, wait_for_client, keep_first_client) + self._mac_src = {} + self._mac_dst = {} + self._add_eth_header = {} self._host = {} self._port = {} self._socket_type = {} - self.host = self._host[data_semantics] = host - self.port = self._port[data_semantics] = port - self._socket_type[data_semantics] = socket_type + self.host = host # main interface host + self.port = port # main interface port - self.known_semantics = {data_semantics} - self._semantics_to_intf = {data_semantics: (host, port, socket_type, server_mode)} - self.sending_sockets = [] - self.multiple_destination = False + self.server_mode = {} + self._server_mode_additional_info = {} - self._feedback = FeedbackCollector() + # interfaces semantics + self.known_semantics = set() + self._semantics_to_intf = {} - self._fbk_handling_lock = threading.Lock() - self.socket_desc_lock = threading.Lock() + self._default_fbk_socket_id = 'Default Feedback Socket' + self._default_fbk_id = {} - self.set_timeout(fbk_timeout=6, sending_delay=4) + self.hold_connection = {} - self.feedback_length = None # if specified, timeout will be ignored + self.register_new_interface(host=host, port=port, socket_type=socket_type, data_semantics=data_semantics, + server_mode=server_mode, target_address=target_address, + wait_for_client=wait_for_client, hold_connection=hold_connection, + keep_first_client=keep_first_client, mac_src=mac_src, + mac_dst=mac_dst, add_eth_header=add_eth_header) + self.multiple_destination = False - self._default_fbk_socket_id = 'Default Feedback Socket' - self._default_fbk_id = {} self._additional_fbk_desc = {} self._default_additional_fbk_id = 1 - self._default_fbk_id[(host, port)] = self._default_fbk_socket_id + ' - {:s}:{:d}'.format(host, port) - - self.server_mode = {} - self.server_mode[(host,port)] = server_mode - self.hold_connection = {} - self.hold_connection[(host, port)] = hold_connection - + self._feedback = FeedbackCollector() + self.feedback_length = None # if specified, timeout will be ignored + self._fbk_handling_lock = threading.Lock() + self.socket_desc_lock = threading.Lock() + self.sending_sockets = [] self.stop_event = threading.Event() self._server_thread_lock = threading.Lock() self._network_send_lock = threading.Lock() self._raw_server_private = None - self._recover_timeout = recover_timeout + self.set_timeout(fbk_timeout=fbk_timeout, sending_delay=sending_delay) + + def _is_valid_socket_type(self, socket_type): skt_sz = len(socket_type) if skt_sz == 3: @@ -201,7 +221,7 @@ def _is_valid_socket_type(self, socket_type): def register_new_interface(self, host, port, socket_type, data_semantics, server_mode=False, target_address = None, wait_for_client=True, hold_connection=False, keep_first_client=True, - mac_src=None, mac_dst=None): + mac_src=None, mac_dst=None, add_eth_header=False): if not self._is_valid_socket_type(socket_type): raise ValueError("Unrecognized socket type") @@ -219,10 +239,14 @@ def register_new_interface(self, host, port, socket_type, data_semantics, server self.hold_connection[(host, port)] = hold_connection if socket_type[1] == socket.SOCK_RAW: self._mac_src[(host, port)] = self.get_mac_addr(host) if mac_src is None else mac_src - self._mac_dst[(host, port)] = mac_dst + self._mac_dst[(host, port)] = b'\xff\xff\xff\xff\xff\xff' if mac_dst is None else mac_dst else: - self._mac_src = {(host, port): None} - self._mac_dst = {(host, port): None} + self._mac_src[(host, port)] = None + self._mac_dst[(host, port)] = None + + if add_eth_header: + assert self._mac_src[(host, port)] is not None and self._mac_dst[(host, port)] is not None + self._add_eth_header[(host, port)] = add_eth_header def set_timeout(self, fbk_timeout, sending_delay): ''' @@ -272,8 +296,11 @@ def _custom_data_handling_before_emission(self, data_list): Args: data_list (list): list of Data objects that will be sent to the target. + + Returns: + list: the data list to send ''' - pass + return data_list def _feedback_handling(self, fbk, ref): '''To be overloaded if feedback from the target need to be filtered @@ -455,6 +482,7 @@ def start(self): for k, mac_src in self._mac_src.items(): if mac_src is not None: if mac_src: + mac_src = mac_src.hex() if sys.version_info[0] > 2 else mac_src.encode('hex') self.record_info('*** Detected HW address for {!s}: {!s} ***' .format(k[0], mac_src)) else: @@ -502,7 +530,7 @@ def send_data(self, data, from_fmk=False): self.send_multiple_data(data_list=[data], from_fmk=from_fmk) def send_multiple_data(self, data_list, from_fmk=False): - self._before_sending_data(data_list, from_fmk) + data_list = self._before_sending_data(data_list, from_fmk) sockets = [] data_refs = {} connected_client_event = {} @@ -1046,7 +1074,7 @@ def _send_data(self, sockets, data_refs, fbk_timeout, from_fmk, pre_fbk=None): if data_refs[sockets[0]][0] is None: # We check the data to send. If it is None, we only collect feedback from the sockets. - # This is used by self.collect_feedback_without_sending() + # This is used by self.collect_pending_feedback() if fbk_sockets is None: assert fbk_ids is None assert fbk_lengths is None @@ -1098,10 +1126,14 @@ def _send_data(self, sockets, data_refs, fbk_timeout, from_fmk, pre_fbk=None): status=-1) break else: + if from_fmk: + self._fbk_collector_to_launch_cpt -= 1 raise TargetStuck("system not ready for sending data! {!r}".format(serr)) else: if sent == 0: s.close() + if from_fmk: + self._fbk_collector_to_launch_cpt -= 1 raise TargetStuck("socket connection broken") totalsent = totalsent + sent @@ -1163,28 +1195,47 @@ def _before_sending_data(self, data_list, from_fmk): if isinstance(data_list, Data): data_list = [data_list] + new_data_list = [] for data in data_list: - if not isinstance(data.content, Node): - continue - data.content.freeze() + if isinstance(data.content, Node): + data.content.freeze() host, port, socket_type, _ = self._get_net_info_from(data) if socket_type[1] == socket.SOCK_RAW: mac_src = self._mac_src[(host,port)] mac_dst = self._mac_dst[(host,port)] - if mac_src is not None: - try: - data.content[self._mac_src_semantic] = mac_src - except ValueError: - self._logger.log_comment('WARNING: Unable to set the MAC SOURCE on the packet') - if mac_dst is not None: - try: - data.content[self._mac_dst_semantic] = mac_dst - except ValueError: - self._logger.log_comment('WARNING: Unable to set the MAC DESTINATION on the packet') - self._custom_data_handling_before_emission(data_list) + if self._add_eth_header[(host,port)]: + eth_hdr = eth_hdr_node.get_clone() + eth_hdr[self._mac_src_semantic] = mac_src + eth_hdr[self._mac_dst_semantic] = mac_dst + + if isinstance(data.content, Node): + payload = data.content + else: + payload = Node('payload', values=[data.to_bytes()]) + + n = Node(name='eth_packet', subnodes=[eth_hdr, payload]) + data = Data(n) + + elif isinstance(data.content, Node): + if mac_src is not None: + try: + data.content[self._mac_src_semantic] = mac_src + except ValueError: + self._logger.log_comment('WARNING: Unable to set the MAC SOURCE on the packet') + if mac_dst is not None: + try: + data.content[self._mac_dst_semantic] = mac_dst + except ValueError: + self._logger.log_comment('WARNING: Unable to set the MAC DESTINATION on the packet') + else: + pass + + new_data_list.append(data) + + return self._custom_data_handling_before_emission(new_data_list) - def collect_feedback_without_sending(self, timeout=0): + def collect_pending_feedback(self, timeout=0): self._flush_feedback_delay = timeout self.send_multiple_data_sync(None, from_fmk=True) return True diff --git a/projects/tuto_proj.py b/projects/tuto_proj.py index 5aa71a2..b947368 100644 --- a/projects/tuto_proj.py +++ b/projects/tuto_proj.py @@ -56,6 +56,7 @@ def _custom_data_handling_before_emission(self, data_list): self.listen_to('localhost', 64001, 'Dynamic server interface') # self.connect_to('localhost', 64002, 'Dynamic client interface') # self._logger.collect_feedback('TEST', status_code=random.randint(-2,2)) + return data_list def _feedback_handling(self, fbk, ref): # self.remove_all_dynamic_interfaces() From 6a3fd6d93e2471a43098fa8afade9cc8f2ac91aa Mon Sep 17 00:00:00 2001 From: Eric Lacombe Date: Tue, 26 Feb 2019 15:00:34 +0100 Subject: [PATCH 08/17] Fix DM/Proj reloading issues - Fix project scenario reloading (in some condition, they could disappear, or not be up-to-date) by using a simpler approach: perform project scenario registration in self._generic_tactics instead of self._tactics (which is specific to the current loaded DM) - Fix data_model reloading that could used old DM reference in some condition --- data_models/tutorial/tuto_strategy.py | 15 +++---- docs/source/images/sc_basic.png | Bin 57411 -> 0 bytes docs/source/images/sc_basic_cond_fuzz_tc1.png | Bin 71785 -> 0 bytes docs/source/images/sc_basic_cond_fuzz_tc2.png | Bin 76023 -> 0 bytes docs/source/images/sc_basic_data_fuzz_tc1.png | Bin 74719 -> 0 bytes docs/source/images/sc_basic_data_fuzz_tc2.png | Bin 67535 -> 0 bytes docs/source/images/sc_basic_stutter.png | Bin 71441 -> 0 bytes docs/source/scenario.rst | 30 ++++++------- framework/plumbing.py | 42 ++++++++++-------- framework/tactics_helpers.py | 18 ++------ 10 files changed, 48 insertions(+), 57 deletions(-) delete mode 100644 docs/source/images/sc_basic.png delete mode 100644 docs/source/images/sc_basic_cond_fuzz_tc1.png delete mode 100644 docs/source/images/sc_basic_cond_fuzz_tc2.png delete mode 100644 docs/source/images/sc_basic_data_fuzz_tc1.png delete mode 100644 docs/source/images/sc_basic_data_fuzz_tc2.png delete mode 100644 docs/source/images/sc_basic_stutter.png diff --git a/data_models/tutorial/tuto_strategy.py b/data_models/tutorial/tuto_strategy.py index e551896..bbfd862 100644 --- a/data_models/tutorial/tuto_strategy.py +++ b/data_models/tutorial/tuto_strategy.py @@ -71,8 +71,7 @@ def before_data_processing_cbk(env, step): empty.connect_to(step4) step4.connect_to(step1, cbk_after_sending=cbk_transition2) -sc1 = Scenario('ex1') -sc1.set_anchor(step1) +sc_tuto_ex1 = Scenario('ex1', anchor=step1) ### SCENARIO 2 ### step4 = Step(DataProcess(process=['tTYPE#2'], seed='shape')) @@ -82,8 +81,7 @@ def before_data_processing_cbk(env, step): step2_copy.connect_to(step4, cbk_after_fbk=cbk_transition1) step4.connect_to(step_final) -sc2 = Scenario('ex2') -sc2.set_anchor(step1_copy) +sc_tuto_ex2 = Scenario('ex2', anchor=step1_copy) ### SCENARIO 3 ### anchor = Step(DataProcess(process=['tTYPE#3'], seed='exist_cond'), @@ -99,8 +97,7 @@ def before_data_processing_cbk(env, step): option1.connect_to(anchor) option2.connect_to(anchor) -sc3 = Scenario('ex3') -sc3.set_anchor(anchor) +sc_tuto_ex3 = Scenario('ex3', anchor=anchor) ### SCENARIO 4 & 5 ### dp = DataProcess(['tTYPE#NOREG'], seed='exist_cond', auto_regen=False) @@ -241,10 +238,10 @@ def before_sending(env, step): reinit = Step(Data(Node('reinit', vt=String(values=['REINIT'])))) reinit.connect_to(init) -sc_test_basic = Scenario('BASIC', anchor=init, reinit_anchor=reinit) +sc_tuto_ex4 = Scenario('ex4', anchor=init, reinit_anchor=reinit) -tactics.register_scenarios(sc1, sc2, sc3, sc4, sc5, sc_test, sc_test2, sc_test3, sc_test4, - sc_test_basic) +tactics.register_scenarios(sc_tuto_ex1, sc_tuto_ex2, sc_tuto_ex3, sc_tuto_ex4, + sc4, sc5, sc_test, sc_test2, sc_test3, sc_test4) @generator(tactics, gtype="CBK") class g_test_callback_01(Generator): diff --git a/docs/source/images/sc_basic.png b/docs/source/images/sc_basic.png deleted file mode 100644 index e7189f847ca462fc96caf31de5a4c5e12b2863ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57411 zcmdqJcQ}{*|3Cbul994kBt%At$S5PdjgS?QJsKz(5y~v%ZKsly1~MB`Nk+&{W@aj~ zBFSiwtl#7G`F!u=_#O9g|8*a~e|}xZbzEJ=`99Cr`FuT}kM$B|WT4GR&qGflkr)r@ zkWEOWbwVT(wHYl9{-k+8{4xGRYjYQf`Iz&ET>X$nC+1G-tXN6|Q z_uFeB$|XiB`R<$@;^JEEp4c zxxA6H^scp3ecHiDJS>l#dVM@!jE#c0IDN9T9SzC*+xrbZ-@8vN7Ul#Brt1n(C^|m^ z=Rf;uSXo(-n06Z8-Q{UxXlO{KKK}iC@+(^{tq80Gt+aGLOV8&5r%12Q8QKZYmMyzi ziqDOaZ|`-hQ5Xmi+P2@%XsCotKEc!=Aw(v-N=fY7DC?#&^7ShcmZ{Wd{(KaEa&hs()1RwqKU6MNXiPm{wrnepf4Mi7@sH=u zNm-492bp=`8@Suc%zQt;ZXg9>*AX&*-P&liF!<*=OR`1Hc`L)!y9d*AS^~z(4Ig= zI=?y2!TC(p+-c**9_f4awH@`_!zypZmCh85ZOn0Uv7P)!CX3S(uj9cv=H*DMTzdHy3_N<&Wf(&F!^$jQrn`PxVGy0los&|G`M znQR(Zz$GAPvT=5H_R8DiyX9o7S}Rs^d0jIM?y3cb1*$!!B67wN*xd`NQFE?yPYak^rrzsIZ8?q|Di_t}vy8x<84I}l`>F!^ZL ztK#Cprj$Kxot@W}Ihb?RHg^aLUhQj+l;M&+%j5Ozxdp|j@VJW`!;y0mWqvDc8nk5r z3oLv0?lq|wOm_{brDaYG+otLt|E0~UO+oHbd0JwpC#6x-N_*qo>iXe@4(*%rPT~yA zf+i^uom3QxtAxP%m0x4WJRa@pS}$^v!y_o0&tQcw_WBF&wFiCU7)H?%bSRo0oPBPQ z>%?T1GnvYpTFxZsvq>;d#3Gi7`OuEtBN3g<6mdfvh?=mHis=v~JY+4(?O(ptNfpp^anp?d@2P>K7DBo<+dY!!0f}5p^s(1@d^e2)Pf9J>I%IXvuGf zh=?vJV@JjaW3lW)S`^~~(p%Y4|{5`U9Zwftk1-o+?y(vUdwHzzoSAu(8=D}Lqmr^ZjV{zVU4Tqlb2q64~beD#>_+|0wnGv4!@ zp-I0{l{;HstDc~qSXwBYr);~edES~XuhM}+d zQs6gbHu?=KB_10tt4(g!JJ%umV=Qn!;i>lzjvYI8+_!ywA?f-JiYxV6jZ_Y@jxjO$ znO%J;*{r|Y%gFuF$Mfn}=j?i(2HCvua19&Ime%FV)G@guA=Xz|__Fd;#zx1{2o;^y zCKIWm=LJ>S{PS;a-J28Gx6^!dSw6=-hP%I7LP(ssi}yglEe>X5Nyn3pVRS+p)ejyl zagdWu+0=Y{FFB!QIz90k1JH*}{QIaZgTJPNN<@>BdtXCZz&(JdLVO{#S$h|)%kdD;W)vi@E z-Luo!H<&S{G{=36Y1tXo0&A!^^UN<^x4JyOK{uH7F&&z zM_=mJZzfISj#aEq^X?DIY8Wm)r$&$W6u*b_;Eb%yf@FmrnY>y}be7Ik4O+5F+=0mM zX8YsLB$D61r49XV-!>g_67rsE;|Wfst8ov$8~(UZI3l3)b?*;@r9f^Hp4<3DIOR$F z=8}qm&A+k}YQJnL9XwgHE}-K{1_NcVRc^#2}zd(OGxxbv@pKNN~MeGD_Y&2}** zvxQ))?P9`=Z^c|lA=k#Fgk+T11sr^@+_)Qh;bphx*`47xqz*sz`0D2!T*0@pnZ7oC zx@f4^j`PZ`8^$hfdDX=w9>Tm8_cpJe$-AP{+MvOwca9Wt`}9^h7o#mZSIc9UKJLvA z`~0TAdgttn=L_BX4J4J)7m=H`W3==r@P z^qPro*_jppg5=NIn|B{MKsFU5_DG(iUEm+~ClQ}DF2qywmHak!xt)Gj?&nuos_jA& zRtw3$hF(hKj~Vhi3>cm!k!&t*A(zgVCYQt|rm6n+_F?Ax#nLY0+0fD==DD1o<;QM0 zcW0o*F_}}7ufubB11I_wKfy@EbQoMNZ}fx@6w8N zKaJg9;87|Q-}LMp{oRg5<9zyCT5~=zpPuA6DeRS1*LA&6;CAZdi3-c z<(!IR;x5x6$+f$<=kQ3p2UXorW4eYps~EV=L?rX3n?18{8!(@o=PS z+J`<|bT-0Y)%O}joPn5FT^b9)nbHh_fd}Q>+G!43y$CJN^!c`R$z0!hKlWIZu-cjG z&yj|^%YqF9RD3om`#NvnbNjP;+rq!4E9byA%(2QJ7ZXTJ?kwuuA>*t4HT~}%9}Im< zCE29k_Q>U6(#Pfs>i1YZ{ooX4UEJst#m1d82!LpZBB2?-gRG;7G&N>Zl6ojB|@e z$3Hi-^PdmivU8<8wn94m#>EPElgM)?9FNng*zhSTc!nf~hW@-2Vhl{?WDjp%kCx;NZ0} z#o&dK#`>Vu_9L%ex>4Ei-O;EnCsnqo^OI)2%7|H>kf4oe;;*O%37yz`N>S4u=`Tg1N$Pca6b-&CwA*Uxz|yvxA$F7K9YYSqQ> zdJ^v@Zm5s=KBg|~ej zPB_q9ELC=;AJecnJvou36|tdDa^e-rjmi6u$fnv8>B)Lg4zHs|YUmdw6GA%8waV(zVnv|UydIzAxq@~ip^!lL;n(b1mbQR0~ zFWXMDYW>!BwN#$tTh<$0_M|y4XhKB{d_j}0d4{ayk``aJshXJND6=bBjz5-L59#x% zv1PYxSHOZt-k%e?V5e|deB4*M50t?uolRxYV+oxX4IAGrK5+xFOpZix34vVKlrAObi=(RnHc+3$3V_OHaCbi)$>LXs>i!@O2~VD`ac-`3ml#0U?eaHL zxwbIInHxrL-E(piaH=?aJ^?bv+dIZAN>8Z$y?oO(y5DBskfjkxZG#t{ajQ7!F-nwitp+gpCi8fD|l5gv%J(WRIS>OF|TA->`8 zj-7Q*19N|L4(V2g{`h_+<*o>@zebow=+iF@y)Vy-9shYo|JSyNthmX{cujH_8(P1h zyu<0!G3ES$>q%U@ooOFEdbGp9)W~E%>UaA@(0T)YwUFyIeoN)S65fS>jW1Ce6AUru zOjt`wN_q>NHeQ&Uj4BUY)G!<%t(=k`d|znQ(DGL7=~TwWqQu*G7)j0}oxGNP%C%GT z4kMByqhHlqE1BlvZgG^_x?JNqc`|Ews@aGXQjn|k)9c*B7t1}hwPYbN)%YLgo{K!P z-2djMBg3+NnF1DMBN(^V6_Y9BkDn4WY|p)*f4M(bFaPtd)>i4SZ>B`P3Y=z6cd``} z6K2njU%K?-`f*aoyc$Pk?~A`%GO`o+)m^;=C%WA!gRCdnN^Kph9!l_&zI>G-O`rFY zKL44s_3oF6+C5hbgLh8SYxD+X7Ue(8U=%vIuJ?n*o7@5w%M!WJ+yds3^6%7#4oKAXWYj1zw>z;)9lU<&lk3BzW zRyybA<|gcBH!+;wFc(D;*V#@Kp4q{VFWru;RJ^#T*PU;d5E~n7a;o}l-wUz1>47aI z=g%cRQQw39earUn@p-)WhkNf+sx900UwIUmn^2Md5u8F)Rv_$(@_J@w;}f^!Pjg7w zmQgAz)lo#u?W^l&nYq?fh#homeT7W^zwS4Zx$BsinazY#^l4J`Bc#j!Qgd){aP9Zo zotm1;#K*@N92~r~Fm})H=LjA3x^+n($y!=NZD|K7@$nJ&?(vhf?d`W8m*u`C=W^-d{1m(8^z3lGbK8BY)H`=P$6D?P&UZa^I7rsi zBxu%!iwB7IM=p%NakPEiI$mC0-Ooo24fE57abZiMm*STW%m0txtVjq6ts`wuekW|8 zK{nk->;OS(kFQPKO*duMk!Hr*g_geCt!g+s=hWnaalcH@zAp?cdg9aP&x2D_@rQ5k zJ;WDI$?5(=G_gFNXV0I%d+laxYr8=NB=NJ~i7(G*HQ z(ZDIL4~#Rk1R&V8d7)SM`THLbxpwFZ{@S*lSXon{(UFnL#zy+w+}vD4cE5AZ7wlE$KX`Atf|4H=6}Le*8GjbGYu%O%&yS zuj(YmM&H}lCnzOlP`pc4mId{aw40rod6|=d=f=&MX=$PD8QKT&$ROoE`U;%{pr&aN zVu^9w+}oY^wks%b;%|ta3@wNGXtdWVQ9{*RLL)o}qDZam06vub<||9aC>p4WK->DjI4?6ryEdxTd_B z{Y`)Ww!3%la_u|6OZohys1L7T`p-3Gk4y@Iit5yos?=L}n)A`)$L+ujuS!a)K78Q5 zapOi?S67myfCAq%EIqaVWBrdt|FB|H6@==wYN(cQt-HG1@2a&>j}ItXzl+8I60E-r80 zzh}XRp-bgW;#e`h#ycaYL zgF98INtyGL7KNqM8nvf;p75QY>ctDn9PKsW;QK95_GOnGX zHTCsQ)e($n57(4g%$QVER32Fuvq)OL2+7Xg>FDH?8ISrDT>F%!9=qVUkPy<+-{FMR z{B)Z4@84JDnLXTo?d|U44NNm>DdYz&MDcV9`c8d%Gd~cVAYHMhOjJa4E%rtZM~aLS4KE*`jNgp?TiIA%C6UNW8# z#TF+-VpBbAA1c(HR_Xa4JDLj|Yw7hT!%^Y+?KApwB{6Qc4rutbo3!|-m`8Zpy$x`oN-tt8UXqeuUKz3ndk?|g+SeUPc8ECW69&PDUBav zxnwky${0xAK0fVvW)i8Yfjr;7eItcbtgdXu_HSI?%&*FOB->!aOy1w!d-ha=qa`iA zC@g%Ob1+@@X+gmo^y~-mP-JKcWy5m2iVEMqfB#POWgWUEGTvJtw3%0tB(VOj%0-%D z>-bNfte5btq!3sm9Iv+2zI(@jol%bkFYy0$a%5ygT`F0^PFhcf%&u)`$M53e0uJ$? zRbys9T|;5Uve(3IRd{;h9Y<5D606F^xf<9wp40s*y&pcXi+WX6SIaoJZSVc~ark+w z>SdI5*f+6z`Xa;F$2yV(en@vLE-kIDtE2heldpa9qzF;tXL@&kD)VR6yRqxd%wRRc zqt!n>c0$t9TWm_asYs(6zr0tatqA%HVTCCsz;3|CoM*>%IVt8b6b%+1w3ICPEv@jkacIWBH)2VhiuL_Pk@ z{>+(JOaVF#+B5xS+$1JW&d59ad<7*X8MkfQwp&)V3M-ZO`&FJ$i>V=5K2G0*Nem-I z;dJ+|B|PBTem54^x3J*SisK1q-D%JnEg|0C-v0OB_xu8<`gmFQQtU@e^6jFcIV@Mv zqO6#R5yhNayp%HG@6O2FB}c%loVp{TY_xQAZ@zV8d!nl^E&Xc!JEpc8H9qdVgll0S z)J}aaods~h#VUJyxll4%9a)E|NX5m)y=DG;Y|H$p&Ye5ALs&R1X!)OFz~9~Ryh@iY zZ`|V8o~}u<{mg$2OKBIp%no>ODzz?Ta&nU8W3lHj%?(Mb2o6b0I6Q2ad|Ctr$hsgT zBowgtqbPOzRf_8-V(*&{WAq%r=)-1K2W-K`srmW&iAn^IuksxTjFfSzyALox3PII6 zRE5zg1}!OW+O%o6=MWj&x4Nf?6S{vxQxh4}3owm}#3g&2_T`xmm$D7-QIkL;cAsqG zTKf4;y72@__e_051MRu5O$Qtu1apFx(5A<6c@?Qaqx<+@<)Rwd-ha zKLB&k9<(qgM|2aPGFw(wmf!U2c{GW&XRcWJcRKBdhK2^w<*r;|;@$7Z#KV(+cQHLb ze+wo&nv+;h*L(3Li9df%WEOTjwo-WT;K6ob;SHs}({-5VgTuq?`1tNbD{g>FPqb?0 zE}n%wmA7x*5>Nh&vX76CZ`9_jZ~+`8h0rkauoX+2C3SBmHQiR&g7S>a&zB~=9k`fP z4GlT^Kft1P2E35QWn2#(Iz(W7**$w0US}DJUQg(VOGwD?JWr~`u->?38yWM~<9oLx zu+<=TmV1{JbE2{nf$a({GqmEa+_@7cY4wuLB4~9baPq@*ZKZ-?OkjqxyVtHA`SN{9 zLU`dFJN*W=ut1tq94G9gUB2}Wh17TFx#cgHA%Fg#++ybtrj(vJG z-!=~8W(YvRqUbCi0ZduVgEG;d4^X5%r70>}9;^G)dYkQED;Go7Sj{#NQym|8%6PuW?c!Ptt-OJyg z4Q9q4ebCgTPfSGs<8?ExER|^Y>xk}L;Pd^c_jErWDy}Y?UE<1>D8X70`{D0A-}sJiM_memrV&98pq{~459rhDv7sl(H}nC%<_@S zR^^{B%Dwm535KsNcVf=}=yRxzW+zpqOhn~q4Q+zHQmC%kQn`(l_40JZ>bCOo^2QT* z)R2UPEt5Zf%q~t>RQPPoyK;eM4jcDJUjLW>$pQ=l!Zs?AJGlvEPX0d*o2dw%sXEy% z=$QR66o&oTv+-eJ>!VD7dVSU$+Z_GA!qes>KRzJ*qb;UQHzm(nNSfgdm_WW|Nte#M!;G6SlGY{bopu^V8=2l`G5h15Q6j-gd3L zqEH?X{$&nFTSv#ivuDp{PFhJN4+DBt77YZ&oNT@&mfX|%p=PVX*;u7>U+n>3Otg)U zp-=q&^Cxp{vJntDZv$qs_>Z9Y;MK(eVt>xGYxA>PsJi#>=i=h3M28U4idEOrqB+Hy ztOMY;OH%Sb-@G;!??aNH#Kmdq>gtA1DQw?P!i=1mofZ4dbBfiRA5BHVqbr)CV?)q~ zybZwHw3zAZH*PfVRh5Ru(A#U_ckv>p#Wo%u`uo2>)a1I>)YcAS@)0-k0BrET)0bc? zeEZJV01QbS4?b|<0P$jBInGUej(L4?-Vy8nx2Iwy99WjhPaf-=baRPru|HMxiuge8 zk?i~V`3XQyIs|=XT61;v@Ug?cTGi@-uI=62Y^IjOA|f0cuZzJosRJlyczsvxo5RYP zAzWSS`0*Whf06kWD-GVOC#Iy(0TjJB-Ln~6v>PTh|4iO*TyyF0C#IDRTxYG#6 z)yc88zQ@Pg(#E?VZ^bYO`~4eUw~;WjhW)qqneZ#tau)=Baa^-$PoT)dU%ZeDn62eB z5@j=~Pt$$sIjlthIlPD6^72W_o0*LZ){v4c(IC)Gz{xMcr+&6Q*8y-S_RseoC+w#2 zu7}B9O76_Uf@^|c4u=;g04AO1NCWc{AeuT9x6Z_INjia<;RH3um8FH7`+U!)MTz=O z_s3!CaAF?WJ7|zyFR!6Oc>ot;Fw<_?QV+3@05!Qn#!A9Fc6^DEF0aJie(L}0tKhvG z-DUpg0Cybz&J04alAoLWu&&rz>VTzS`YOSjO=El}I_Z+7Y%V1xGDmFYi$3x028h?O z35y5mWo7#(KNYhA*uC=k&at$-Jow{>%}5DYW;He}5g+mG_umMb?{WTo9Xewo8gcF0 zD2nach2P1$cG415;e`vU!9CSD)xdxDAD*3fb>WZr^iJa=1ha#1NNfxOrQB2vR2CEz z{4zCVJMr?dWnmRSAk?E!{KxU%?{CB>ymb3`B{|uA!?!5PO60X|EX;`}y1Mjx-TPyk zZz~8vc7g8c;pKG+tqmP8Gv1u&1X4C7VNYyIHGusm6qBUT0>s3`UInkJUcGwN2;?f3 zdk@XEorXl_r}6aw+NC2}8Zj{@x*x~nm;a;h@9#%hJgWQ+7V;lPyJ3IeKQ;pXVUfR# zjD+FAwu44{NeCc?hrzNi&Enxx2?9?X$t!va^%>tV;0X( zGL|n+?Yo(h;(!_|yB7wBbIoPs@G9no)JhDTlT9~6fvt#%U{M~R#K0_`c=zr$@TtpS zFWx`;*a>=f|G?#$k+-b58O+A=^=sW)@%$v0WzpF%GVW8Xx?YbBivpX@7S0Eg8CBEc>e z$31U=NQzPj`|`z}q>j}^X)uIq2zbg0n)nik%vfFohzLw-m&H?(l9JL*m5Y0`4DW3Y z3=G6)N+u;IN1_)vyuB(s-d89=KvS$D-gn}Kq5W8}t}Z;E%(<`ngltPR{WAYw_AV}B zKDz`->N+~KstY5m53;j|F~JXDF$oP~BJvlZsw*rgWMB02tL*OPFweKGg~-sV5!TsQ zI$a)vHBHw`mV%PEkweP5!NKakv&t2`h?xM%sB4gJ#wCzzMgN~WEMGVa_{gOTa&O)0 z1QpbxB1na(uuWXMNpy5{NJR^TrDoE)bb9 z@w&Av8?0vXbpLA^`$`f48o}Diq{Cdp6NR*gI&+T@y9Uq|%9Mc54#X%3Dzf7$bSmuh zbd(6eO!3>NP;Ehf^!Va(8i_O{0X;GM+Dm=iK{0f&GI>n=KlI2uLc!+=RG@SCaP6fH z42nBqTwm|=o%(Wli)zT7JA9ZkkOS8~DqyWn%}OrLOW_eNQ>~-9Z&h^m)w!|Fk;j>- zAWXhV*>j2sa1&uD0aaC7Sa7Brh$S=&ee`G-(Lo`Csasp~64ndYZaUs2me6FYfq?<> zQ~?1{-1aLTkICWqLI{bv9863EsS^+pp+OneLO^Me5FiWt{hbj|_MM6a{2N5MMm~A+ z1O<}g<#7|ys1t{Db*muR%)Z&kU-|Yeox<5ql~}c76sDwfbTUQHq4j9k5dbAnZHNL9 zrz22^t_NOJ*{_y;y%oV~sILf+Xu$jf6BefLgm>;t{(AxawB??fou-TK9NNei;Lpny z6>3@fDS|${>EgzB{yDEH-=?AI85smKAgW&F!tY8D`7fiR!Y(2(UH}Z2e%)KGLC<35 z=FV+GA3M5f>)uW1PeGw>7!L&j3tswnl-Z#X%ceZ)UB0Jskz*arCZi_`P!K-7zQ|em zaO<4ifHrm)B)bpPAM5Mth?mEu=&?Ow6ZfSAwG}R~^J>7eXtyI>ZlIpK{eK=mu?Qqr z31^0i1bwRxy9yJf4U*9=tCx>z_L>QVRRaCVT%0@e3IYpcUDTX=VQW$I;McEki)cVq zEi5fLii0#XsLTINNZh=A8(;M}x`QJh70_Gc&JW{lCwiy_rg~P6s(4tXq1tlTUbntV z&}nFdb={Ax64grX2EGXB)$Y2oT`;`}?XDImCA^@Zz{A^{EqM7)a%bXo5(Q0?ZNbvi z^g3xfJfxV<hdR7X5);(*4PK1Aibi1Trf7)CegS!w&rinf zmXl+HqCp5gn|S1OtV_HPfr7r$n6vBW4gB|;nWVd--MjY9)72|ZpNbLkLY zZpi!Sxc9cc=g%MgmTY%uQOuPefGGX!=aQ<9j^_<-SfHIy=`~GE-dnjHF)%tg ziRIgObYyfi>B5;juk+_Quzxn(Q1P2l$41Yr>JxR}Y*wC-7nGk#%vHpt#USKu_qk3# ze{#To>2Cp-lGpK2pA}4C$JM9*bCV53V;;SJ?b_uH3>^0lMs5b1f`KdI^T>4+8;6GU!tLYBhMr%or_|Le zt=xW9r0qe_(o7P@4nrnN$Dr1rm`|^R zYXD^~hWb03k5r81Uc5-p#W}ap9w`1SARo-&tW1yzn80$WLqO|NFxekK$>ace6F()H z*SvIYci=y7&!HN+0*7iE2l&5oa&k48Eewx@!kCcycKkiw-q_QrsiAR+*x7fKeN0LP z5=YG}`PnomP^B0EznEEAOvdHs+Rb~l9}4&HflHPLycC6wLA!3O}sjg3-?*s*i0ukzQL5E7YG9(VIh$N0s>NGZ(J8z<;B8EBtz2CA~6vzZ+CIsKDRsbM3BC^O$f+B2Wk! zIHkxqhTs5cx2?^n>8?tte)Yzjr+DK6Qc|0tw`rE&y?r|df&ejSuy4M9ZN41|sZ=A) z$i6a!3KrGXMt@XG6a<;Udj8-VWUCXQvw`^~6}58m^2UO*sM8vc--As;60N^#WnppM ze>D>%z<4DX;Dr=|cFb;(?LJVh1M-lRWENgz$4mfhpn)@=UNNI@T~{{9w1Q2c`uEE^ z7zpXQA5ZX~;D1cNbXb>cdg=cC?V$Gt{_Z;J>ZDtH-Nbz!@ub1nUo%xW7yf}2B?5|~ z2rIhwy7TO|ClUGt_D{gy!AL-5bFpcBN&B-CdU9t!aS;+EBv;*!uf;&g2#F3zC#@&% zLY61cD@jyNV5~7oqQHGX_3yubM_ukdrUMCrnB(M^nuv&{+vrv4u% zGbS_4H1bZ%0=(|pv*!pVXb$7q46jFcpJdSB~tI4b~1UD*mIJ(hL7BvCV6U$Jvu07#<`Xjc74zvx`JCZYAS@$&Av zrQj~5udiQ&`DyBW>FGq)gDu-(>QIMZEA2Xe(Jbg&R%Rw0sj8;tLECFNuVQQe=VRYl zZ_x>m)zw3CtcspMV7cS9526C3P)W>=nAH8x_J1TSmJcf!{7L-h&-Rz)T9?E3cK{oN zd7NW?5^6Vz7737o(>+q7BTi7ChJY4eN86H^;&|l6+qABU91eqg@a%qbQxhBhh=o-1 z%5%5|SgHmLNHgE|^>wG(UposV-Z2&|M86vx8ZxnuE!9Y4TAdGG-HCWoen5i4nO-9j z6Yd#YkKWhU7w)k56WiC8UwO@hA#%u_?$(F=8J(KSn>&16&p7Zm?BS}NIa>sLn5=g^~IXU0D*EM$iU zZ#wlIKsDX2BB%=X8}5M;#kw;ZsT&GJumb0H!4p~dgB#xtY9WnVpajES01~k##OxXf z;I(Lt;rH&{Yk2pL6>XWYHO*d@B4#m*u}mn-RO(z%&%qg@YHG+y&rLJYJ?IV|JV*e+ z+>Gn*ZU_GUlP%BwXS(^`;mM5rrwMuP{E`Rm>(dJ8^4259$ndE=a4Y&I3V{X$k%C?F ztnSl&scUb)fFgrLTt9sg$_t#MkP0cCJ8p2gFkkv~_piP(B*|E_nSY7RBMzsYnR0)ot9?X92K==B(Xl z8m5=Mr#^9`LDs{0HiSWOctVkEN^i@JPatUTO+`<^*tj_2H38LW5?qQ7;N!yY@z7VV z6s?QBHXs^8d~W(I@yr1LwuER04=4;9L1@R0uy9&q#w?tbvnf4KFet?BlaLX}Sr5XP zqs6d!vldJcVlEI`1%&rVT%of-Hz_sMdgwa@2t}}R_Do~<{pO}@7cX9XWc!*2=vEOm z9zJ<(TE1)6+Uzk#86gvcoFysirVy*cb12Q|3-oz)Xqfiy?g>apAb?;pVIkc3?p-x_ zPdljj8kaEm-?@aY7v4W!I>zSKsi`TCbLS4CZjxTuSZ{{xf|d$0`>?B6unPi*BVZ?r zx*A&;rQD(Ga%!@-VEl8bf{6aj5LiB5KQ1qu{NbHUX3uyu@R9J`QTnx5)LE3Cx!+yg z>3lPlWiCTWF#6^ScqVu=>z%G-L%*n zi2wjBtgN*Vy=ovU*FY3}_+WX+3-_U|wAIry@1He$c3x{z-h&1o|f{azqd$4@fE9Yaecr@FX zF1yJ{iT|%Gn9iKicICUC?gtuv;oPqAAS>&rZYgYZPD@KmK}E$^pB9aZ=6M!JKN|N0 ziILmKOxf9OVoi;M8Apf;X2uf}lFTOL2|$cBAt-_62mu=mqB(95Nx|A!zsVk=1lN=j@uQZOJVb;7vZNDkguviP*d1}6 z8-Z_1w9DNJ3fhsI`H=ieahxyq8r2=2m~h0GoG1~KLvD)*!4SpsE&Co3VuHXy3LywT z?h3jwB`PWuCK*uT7i0v`6UCB85oHO3nF=7$j<~?@$q%f9gM*02LAR)cXx&DH9JRGG z@Wmb;9_rZex!f`DkRmuLJeK!jsg_THgxt{Uwqo2ZE3z9utF<7OLo>>>K5BiDa7D`l z_Yv|SXoC>MHK)4R(9BHXe*ZB(Z1v1er?>MbpInV^@98P3DO=E0-a_(#Q9;<6Ag5xo zM<5pw+97JoZgkivNnfNICg{wUH|qkRN^3r;4tg6T=2NljTk4J9cSQ}Lty8{#{ia8@C*#hp}8L)8}l#znGhzR8uA1JQr54w!@%1>Dme2&*7N7cA;Rg%dbgxmc#M5a zTtebu4OY|_g;P)rKx^$x(Or2q)VCG5{Z-)NOM=RiYOV_F)d2I?w5qM{B2f?)$_vnU z|0@22ORw>E{)=BT2;(7%ymM!E!+tbmvzIoyJDA=m481g za(?V+2*ByI0%2Tx&)kF`$gYuN7CfpVL^f5at*HU@-IDh3p#)YgW69y+6ke?`iGmR* znmqdTYc%*|H2hM5n$@5~W2+`iLtTtxj*35Ne*ZJiYa5T8^LD&}m}XW!K1mW~i`vpt{d!{#s3lchU1m7$%KkUy z4QgfKLe|wAX6OI=^vEND2|^@6y$kN&Zw=D)(CR;rzMQJ)Nlj$HOikGkZj}Ksd|Tg> zCk~gCWK?2?A1@`qY_ZcqC_s5*D<7kx2jdf9-GS<6Ad z=(PF2-+zv+M#aTRp8EIR#nsgjA3-E>Ap~Stw-ArI_s8qmW}ON88BTI$V&E(Dee1X4iCgs665m=ob-CAnQLSIQ}Odp756hcOMFg~D#0v~&b|JQA7MKLC~?s7 z?tXu2Qus<WU>lbBt zsgaOB1#zUkckfU4TQ3U>B|jdxUQHx=z;{Q_bWJ8FB`p8^aeYjMpnvxv1kcRR*TaE? z?HCEuF4rzN@MEYgK!O`sH}Wkyz>c;MNzEq|-A4hiiEcz<0+^_3Z8e1aY^Z-r?vVfR zqnH@IP?@)&W=jLB%lF0yDk{8EFQR*GAQAiS?Aa$CzP_AD@(e+8IML|zzi}h-@&6Y$ zdTR4uZbXTWX3;3SjfO{!gMC^BX-Y z{GWs|IfA-Vhul zF)d)(?P-a$^BRVr-i((4Sypyh|?CZ0f zuyC*a^of_ytne=s96~KnR91F`kKESNvyr5@Zyzzd)K-`F5E4K9m^9#T!uNo^`xH}n zY3}3dbKfyfrO{>ndOPz&HzpG$HRj$})Q1UNn=06<%PJ2O{7rfN2)U0o`B zM4Ee_Z8Fb4B^-x7K8Z{>p|?FY&^nsQR7P}sB?%zZRS%Z5)HXFrF)6&b3{Z0@;TSxW>C+7 zRk`*&ISg|A%%|znrArni-Yz9d^urdlb#*6isy$ye!10x@1(&X8X;4v70d~h={X`5> z8_-e~#xoLfOfu>Bou9PFFn4#GCPFU2BRbzY2%#QZi`d8{9UYzfcnZW;j{95NwVHwI z=?812j6Ob+y&1myskZwEQnp1RWAwAbP^}$#GPL}eqjwQ3nL$B4g|JNhtzsq%mwE=( z1=+AhbGty{vCP!u=zGa2d>u_eI{{Iup~}$F(td#xXZpw@5i9f@i-<=j!&lw9_e^C? z&3eS7@{T^fH99L+^XpH!ZieyS;%iJA4~CZ(eg({ZI0Xsq68KpZydW{YL$rz7&=&~< zCSY}W-u-nqve$X$`7O9IgJdh7|66A+D-gyB+<*8>8-eIc8p?LdnXMEijcjK*9)yaX zK7D#0ev$Pnj|23H);f1`B%bGEr-<_%+37W*5wY~(-6EWorASofFBq0`1M~CqV?a{` z01Ce~2D7iGy*&|Vm6emz{L9CV(R*=EMmxhdE#!($89Lm_|9PBDo3;l{unIgu2}e1k zq@?h(A2zC}6rU2TDcvC|O2KLj4-Otcwd16#6GYBOHl-X#j&x|W{`x!X#3S!o18$k3 za`AkcyB)2~QQEPG6>LHpw08)o1~XbKiba@6OcHvi@>QnvkWVE(F_5l_NC07z*2VMP zK4}nL^%9CZvJ7>2orMS6=mkvJW){S2u@c8z3vXeQKC^#Ajj%#UWTbW$D*`IypqPY& zY%J|2UPUHwrOaVk<5Zn{%mQIvQ~j^?P$5JT{}{3iU_!|nWax*w$x?9uD{3o$g%sTT zO_k%BYNPjaGi!)CF3$bHERup{p4*8M>hHJx zrlUcds0BJLW}LnrJ^YArG*fLTaU*Q;$SQX{vDt@qUx)I@WMbABe*go(1G4kxZToY) z*#x3Dh-rl}Z4eg|i$cE95f*M?p*$`&{Cd9si1H28;zMBi-#x`qShqn94RqD;%CoWhgtj^|g4fBjkoL;K9< zk|aPoqM`+!xuI=CPl^PI&oIfo9xqf=s{1dNR^Vr{R>m993dBYTkrjTy1Kd6v@&rv- z8HUM^MTXx*HOe|h-cpzZ2Qa2_5M?*)7(>EjhjK6ypU#xtKxnH-MM<%WqX#)_22;d`oqgyt0cNSI8O=t!nb%mq|Exr=Q~ zbhIPL1YD;ZSjuM?=g*3pKY3(iA`sRPj5yk8#p<#&@=!*2ebYPEKC}~eee9Ko07P%J zD&o8dBKz-fkHm?osOV^Da9goFnHlr~8*115rQJ+2{$4I>!%_U&5;V>1J* zE9O?QB;3jU?Cg4ClkW4Cg1tYz1{s{f z5S$;lZZ;uxQj~Z-|ID^)9LDyHEo=0s;HzyxFR~aI5wsvo)nEhp zVGE0!?>)o(PU*-9hUpCBz{n^x<_iz~yKe(G;&fi!yLS(-T%{8Tdj`!h2W&CxmlU0V zm)0#8I)NAHXBzM&2|J^NH6TgUn7YvNaEj~+!Y>BvL=S`8vTnnByoNz5OnX4oVMJN4 zOWWBJJJ*NUFtcjVICcUB9~N-5w-3?HtilnI;N|ajM22U-|IaY6BSOz3wtsCSFzNv> zuV?$&IRx)DTw@kkPeMx}0JC}{>y-x&c0kSl)|1aqq^V)4JuuBRq0h!o&KW(Ns+b6v z>-p>JVQw=cN?D@6R!ymxj7%clDB^>4aI~+-XZ-4Pl?O2r0*A+aV^SSN7?lXpVGbXY zvdc9Q2ECC{P)Js5(xCX-LZ`qqhAo^7M@5WJo06^{%{)5=_EuY89|_*#g?56hPpXam;{z^c&3o8;7|!<4q4Bmm zka|TJK_ALY?y=`0=n=Zw0eXmHLI*(p041`3^RCel*g)cH@J<(CWyS0Mc1S8kQ0k>nwR^n)qG12@+ZjX!{$7uwef%3owG-DJU*;qKkB`W=YvS-^zX^x@GsK%D{{&mbxLOw_nnEN6L$V~eXnb0Knh82E@b5Z6f*uBC{2 zVJr|TD{?c(;dUT6aHRCSTc%T#H{p!prqDsSk+es5G+%2`w-I2udi{D>QBe`Gmd|uW z`P3_Y8AQGH>p6yY;>R5?ZUcSzZlhNF9(l zx5|YVzY|ymIkEplO|Y#zeSJ^$m-(Ae*s#2N5hw~LniT|2tfc_CoC}BHfC0Zksz8@X z*SYFQX5$6~#G^$(nI$SXbC_ZccF*K6@P$g&vIsk6zdl8ZMY&pJbLt*MH`$6NI-`}rJO>0K^N1Y?B9w76xO{CAgGNivMHIw(%#8F z`fvfqgJ2P4I#fqQkLH*??6%qyt#;wJPMVyi6`|@rdxoQ4nrruP3Ka?C;AznEh0F|E z;}NtG?|w{Gzoox2n6c(^^$=wjaQdNeHF)L=RY$g=`EKL(y%77L!l=Vj!n?x4<4Nc? zZ5n~sVT>-X84X}Fw(#%Yd(dCk?nk-aY>(>``1hRsq=-vrd0!WN2v-3<2O(eMV+F`} zeWpI85W)O!-|)o((QKIAE&Fg<1c#76T$sk2B6cxykY9iwp``7B!w%N}hRFN?2vNQA z8l^|}67!cm1dLb2@NN?(!cC=fA_zZNR|N6teA|!vKL9EUkC)mWI3O%6jDX}R4bofS z8{=fk)AurX6g!BRsW_051R0%Jk>kXQAp5W$Dj?1d?y#C(MfQnH&Y20r3nu1GuhB*s zB*iiEVi6XEkU^4j?WACf5U0Ez#Ign>$N=fC2H~k!LCYmC>24q*iOF>w)4Ok{07NJn z9GtFaGv=zQCv-L-$vn9kn&857bGVN)Kc19U@)z}f8o!>2iv;- z{=1mQm`w~JI1h`(+_YYQw=Oh6Hy0j?#zn22~Ke zq2XbSTNAu9jgb`GK9ZPUVK^iZJ5UzgOj)wfPZVdRUM9QX&^FfJ9(@FeyQ->6M^_hd zm_&fYbvobJ5XR;IA&Ve^_xEl_oWR+8_LB}2e5_GlPZE#8UmNo*eQpvO1iOJC+L=@D zQwLs^3)fN2cub1mfoSHm4mlpiE596p{O&>^#KE{&*w`@1>KYrPvBQYgk6KE}s-&Z* z9|C=;#m`-X!h2f!s^%Nr<>IWB)NrQsobMgkTav`ht}1DVHHe65)$DtVAOM~eL0vrv zDtJGew(Y+O>9_zm8mEokmjy$V%jt4Om)^8AJ*4f7l{U z$B&aSZPKQapX3~U%mc1B0*3pZ%7#|JwgYwxnq@sqYm`@5%Mq6c1uxqi(Dw+#o>$qM zLcl1#odX0E*tl`yk1}D+V-RTJO*rAifs*as-MgO)Nx(Bim7f0CuiX8Z{EA!-&-* zl0}uItBIcq0qF|s%d%~o0gkkRd0~gqh5(O497DevMiDIHO+@A#dl%c3 zYB!GkYQ^)uh1|DWR@TI;2ZgHhyr}3DhCD$MfmJYs(yn2{Y1G38O#x~yz|WC#D;>tS zP`70r-n#t+jXx6H+n2dVGSbr_B>^JehWkt$PSd@y>kv>p=-VlPDR;Md^hkuS3-9|H z0z=}s%X$6Vd*jf}e?iqIM%JAjfWOL9usBFK%H&*;ouA(tLbdM2s;{^A4rEcVf78>` z`)?fxf({DH{TXz2{O&Tw4=NqVmJZRSAFOP|3$4d41ekq}s3LlHLx{IMrv}VlG6^Ty zZVg_1cI!ZYU*9e4U-Zv6$bO)jkK_IB4_-+{ZiJbif3;ir+O=!He6_>=7iDiA)${tk z@n+a$*o08Fq)bK9WGHhYMW{?685&Jd$rKrikReJ+hBO$WXh0!DhLWi=>KeZqUr`dD4zK|$cMLY+5|hajCq6JLY}u??Pz64 zdeS5%pxa%8LRLqERoke6s`~x=#{g+_Cyxtg2^6;eteg^-0i3x*K{`vA|)t*&^d(y7zycke{mALQ{?k>62_D297eCp##a z22;jw6!6zv&CV9rc88zkN^FE%6f&y8SlhlDxf+INS{#g-8Yc}Ryo}q1yH^i|hGkTK zv=84fw4Y-WZw<3$ON;NTeSDhm0IKUG@B^5bJ`4pQ)B>74 zHEnX_`}>Q|iCa^1pzs+NFAnBdcsk&0dsB|&iVw8?9N|kj6)>!XIi1C=G6w8_7V9() zzuFM%b~?Vk_1B-Nb*;?5irl>vB9Ww-99oqDWz*Km%YJ&2e=d3*qbf?->pL&CUG$FtmB*<{+__!A`n$qimYJ2K!fu!tim(78{?>N5TVp|rMV=Z$8E&>hQp$AtYntet>J`9d~#8DZSb zYFjG5eD>u#kK?ALQV3~5vC)LJ?P1B7hZuG~ytq6V_;Cx;mi-JjY5$>g77K6(0tdcm znQrh;w8Gqr(d!P(L^;k!NHVzfoGZTdruplZdGPdE5xa2y2o5}YmF>8Te}=-vdCjnH!23+C%Tzqot= zZ&+`sab0hYZSwo*~*rgSpKVk33 zFV34Qo>{cu38gN5)lkr{KqckoFb)JZW$|ix(XZ<*^lkk7{QZ~J{aRJ} zlp+Vn8qjISW=Ljvqe;&9KIA9$!W5P_tW@hG6CZatsbr_oNvxXffR)boS9#N~SHd#L zoD~&(z4y5aS$QDWgPe2wla4y~Ivzmr!p`Uy`VAAOpyLyJEN0zlE)RSYNKS;X{t^eVEYmi68- zCd%5xY+2RE$9-rLuEM^FCs(L9pazrR!ln?&V1X0H_3_E}^x3hjshb`ukEkWmlQO@s zf5@uD++`J~P&5Y(8pPmMw3qT|CYm-;+dQi)oJzmmeO< z5Dhp8+O7F61RkK@iRouGISr%G{ZV;#!HXS+`29o4oo*)`Bgp*c$G8Ns3H)h~_hiO! zSSI<_uLE@pwWvIf$Kptt<^0cH^!ez&1XBE^rK8gle_&l*tveO=0?UKP9^7~4KDc;Oy}F?y$grQJ8oYWH2^R621X$B! z65?u6(c#v^evaTdYgrr$d>cx`&V1Yr)_3w>nII+9{$sRJGPvgA;v&C}H`Cm4xGOO)%|?anwd1=)0BHmYn&fFH7*s^6_bBQ`5*4^ zw-ha~?k3I<1ICtwacHXU(>2MW;~7ZUS7l`naM;kw(a8EU(l^#{pjvQ5L?FJ5qP0K7 zH(ksqllHzwdWsr14#-xe?1Ieu*?9Uk3o7C(u4k11fI1s{_)!C27Z;n;9Z;+u_3Mg&HC%H`iqh}#1D7pd z9q8NO%6K^apPv%0I0iUapR&-e2uINog$Tr9%%lna@JFZY_VQ9HVQQJj+KS04 zSN~?cp`PB=d-rxABpI&zwQ^YPXG2<4Iled?_Pk%#90>9jqJnwPz5I3w>0#o_3MMaj zXHP#Rlbe~D0sI=7SCwXP4{41dOyhV$~Ly49=OEpBn2M~|> z^1ed@supijoK`XoJ0>cUrNVpY_F!-K4t{VsOko~e5qn|Bwr#J00SslY$4QaP)DX|t zoQ^i+&j?{B*y^PEt$*j|YN5><*##;x{b??Bj zvK0DUj#WP`GbYj|$IntlFoD~U%G&nD^HpQm=`wfCFL}pX!|k2N51AT2E6ZGctsh@7 zw~fDoO-~uAjuke+R5_8yGY{m;8;?$ z{X@Jc9sLOyP~(Vxi`Zo_ZJ*Y@ZIkt1mzC|>vZd*j`nth%^>9Yg!+BOaD*bOR01dRx zUjbTsXdRL(^W`C~LrfOsau7Cc4)7R=YAr95qVb<7CirPLP$?^4ZBb|Q-Dc;ct|;pw zT`R3skg1uno!G7(ot!f6Rd)bo@H{uSEMPCJgRm9;6I`|jQQn8u|MC=}5QOZ?N5&lg z3cd=2aFD{TrDh#4WL)1d!oZsu?aegdFhz@$AkA zq(SJ7ED11u8^_9{!P?3kl0Y0&t!NBNbWNh#u~e)-cpa5R#^>L{3d>_FzJC4s-^*(T zVgv~6)PEcFkT##AAtW^iaR2BNk$jf<@b_Q`nf~kQWMGD~I=#Mb4gTE7r6wB9_Z?OH zmzF61+~R*2B7Vsi;uUlIKtB%66OK9>&4u*;))4}5-WS#tJ;7g?m`%JsT;;i1j2)|R z6_mDXSk2g1ex}NuZ*B|XG&5vCOc*6yxo4m}~h^;vsb-$n=6YQ7=nwhg8ERw&p_?CA{Un@NfPJrHgwtOXq{4HaSio`&(p%{5a>L}D7 zfjSB-7lS?fv^rGG3Z%iA@8on7@(#mK6d=J;!+hS%gpi*&H4i_FVe_?5RF z({1ZKSM&`_b0tlCzUWP2ItC9OOejr2WaQlVr!l#%M&S{<%5B?>~sz^7Sw5DLD9(80CnFh~V`Y+sF9sJRB$#37=6gA7iQq z@^1Po)%PJ zwg;jAE~VWADIR|2H36-hbnib;uq)ved=k4fuM^6es5}#i(hS(O%LUp&gaO8)9c$_q zwI>&_4b++ebs5EdU%!$5T3_%5%URXD{KpSiMSb~d>@P$wXgqDCy>3QE!H}&UGw8!;@Epl{4yt^*V8DH=3bx^e)I9tP6{GC+}{toiQCM)Q6LV&3yV~2)Dma$L>0NDe3Y~y3094k>U&7T$Yur zvUTT9XI_SIss>JX9EV?4(zHFXtK=PT&VM2Jn!(`xAa^u_4;KKkufKdbq2m*7D+4yN=|gRi`G_Em=;gBxg}Wt|g}v&=5t#~9q?v|*7U z0~;K1lHeevmMYrN!KeH$QQo1$3qN%l<}-+?%&z?3^QVS=$1vGB5SAM8%Q&E)U*;e8 zjw%sP%);(txgk@2I6^-u&J{8U+2|&PSKfdn%4W68_`fQ3$_J0$SY|O{f-}p7iS_mD z!`M})(L4!>OXW+nvRM9Kyy-K5^kS{Ogese;v?%nZ99PwN{ z)pmQERR)?~qpgRAdVB<2qQy)vQ8B#@2uP&guEV3XlyDs8!5+|+A4g`{u&5ua9!{mQ zsJodI(KJZR};zr&Qyp>>B;b-r-#^Xlq~9u)1Ky1F}|WcLOK zGa&kFW^E}$!YBjy{ON|7XA>jEztl7h4Gpvzea2&WW_#ZmDm6K5A+;KYoS-uNw};YGsgK4a3`T~bb@_1zqK z8SgQzOZ$hN|1dg>>R7ksK{;h3SWStS)#blZ(&FXd}-q1QoLw!wBCuyy-RQOMiqMpH8SGPd>zmxKBBuA8TLDeLA)i=;bKGI zWf^nl+s^BJ!V-M3RcqN_FX{Xgo{_D1F zQ`eKa$v-;wJRR2*&$$t+R)GU)a_{z{i!zTDw#0BSWd(1pkbSgyOg~@eG_Od$f9bt& zec)#pJ88_lit0b-8#>>Qu;BmUkH%>nyB8jBlxJBsi=!6;{VO|6Brt&1*NO_$s2l|k z-FN#U#@%7Iud9wuAlj^!TFfMqL)%Va#6;=7BIf@yd|%X|Tg-tWK2O-tUUPSDg+x)o zXlx~_ukLr5@4^Hpio`|+F<3&y@Ji-rM)W9fPNN`GQ#zJFNkTSnk*Js)*PoGynHTbMv{P#FmMpISWnreGYRYR z1ByqiUSMqw^J0L&d|smP8pJIo0whbl4QAYC3mXirGajmo$s>7D{vNQ?!o&N~mz`?{ z)4?As{Z0I^!uoTAR@w4=$FepNEc5GBjTRRKvzlTGw`gVb`UB8`D3Eo|YexB>z-%Ka zj&AAG;->aRWI@-ArKGscUij#E8$;*23N{5Tw`|_*eBlc{QZA!2KkEz(p1 z=*G^OGbjFBbsC#Z3DXe>E9G!GQ~)(Sw)!8Le&k{A_84i8$N;N#uq4eh>FxnZ1%nU5 z2BjbxKg0ln-ShlZ^UfB#zB#3UR=ly_Nc8zC6>ZjAh_w zQlVK1(tE69)*gj|*k$>~O-g?M?(L9%sVd8C!jp36z5hP@{N}(akD9@|bJmTxlicOR zP3zTnLi@Fj9@|(OdVO*#g^9_JzJB7vfqnW&_Q2VoF>Gu*pp}42+}W z$~G5xwCB?eYS($kyl{%Qj%q?&axuHw{dN7Qe*nkL`OvD*)KfAZ3}T-!xb2j6oi|-N)opl{ ze!nY?N>2Alx|>?v(#pQ)ch5ohs&Yx8%i1gB#7<5jZ+5hI=`|rj;Rp>>N&1UqH`}f; z+7`3-t-@YC1eY3%I0+M1=jU!nA|_&6gW2Lg$doMYUxR6fbP=j#xHjxQX+G53f@C_px6lO;YX6m+Z_Y&}Q)0SOAE#iTgc{NCktn ztM{}kIk&ud^X5nSq0obGv&U>pl!oGBLq4DZjM%+1wjMwfrZ_Car4+qiPFdLMJ=rf} zKy?Yj$(WHB6`tF{s6BHNcj%N9TeTwLZ5Zvq78+1n&I+E^G5aMP=P6`X6Cu(Zw>HgLX>{`+%j5g$|}+gerQ7hKC*N_}YqEqzF2;n|uSQQJA~XDWQbc z2YJ6!1 z>p8yE9Nxz>dFRtzzTQ>(m*4Cu_-ypOD59>FW3oE%?+9l**(YuP`RBlrK06=vngfZL zRq?Njd7AGsIL4Egw@-g2{+JzODxW+(p!QZ{&6L$RVsj^-mjyZBdAw206|c zlm7E{3uXuuqm$zvd^YWi9W~+_Bo0l_0`s0V%)TR#6@ynPfv$r;K>5kKB66+bbd=aG zOUU`-{Wycqp%<<^euQ8;lQgmr-tos9H*EVwts7UCgzMJ!zc1+W|PekDz$(9Z>0MN$;HW-oR&=Om_eI0_ddwqY0? zrOutli`N9a2*l%Q7^1Th6+Zp)>iz(!|Ka^<4M*}aB+QRC6Z=7FTGEW3gV8TXlX6$& zQ~9^_@M76!n*)w30C&!-TFPY2?IrwY7#msci!0y3nl*B~K-lB&ma z_R{zI4&H{=N!#>u|9a`=Mv~CU!uC+IMmlnJGaw!>f~^*x{WJ7K9VRU z*Vggzu&LNw23b9*kGOQGz|Jskd+6OKdtaDH!^N?H(C8EA*(&FS@o71QLjWgZ$`YhoNiSej8~j3lW#t+i3=$mcHf%_u=Oqu# z%biP3Ly0I;2Cz`xhGcljwNyU)ONg7T^}J~maST~y18!SYvd)=?C>{-6IPygGwOsH-U6Q=NqGu3 z;fwvZY-y*YbQJ`XxM=p&&TB~==7+=#4y-_TMl8}tj=NV?WAoqvkuU?B9;lf3BP)~c zDW$aDtcuhA@2*xUwQ1b#!F~}MEziLHBQ*_j8El52`@qQes{y}Z1ZKAR^0c_$ z!+suEg;&z7RjXThdA5aqG(n3wi1$)}W3|mHq6|+er6DMEuv_=e>-h8Qci(^fc!yJ* zL69Tf#sqSq@=U-w9zD)fqFgc=R9MB-PIy_rldNM)hhe^tegYWBsrZ7Rd z^7U&0`|$}0aS)UR6V4}GIWqH_F1AfVx!;ORjCssmQ6ytl z`7Rm!tb_M8Z8L9-yH3?HkN*Dr*>RlO%lUw9wjTdTK<9^dmX?c1KzUYfrh zv{|mV-%Q=6`$M%FdjpfK+EDSx(wm{h<;6m(1jlWkXdL=>R_~9fz)~rZ%?g~ma0F&> z?@PCCQRjP}_CYM9O8%^_CgdyK=%6+8=HI5?w(V-w^zrL4oc7oUq0gaRHhDi>|7^f$ z<=4yE5ZAGYV?Pm6cGIRtn5aecaK39+qaKnhh}E-IXPb1eHG znB_m9h1)t4G-*zg#xEfdkUyDmdFQ;^h;Ti61G&b+p51L2yy|`jIUBu}Im@4Orn;ax1 zdRy@ zOpB3F8k@gt)Gs~W@S8(CSl2!ru;jKT?^F0GljqJo64XZcXT~8(d}Vux;b^pHW@`6s zd!KpFdAfXbYNJ}P%=DB6Q^I4niV>JL3O})t`Na7#jV>ys^)XlbcU(ws{|y^P#)m5? z$3E)WD7D;qjeBI^qsVcFwRcKKC&uT+wE7GvUVF28R@>2J?WsUlTU8Ei(V_)due)Jx z3+wUGgG+>fTb{0h3$7Jx=@H(lybS0tr$1$#GXonmIZAk=SwA`J24MY5T3J3% zJeB+lNk?Y|5JYPrx*t$jJnDjjrp0##(H;Y#mN;j*)*S_I_#BvIr5IV5+jk6j=y+bK zuV>|!^Q@bKpbhE2s|tVZ>~6MGT30T}^J-}v>Zj$*@dXfyqNuQHRX8X(Cbu#>3<{!9 z^vzR!+W<-F|HB|}F*q&vxOJ2Y{zItXZ10(s@ZN~G#}5v&Q7m6F?!3wgblC8qd3eqH z=CLV>K+FdSzXCjHYv|de34MTPYgM&nt(i`Wt3>{8T8c8~K zKvB-)Yh15IAHa6=BTBZandK5;oUrc&0odNHR>P6qit(`@L)qMan$xtjT&}%h{^!8^<^NAkUGcF~nJ~BcgsYZlIIRnR z)JtF0m;iP{RBq+x8@FDw-$p0Pb%yB&lIxRm_kQU$3ZXBR!W-T!tfF_)ZNuHUn`bva zqU>?%#ad&`G%GY~-%f8IPsK=L77$<+%{Vo|;5lJYRbIl`Fn!8S96E zHz@&czI>U$fLElEJ%$|8pu!n6L3hIGcPk@fRIP^nbRt{v8Uaf%0rzAQ!)0ekrVJY; z!MT&PKsI&@irg{kylQ&U)vI0a1s0VARrabie)}s+-S~I@>{JhbIpGjWxfU=GMKJw+ zR>`UF>cPg6PVT+{`;HAb0CE-yxtBv)7R4}%Pj-{Z=J8Ia|JOd zd3j>ZDK*=|DGm^_C2HaYRc_;OJyXS-m-?5?Jqc4{$ejVyg+0gqmO}|H9yn(B>rFzA zE0!Qh?(+B8@Di1arvgvLi0@{qsbt&Yn%i*Vgxd!9;ZdC#6Ye}{sY2$3;Unj&#|6^t zCQRBj#alb*6Eu^Uyd>oT6I=x_1-O*}m6{^3j6A6yDSFD*kY z4r&eTx7Q`M*TCW_Q#}Tkyh4Nd%H>DwJONAWEEyZcML_{n)unY-Mfl`#6qKpS=JP95^am*4XmVWX?7Z#s zMz*mosHn{-`Z6u}%XOX4zteORW~q8VcxHI_Ol?@0=LC>p7+NAmsb4>?)XVVqpuvOZ zFI+f|Hb@3435n=tViJx-X$vhc_)R7Ru@TdGM&U<_U&HJmwzbK z*C2>6=V@hAn-|AanMwBQG$wKN?3iAC=Dwxxh@sb!;eS?#Yv{#`7ukpR!Me7;k6s4Z zqMu;$bdOzlFYwurMi+H0uqydFEokDsLp*m3RSl`u%Ahl%6D_E}f(MZ2BQ;>#lc0aJ zQWA2b%6p5pzJ zO^A_5q|>Z@@OTllfRoL4+QdCZ3=~=Ke^w!Pe5{)gtmCJYYI$;QG&C{05IH1xNjE5L z@g~SIEovr#NT{+^BX`RX1;bS0`h=;49|>P@Kn#nVBQ7wQVILQD!sN%Gb{JP~2up^S ziLU=;>6SaRl~4<>cq&BRg`@UJ0pkrMXB%PFlCTXtddFBJWE35O(htJDgiU2X0UCgC zAsJAz!qIixsj7x8*>U1=`Il2a@>3IX!MR8=9%=g@x(6)$hZ$*_dpz}`uaI_>k=z3-D!mr zL19m8{}%4*PEBksZ|7o#K7TySef#1}lHhe|XGq`avF15tTev)m|9lu`M!Zoz_WnIn zvt|B8U-g@}*Hl-;)t*m_iH*5VyZmvOoR6A@M^I=fs02Wnp!6q?T;RuIPky?0i zvNixHlMh$#9%Z{Sx@(LR?qEk=a>1+CWbiPfsU*YVpa*$jTohu6-Y;V^APbIryUptA zW8DrKpG{zv35m|<l(77_V^tnZ{b3Tfz&8#A93#5P$UezmKpb@ToDA{woRMJ%V&(p5QPFi zd0^xBS@`W_uoIUhSK%1v*NHI*_eR}^bx+)ctz=(`{aKm>UcB$pAhW|U-}g`57Ug!W zuyrWU#FO69C?k{pP&^^@Zo9l&mh(ItxNiCDk=#?n{emlNxA@QlOWVcIUf|%#R1-6J zw>h;S3nx3D57D20%e7vSdrNSDZXG|ozNOq5jeRkj1z`IN4f^?U#1ah4!u+xI^3Wj1 zC-myi4&|{;ggvl5HT-nxFg*WMw{d6h1*aRi?u$toww@6or4Aip)wUjk^hBtwj@s-s z-uf8>nKJp z&`P0nQ38gUtl5&2A-pBjis8Ax%4Ek9X*M4!UuJeF;mTX8$3LKHWHPd=wsy%T-|&9Q z;jO#b@7#7Ys9kOEm>R>x=XN0lb*t-Dm(zPX3ritF;MTF8Ey-fmyA;!j_Td`U5*UffndN0$y z%(o=>t_x7Aq~&?2tbV-N2k2ud*rlb+q?2Af|d zwf}r#LK7u~XBEpOGfZa&!eM`iE`6q%@;epBg(N)n{Uor3zRZR6l7j6<&d$#NlR)uq z>ur;~mfcuC88>dc-IPDAD5iRZ>RxH@W$#+WD& z_~et5w?dEfnSAg|wCWfJ6HMvxSO61GCY()7Y&ZuhDr?Ei=jm$?h*$UVkLO%}@^Vw0 zKO}$?li4VxL+|}K=1edV6+Mq8Nnx>-s%rbs&*RoIf0g5%>@$u07XV_Wm%Vzoi&axB z4bq;MIjBDVwIMgKeZthZlxeLzJG4N5mvIr(X1N#N;{4>j=+GLmRp zJh9d8%HlVU@AhNNJxV9c*t{YpPzk^?_H;gXcv}876lm6!1O_dtM&;zJv=~r6cl8EC zO-*iKS46xzZojW!(R)(`25~lvUmw#dJ??nEGcEqO%B>-fuumD1^dggUsIz<3@)$_~ z(um^{Cu9bsNOH$4ar7l68M?o?Bmf@7$!^CI-V#hBUP}5W@%CUMmaB+yS{1{8 zWZm(<8aJK<9ama;rEK_hap!Q?qyyh+6m>{E7xXgP(vdOf6vaBjiqIAjfW@wmZ0O33 z&Mr00D_~S@PIc?`V~XW>7src5Mbo}kjr$iBG6UD%T@;aOzEH~YE-=(Q=IFF5M%jD! z>`5+6Sk;{f=wXjtZWo2x*q=CUczN~3<)8L_S;weGe~iqi$a^5@*G<~>K0`BRxTbhY zsBj*2(O)lSDH{8F`O2A|H@K%!M%jXo>(8$^D0#t@LAp(6w$Q|||^>&a4UtEws{1DXrM=)qYpCSgf6DomJ`&;_>c9iNxr<%1BKT_6}y zLT{mG;~pSZEC^!Enx+_T7E{-2NEAl;XJ1?gg@XVM4Bd9Fo9~!{&FA-S=PE-FzznVJ z7)+Q#CGimp(hI`3`Y_OI^K2cx_~Ndsf0S7+nLh;8)!6Ow5uT?24w*Y%IkFsI-uJcjyywQ!~dXS`&ZaW1^Ux5F7=$M%(1gW8m{^D$JyI%&#@BB9lK zT9?bt8UYH(ecqMX2AnV{m#gAPNV{@nI5)*dQe6r2{WGmR#hmd>hUl_oFTgJ0wR#jc ztu=uuX)DSkT1WVOv87?acVrj5eQ;PECl1J}>80OGPjQ@XHZS95lD24V+D|x%;T5Kx zcyjoO_rgPaeS96R6`HqaB1fFMG~r@9!HFbOTb99E*Vx|4yJ|0AOh-XZM7n#q*r6GZ z>W^!o<6bgB=o*q*u8mxMKAzSf{=5cwWej558W^KeHNG-zepi7 zLxS@70q0vE(!Y#c0|n%Wx+7i0!ZSM2o>YlS>ious6908SLRlIDV6Ra>sNPH8A9GQKsW$NSaPSJm_ zmtat=1d+s$C~MpJu`mIl8iE!=zmTJMZ-Y-kL>QR-rC0MQskVnG39>J%I zY*$nJ0|D8*4%H&nRS&6ILUqzq-g#6u@I3St6F??R4NIt&IG1_22y)u|-c={d(Dwde zak$WFO-`7t1|bd1lzeiIg-FK5M57h22^IV-?t7N@ZAdUCCL*iGRk0$qrk+y4`rcG- zF?++_aOMdXw3x!pPtjw8H08?@%R;Hm=Q1=1(m6N+WL6hRSwcS|m&b~oRK{*_6xuL} z1XkF|Tv65Q2@6W$$zKxAf@?;udPQ-J0kO*91ttsXk3WM_I5Sb^N7tSw$RUu)u?oun zVue07(PIu(-*MX30(6lz*{}ysqlSIY2oA{r|}M z-8QL^gB1jU&FyPO?ZAOwVoZ|DE@I_e_3c9&aO#8f-u{{X;5$=9OZ)@*O@||4L?NTn z-oD>-*e!k%1Fe~c7fdhpiYuP7I(}BFDX9hJqPdNK(8AHR=)6bv!*nPDb)CUWUyeAL zZR>#zkjFxlx2WancO5G0YQ$2xXxL#^=2s^17!DoDaY8XRKiO{O0)cG2fdva|ur?X5 z_|lnj5edPgp8OnfkslTOy5>A4etF3FXe6{AFgJ;bSBa#KiE-LoF!4nEicpr?PB@LV zdZ$)cmaXwEihep^J^lt1AGy{QKiEzR@k2m2I^Z=-77DJgCB}y#Y)5^31zxjx8CJ#i;d>D1GX3{3z zlFBN|Wfv5(&T49Vhh}`i3W%@dWMymRzB80b83dG^9JS@+o?&GfD@y+RH}7thW_?^0 z(&b*qN5J*>C-gKuLLW68vmm>ueWy-$XmZ62`gbQIx%^kQd|3R*%9Sy(yPm)vt@>S) zQnmBa<{76ac=sx^4}XV;{|KL1J^A31sHt}L#kFF5rEperK!`Vhb>+;-ZsO)(c=aw{ zEa_cN7RKZtDrrPSgv`58DPIwkAjZ^!r!WjL#yXx$()O#iX)}_YZ+mHBSY(kmGgqxUKqvhdtK5I7B5mBlNOWc(mHPx8n2UrbBi0!;{rh z9!3Nj_wN%wtZdZUIQi=_&n^tL8=8=7duin|1j30tQr+zKnlpR!=&_836V%2{4Z_39 zG0S8nzP1Ce`h6)|jrHj3xm{22E?aUGLY9uJ3g;|wRd=wmfF9X!-m`JcBX0nj&06=V zPpi`(HCg{~bZvNg&ocMJ@jyVXX5Pu_5G9qVZLv5qH@46L9^`~ku}993sRtfweDt*8 ze82*t-6cWQWFs$7X16&~r}{W{)c7X)B@xwKfP>5^9)iG>Tg@rk!5`Nc&AqX^XJA`R z!$gnSPG&eTsDi;ePbzD!`HOrk=6EC^iO|`5w9OI+Ee*xjDa-DCdZ8BS4mBj0m?DKP z@lWr`$FFk4g!Nz8K}tIy1#8HHVz=B&Kb}`lkQiP5@xM@ZiIEBHlg%k9Dd*g`I!_8* zQJj&PX<{hX5KwL#xR>pb;w%F^$Jbifm#s^9{0PzYDuru1@h4(6uj|j=%USY?O4uN| z`UHQ7$+d$tv88SA=q7xuWM2xD+uj@e$&lJ{ZsnavO_I-vdtzS}FvlANm2@^L)AX zQB`#=erk>bnXN_zyhWx2LnxA{0G<{x5sRV>>8}}Bgo?-?&2w=Pd`}PelC$I$wk!6l z$g^BZf0dH(lO3_4e$6w?p*AJ)hEX>EC~)`Ckb6};>{zv2dqchT)H*@bUk7+CCz=9S zB^fkeMle7u|4PQf0D#s=5&))KJRzbVj(T4g*9WZLUgtvIAi3{b!OVN*f9%x66=k!b znaVL?p^tN>9Rv!$CjFEpVS7mlOtgv9jcKQ_0(axSg9fRPFUe?zDd_Y?{jysoXBNUN z#uQpx65L8@v0)mKVe1X2PoK_xr#M%SPEEqK0hG>kj+UWO?Q%E#KK+cK*8paf!8+!! zSuC7JFNU2%uNg^T3%u|4s`4`sLzB>uIgD#Y4yIbh;G1^*n!a(~x}UF_%Iv)e{55BN zW4jo2M(v(Gdv?~Xy&X**4-FxE*MJ%*nQri~VTa~eDFzw7IQXkyiZ67MP1;?Zyz#ew za%cF$Q0^SDhEs&m3qFxDU881A;k>3U)FfYqE-@ z3efhs-OtqZ8|wf;0czN%r|H)aDR(Ag7t=2+jr_Jr*|aryMEkjKc&qoRIWR^F9I8#h zn+5LxHe$5n*mn&j8}`pN>9p^qpUY%+@#4iKaOC)jog(%!@FHoxbVMQroJiNT&GFD0 z^}k+#xe^m1^Q?%@>%2CYsu;jhAI!2-3`!Eq9Q&OIkik7(B_4iCO`PfLrJImE5vDR2 zq|L-Hjn@PHKXu7o@mk@qZt|mk>%k@> z8;h?MSI$cg2CD&gc90X$ns_(4lo{l-;k@DLAEku@Z5KPHHM%HW54lA*W^$R9VlS|Ts#HFkkbKYZ5f_f%CS@cFk8dfLbM>rk zPt>on?yUW^+gv&IK)we`e^NoP@4gI0nTO)=enpS?mIoGhPjT+0n85hM%4n=H8bqGs{p7zp--5I0q~ENXvfP$h`52#U&*cc#ULYk?VaQ zKJKWg;b)roXoGzeUz^ieEUosByZK!c8_dK*L#)$U5`nT%Cc)@DQ8Z*km~{!`D+E|u zGr!D6!+HlQh^9!`1pVc5>s?Asq7eQ#3?Z?Od1*7vzrsl4#+^Hs|CmC{clT2ay6xko zf^#bdIwyCleZctM{I>Ni)qDFXmC1m?06&sytxW@zRy{M{(nf(0A|CM#B(vo?5lT%) zwi^>&ME1f}IHzQd?p$ui-)v&qYi=}kLQZseu#ZQLd18KK(Xaf+RA#C)93i|4l;$}` z?@izn4UJ7o`h^hko|ORg?=a1(dC)P5sSdI1G1rV&h3h><<1Obd9Yh;tWiztM6OERI zD`~FCwNr6}fdrq=XE@HlI9N$_H*5hsqNe=INqj}X)NP_a2>5_CRczTZSV_}wLvG3M z2NZ3Iq7ZLGwzGd`l4CrdFp|G<1LGMO!-IIU|AwNE`YCC&d}eO7`e(YFt7zj4q>f{P z`NZtYX;m(w!d_Uqf%X2$BgFKrC3TZ*HtxV~ClMR;TwEwRj5&sP6bnfZVJm3a$10in z81a@wf&ebMy|`q|d^K(wtA+te4Hc$vpT{2Ee~3DgODv&Wmod#uAzk^qroT+taK`-n z`BU8M_VG3WvtIFY#VImm^*62rT3PBriX`XGq`1cg?}Q6r9D(U*KPB~h_}Wo2D>>l# zD;c~Y2zXnTwBNe&r`3AxS?7|AB3?bTOW@cRT>fHfK|i3|__&%c*jzdYK6BPaMJ~D0 zgrF>Vhlm#1RnspsMFz+a1TZ{Gu!OwMi(D1>BDC-P_jY!6`Z#aomT0b|q}X#m_#q}C zgxlegBJWdE6)#so72#r?NO*?L+8yi|EUfpgog|WFAlp!kKN1?w!S%mC*=FzJMk^UH z+(DDiAYY~9w2gTAKq2?zA2*MDDnbbgY1~GY#Ssnqt*l(3PiL#b%BP*4ps`UY|~bYilCGofq_41 z?|TIBllJhF#BT|05A!97HKhieD7_DE4(gZwb0b|(9!+InppCl;G5TO8`*=Q8kC^ms zc&HGZXGCt}+;9TtGB@6VUJf-=^2t-Od0O*JEJrBj$B0jGZ<9g%N>tJtR^ ztCwYH4?n$0;Yzv8F>UM6cN;%hbR?Vwe|~aCV*fx_an7Pmh6&I+GNM80nOLag_vn(w z)*F>p-)3{LOR-D{_%c`L1`PSI{DcA}4_dlr8m0lA_ja4x_{Fe&{SmDBVd{UxfOH08 zBYKZiyevWh#E?eic0~=32=dLy?2{`FpjL!?2JpENikutMOrvGMgksgKKq%eU5ULzz z;*)^}nz+LG<1@MQMyNAVStNo#NON?su7%@BOp81#`TTST6;SpE6C|Wh{sTMw*r1oA zytQXt(8EO_w*$#{HZgc?+HU9k#I6_0P=Lwx-qM3@7{9#E1lLTKA*5o z>{W!zy&&6z3H@jd2x4vCQTJ5kxx5MlX~sYAfGu%v$+!)uKq4oaD{JkJN+DW=HNX}M z#Z@SD2k>JQRV+l;N^$PL9=d@jr}D8`nWJTXoMBBSY$7>jjnA&W$sfuM0Dz<0cnv?0 zl*THI;_veMUqlg>ino#u>TT5XRM2|Br*)!6*YQ|Tmir=O0e!Y#UKl$ zdYUQ&m;0SX+slpFPQCIQcBf9y08}SxCLR`1U|Xh_`Jv0vKMGaNf)H-Mc?X^2UUp4N z(eyzgn>art{`^mnn#Hyj_&aE!XguW?L)1okS(pa2eCW5El25>q+(1dov(GrtPpRh4 z@WLgM03ugM!m>-7kAi0Kvui{5UVfeFUKqmWDErC-|dp=!&^8$5AlHFidi%$gI@Q_P*^$;*bN!q7t-+RUJ7xS66C5u+JA)kiq2`l-9 zWklbiTC{^}B@%Nae~J>&{J(Iq86_2zQTzOg{ZFVr!kU1!k5X{bmJFNN?C-xNbVOPn z)^Kn8o!2lX4U?aaLU3wUYHrtBJtxT>g1hX+mSJRsIILar@#Y7#(6J_sGaboR{p;Ej&ZB_~=Uaxe!K?C^MM9rarYQw&Sdk z8&)JDfuEseKjF}M+|6{_*BB-7@L96!*-83if>ZuR9+k8+FJn`c&Q^*ze04E--Xy+{ zU{Nur^OHh!Nv}>Ee{8O}KSU71wnAUGUx_^;?ArCkrPKj_nLblY11^m?HrEkKkE6IDhn)R@ zg-Sw3q16%MqYa3$4BhLpYW3<0v_>ZQ6TGxja~aVG4@y3sWvHgvq1a{`0C?_8-^iNs zB=H}nwz~#^;MfNp@kH+8?PX`$sYmkGCHfkwMm~57A)I8HbZuAdWX&rV>km@jpPxwY z2Yo*u;~yVuql~gHE->To{CO;16I|xLMQ}TU$U_jK#zV{%8gMBa=D~#%1i|a=l$@urf;20T69B#Am?kGL(r~A7~2^84A$*7 z(S&oILV+gM+ukysVrmEPvV`KGH!xk|aP8 z-%i^vreWr0Ho-$$lTQ$nO@>wHV@Y~#J9ab&L3%xoxRyKQeb34r9}abnN|Tetc#MB$ zW7Z<3TMmdQSOl4mqO||XTISy?-h7*X;uxLZ1A2MSMQZ``k|O~ZgT%SM^UQ?I=N=V< ztSl|(!PkAmIK`=zgHHsX@;EU2&Qp+n%K!Wm_qT?4jM*mzN;a@EofFJsZ00ga>swc; zj(wTLvPKddfwV6-B=6W^mH6YogqiF3S4r_YKKW$lgqey#E5fww*Xv5wGp$F@nNx9* zVo5nXzrTz^(7Rv^FoOv6Ty$CU$E2D;_D}*0;~zzqk_!Nld*QjIX`ror5Y)*}Pnngz z=-GuJj=BmAVdD3pchF|`KQ~66A#-2*7ui9nxfZ1lWj)u=8S{f0_3Fb1MP@ack|Ofp z>O)PNl)k;F*< z-N_&0fi)`cpsIQm%>-!Khj#PRTbgPTD0KjhO4@~( zacwlG)GUn{Ml7jJN#rN;Q|H06LM?D3}~z z;+hbTIeaW)$W=s5-jcMaUGN&Cfk|4Vq=YC?MQ>zfm6SGtE@md5wU;u@CvRpE5b^{% zIXruJzywl$CGg_ShYy)>9bw!?+4iD^mQI5DG19-Ot#Z4|^Dl!mT8_Xr1%EDzARy;| z1`jc@?4=7-3Oh1Z1;3mcGfMyn^NXi&Yk=5X&A;JuNU3UF!`BcgN;Fcu!3q#nu?2Ot z>WxcIyK^W|b~jV#8i9{S&=oMg(T8sln)STpJHsYUiJ8O;t<)*ou)e zxA_GDKpRfNUhSBDz{suPrOWeLxNd2U3*ksre-K}Tv7Sb|er!>@`?==DVp^HW&Z7oS zT2(#B9tQ+O;Ss8V>bhM@n{`Y~I}(7ePxGcK=U}nKdp*Z=T~f?-U;S z*mJWC2YYkAqPl>+*0)aR?eet7@70~S+a*W{TGP*TMb~{|v4$#4TGgY?yXgyL@L6u4 zV-$=>V;R~DgYqHGxzY3k3d~Du=2x(+8a`xAPA;nD4X=0HYY;QgGUxWGi7N4zRz^q` ziey3f{QT;e-G7C>pOU+UB2l3ckz-Y)A&?+U1R_GpreE{{*-`j?^@^`uba+kOKR)w( zd{t@~@jpfdY}&MGfFD@CkNxa(xxdtcURC%j8CwKJ zu5G~*X1B@&wU~?8deCRKX!9_0Kd$+W=Ol>*ZvhucK592$(Z5I5S}|ac`1&4sSE8vw zap0;>3PQWHP2^JAJbQ7~ChxipTP;2mDaBmnqrl28zF3w%Zuz;-9F$@iP*`02o?mZ6 zJHIG@i)QV+m^VfwcJZ2+y&pGX#MP#SHMs9T_|!KGIv{#Ix;e!fm??K(Vf{hNi1l(a zeof~GHU0N`D99x7x_N&NK!ds-(oCbmWJ>PE&}qX>rO^NcGM1Dx+EanfVOed}V)*Ci zqygXUi71Bh>k4@{D(Tg>Kn`;QhUMqdc$h4HsDv;0syzs< zrNeY)%iyulf3R5zg`WMAG{n*oL@3dwCN&?s`YF|=uI4TW;AkHs4M&t3XW^d?}g!<-%RD!D|63WOyo!i+kdaSJatja_|W@K-FrOl=;bEAPS1v^vJ`ygl1 z6@F#9RYCMIS`X}fxfZoG^@Azzp#qj(EA03kKP#J{Grw^C?4L%^zcxk{P3nM7?nv`YH8p{Y-u{gqEto9z5;UW zN$AI7DGwoLT$ZPROZGJPv+U`osV=NC<8ql}mzgHWkK%(Q@vdlH?^67D91%k-wf&S< zk8!CCBHXg%7${?u=o#43cZFV%{zg zS_40n$BzjgLRO5*L~EC>f{0W&ThkB5ATrjs&>*GJ7Mhhp%RvHm4|I&;B=dIB45FB z`Mwq9<>dZNWKl{}7NXl@T8$(X6V%5z_oOuCxi5*Xjs9DMh?CtgwMwbM6Een9%1|6` zb3M6B#;;D>ILhQy7WIv9nMbdh+3(7)jSK-YhXag{lMyW_%vX#x1qDs0_fia+3;*gw zqs=&Vd)gA#U{`>?1;yXe8cuDCWUX z%j7Onm?z28wI6u+816!8+{E0ENE)JhdyWMDQ5Xe`kvXnCttL-yhoJ}I4lhmIOjzc6 z+walq+SZf}KC$w_KKD6{XGx|TB+A(OAS2R>ws4S?=er!1`3r6Pog?MGC0N{7uoYrV zNSSfk%VM=QmjJQ0WKxv&v3o#3XD+O|k4=QjB<( z3TKejpB~3TMGB~-YGBRU!RsUcfO)16c5mj(E=Wa9oo1?F>N_>_{7&$y+<8k(I{*%D z_gyF27PMSguo3h=610zy2a~<&G5m{|rBEbPIJ!A7rlobiEE#A3Sg}uuME>N-)RH}B zT~<(&A!SDMam3~%mU#Yts4Toyv(63I8cy1z=z7dQ39HR7GW^$ko{%(z8saXS3)4h3 z9yP`{r;$+JqNHOufTR)W>`$`2-8Q10=Kzysgn?H~+{1c*fB#&ktwUsN3x<$r4GEWr z7~lf0-GgtLD$Z3%jA^vSl1V-G)&L5u2;vObZ0DS_0USq6zdVmWlrPOklG|ZPl@Fs# zihdX#wME$~x0`qkGc}mJj)UArqci zUK7~78|1Ad0m5|RSc=(nCUXFk!;Qy}+e2eZEFWD%52@F$?o{g?4cfhXJ+=sn(;J?% zG~h^jJuI_2a2=ea{9`(#FzsEWyPZZG3iCQR6Yq9UNod*_vMpKquTP({ISN+t>uFK) z)6!WDY>Ejq=~9k^>wm4ubE#Z$$|)9;i;8OmO)6!t-u%x8O^#%<_}|O z{0E(p6gt0ser^6*%Pjy>8s$ufvCZS27>3J3U<;vO+L=PKA>nChjg{jdK-ogY`u}L_ zy5q6#-tf~hdLy%xN*=Q`WXmX9NVLc*N=0NOSy57Cyn2)wN+~2GJE0;OWu$}@GP23Y z$ogGJ?{EC^^UvGo^FH5k&i9=A-1l`|_kBnFK<@vAZj=)vUAv*d0Zo!f>>14*v+?=H zE%@77@H-i3yas(6eo!bp>HAcb4zsC`t3rfD1X!Rr%ux>kpgkKBV4RNOr;p)gTdANt z;e7%putb8X3nmT<@>H?0GysBOq2gKV2l#2q409KI>jeIXGzbwL7i)68cK&!43KfQ`HAi<&W14qOnfxv?J z2p1Bz0wDPfgk!*c3D?M?EW{t!xpc)!j#miqK;=7Phmm$l$f2yWgu^Hb77#m>=v8q@ zm7?PtP;5;yt8s_e)E$Yo1_16l1vd#)FNi~6ihdk>$rxSzu-c(Rgi}FsyGS9zcOZ1Z z5lb@iJ=X!&^-q`xo63p2V$jkl_yT?=L-0!ol$~Lz4WEr9(-E#m*>8Yq&L0jxhZFW8 z0saVnMvshU1RGBluOkF4N$SJF0v>BIg#2@zpgI3qaa7I2HX%Sbj$SxUC_Iu&2I#m1 zJP;XR{gY5BpR-~30xYs>#hQ!4NJn(}fd*L@m<3V2!GR+qbJJv(K??BV*4B&mp~)9R zBZ{d00$LXk8ZbLs5-NH!@3riNx3X(B<3>SAkHyh1UO*NZK3yG5+DY?0Tk(MIC&+l+oB=#;Pm~0Jb&-%Dh1~HEGw%M=NfrVxzhjT$CMc^ z^P2OoChfUE%fAM+3BxFcgCFtKBQj?;ohSuNP|ECV0o3n<*J!=?wBPEO00YF6aHdJy z0O&M9%CE#s!7wQIL;!*y-GM%TUW0HKOK#-XudaZdMSA%X#H~NVroPJO$Bup2WH9ke zfH$Dp-)b7h(#~h4tO@a%uu+yU+Bet*bGlP3{Y!r{62OP zoip$%M)|NRVJ*s{oXI~12>7W#QXI(~(P^_QW8BKX@3x>i201R^7@$6uAwlrbqLU`N z*gxzvs#p@v3w$~XPWu7KvCcNalSR4kUv6bW005lG=8cDGT#VqAd!l_XJF z95(>9C0XL4l*kgL4eV4Vc`%iC9P!aPpFN$|ztB_0(AY z_{{;n5?=BteBus5A1biL)Tj0UoLuDD_#jp$Sw2W~PRiVwi(1JfX5fM&@LUDUI1y(E z5+M)x52vBZH1Lbg?2?B6oHqa}{0AP&>!utfTM7^%DMN@YLo%X)75uYc*k`+{{WL>y zpa45232MmA_!~Bwuz4}rb`};P?W3R@6J}aq-g=%gUlyDZwnGZ`|D{N75n!koz!0m| zR=xWaWD=m{og@6O|Jp*DNFlc}so@8eDQTn!ed2v=_dk={DZ|J|RUz$w;bH&V8UkPa z#~PM%O7N1Z8edqT^P?_K+3VY)RI#VXt$-s-;5gJyNrV;)-nu>v!FZUU4=LjSOb=aW z_CfVZ9tUUd&*Ox3i~Y*8Y7}Pz(ai~`{TmPkh(Sn}BU!pFq}V@LL2?I_imOGr9a{!e zA`Sbqa@|yLbqIoKgdlHG_H+-na1x#kZ;gm8I6&n7UnT)Ep{qWuBl^T{d}2s49ycSOeaU9* zxCVhA>oS7xaAMyNvWo$|0=dAz#1y)Cz?};5VU0UP(ljt5(CCn^reJ7ZL9iu0I}e*D z2BL{Vr2H^8D0J{X)$xKrlSZZM9v_sOKFF<%)PYndSL|HX)Q4L5JDATvlZG744|nK5 z9sxuWs8+-XOGQVD0V5j%9QD0z*`^7)8==|KGL!R|o14Qf;{78^D_5*z$JzJq!W788 zBOS|tI42rDdq7xp#FddRAzEuf-m)0*{k6y}z^A{Cr4CZ!(>y85`py;=?Ry@m77ER1 z9>d-8(A*p`8OTNr@SAzAV}dX!VPsV+_guMIx5BR=4ZIKOSVd1y4@a{E?&X!-V6LG5 zVkHT3<5qAhNF)ZP6ut_dkd{I80G}nycZNfUI-(o&4rT;qM4HMj>Vy_8cS89|BWfAR zcLVf=S5^khPH1WPk~q@}M~6-t!Jr?_-*D|ABQM06U64-@6M=`2!c&BeMz>C?tB!W( z2528pP75~V-vmNW4=QGXSYlkZ7IjI`PJ!r(5x0TdFrJYKBUCC2bq4!ZHWj`B$;l#z z2h>4G5#RbyiBXMI9D-C0XDT+{5D`0+l%TIS^8k z@`80#4KP%QiXJlYVlapDHZ4*73>&u=PAWbz91v=-|KESZ=llaV zr!ny&ayjznx;Ap1I5Z`Q@QKNW-~YcRTXoYP8(Mn+OT8sMJ^d9_P?(zzu*1-uRRigU z6WnP59WDG$ggu;~$14zVAo(2QAPpdd7q^%BPmHAy@{?kCLb7NkfdnP=^&z8=R5LkM z3B(cNHo!4~8)QRJtqJB_*sLJ$k4B9(QlDMZp z*1todg*`vM7fNpg)`);2!H#zzmV)29=qa&0_rZZBg7Aw4fd>kTRbVENdVK-`Jg(#* z1QC%^;5Z?Pq$q4v+rqfz=+lTl1EO9#ysu8&uu}}766upcLJDLOlM+4$M;Ed$b-&jC z?7FF}^;>Usz3e_JO~mIc3)_wromv!D4kiW$nN526S0s1D>Xqb)%bm-zw$}D#6)krg zU~X&s+hC0n$MQ_!a0Uh~wjGQ9^pDw|8}QDeeIw5{s-UK5CALnwnaCe0(~B z*llb6LKG}hBR{qZ`$1g;#g}O3avLn1meS63q4XrY6@)$(_@3ZqM!uX&Bh4osJ~Yi7 z7_bo&7vE{L{b^oa<7FGMNYsv3HP$$1u=20JNRep3?L9kDC-{!Z2SzC zsG_`_fMW!#!TOV)VQc+lY*$!HFqhQS^Bs4>Cu>z!n~O#6Xnx!4G>R7VIrN zp0t0^;OPR|CM{}pAv81;#2|dN3l&m$HTX_BP3b9r_d$M5W(E1QPmlHsG;?xr{6x3R z40K&aTtRf-@V=w>8*d_!>kec3tY#&Z#Z|rT%80rUgVaEbpYiNt# z0rZeSF5zD-=i%FZb$qwoHu}lMuFg(TxAC*1qoavwX`}@tN^n)0`gC=7k0GB73A)}@ zr%wVVmLGY;4*Zfk+62{NJk{Kf1o~-I*3=+TL>76Z-@b{|m0M@mp$-vo-h+dKl^Ay> zCMWaZ?7^)AQnA2L6XDxLoRPkn*(Nj$p-M|PT;BQqw76Z-v>?=?1xQ~jz_1aZ5DzZa zV{{*AnN_;mft1?rEy;Q#ejT6a=+V#Zk}tkhgMK$exX+zn9f_$!%vy9dF%16#xtG2x z&S;;fry}_Hs+Fwv`zyU5B?L@pfxw_(syzULj2jRIOm4+ERClv-^0m|EN5fyHLrWCk zrnbJmp#g&~2KLx2bL`zFm$s_`8K3LxcR?z3jcv{mX*>8lAu%zRKe%7mP1;V8|C5oC z;nIFn2=gr|oP_nqrG^*oICc6oIxyYRUPBGi5oCvfi%&>Eg_?TxUZWrOGDx?KzkmN# z%y!^8;FP(e`nz`{wFb@w`alkZOujxPM%r8A*6D^>T{J&GO`&*mqjpyi>gmmc;ikLS zn0UpcB_$EK&0w2}r|s#?NZ(Cvd)P}eOm=l>8dKHl*-CVu05Xey}i8#N7^EF1R;GWg&$ZihwoK4eO7Y&h=;7KEV((- zTNUTV5BUcKJTpqcMp(aoJ*6SM9r^@^2GdLHq9lQiMMrNS#fcDkuc~rP0bT}fW+`H} z{8PLZQg^@IyL^&6pmeeM3KD^h-;&Z{9Z6iY>QY#QYPsQbxB#e`tf{a6L11!{W$oQ6 zb+{Bjfk@3E>xz zy>|`=YIe8T%MebEbR$O4l=#76cL5;b4D=KO61`%trKJGy6#_mZdkNUx*XyX7krmdD zccXlWf;mYQ11`z`-kxl)5iQ9HQ%4>^uH#B|-WE!l3 ze}J17S(K^X{;VS==m|x%XLs&!OzLe5DAlr=W%wjFYf{nkq~b#zXFrQ52Bcki?&546Z_js$&YWo1S2@lms{Sr*K;evP@k zSj~}$OJP9^F1ItrN+}TX1zc*qY@m`uuG7j|_QEBsY?J$YQ$JNz=kECx0f+r&D~n&4 z)m$Enr3%OYMS<2q5k*Yj-axe+8ncoDU;D0uhYm#ot)%2!+3cCwwr=S)6(-$!SB|yO z9p4s=bZz%D3EsX5@qsAH*grf89!PWM5$xqENlD4sp-V59y*ec&-X%SD*e~nZ4py2@ z)WqhtFD8y~vW7Tw$(JyCV4*5cGK>jqM_K0kH#I}!&g+}I6C&=?v0V5_p`iM14RBQ^ zUFZa;gOUEMy+5BMCm)00gi_==AGkrfsZBM;xP9ZtbVHN7n)A~&UPp(?0c8?Geu}r> zvcoy0Q)23~s>hryolxIbZj8(exs7j`v z_Sd$*CZYLcq7%e|rA_FR`Z>7(Fj>C2;9R_5N-jH1;laUG&x$_tqgCT`Bu`^r%Ece5PE5nf#Xpq-H%Z-^eZZD8__f>Z_^T9^ zb}qTm(;JxSrM&idFEbt6WoCM}vSMJ^xa*fO8|#|NSDw3;)6?A&^0iz*THoHs1+r6Ce?8a?)Yz*gtHk+QkSjz{_0GULJL$Rbje(9zzVeNXr44v|YtGmcq5!#xr{R-BPr zt!I3OzAa_7RHo}pcuG`4QPKAY7fR@UEoy00WHfWpJNLX@)&QAI^m(RV0{ll|;#=pn=syCjrdsOy|wO~eneHZ5u90wTE5>XF(FDhTY-XY_6Pve_i zygn{0J&|pwS#VL5)tuKUAn<}hxjrqZhs|}%y)QT?FReNm z|Fpn+d-LSz;D_5U=Y2)+Wn-e;)K!h_O$~+L#k%`j)+m(Ld0mbc2v*R{O*OxAj-erP zxMr7)-kzm4^Ary+se8tB;{r0GS3Z=`J;TgDZ5>@fV>QR!qi0-sE}z({K;@h2$uup< zH0S=7f5X(_Y`wHbQODq~dj}60V-}DnClju(LVx1Cn36|7bHl004F9c{c$bQv95t*9 zJ~LANw#H>T$g9n1_jLF3k0WEhUjG<=7h!&lfr*3in>iA0zVln@+=xJ=64XXFM8@-m$4Fi=3DZK z%a^CPCUvy#yPxuTF*vlWx9Z2E!$<4wO&dcm1?{n);?g(}Yd$)~8klcM$s8&Q%4f zZo@L~sc5*(ETYPwLFw4BBUSV~cHQsN#3N91@pmg%)t#Fsr`kFOx91Gk7m9SR^mlQn z&(~Hw(-HC_=sLsRw@vmGkCDw-K%XO;U8St2VJRd+y$_x+(E} zaIjD9XO7vtK}lINH|02w-K1=C=`Sw!wKr@ebH>E(TG`7mtW5vj-?6_n!fg4}OhV{PRo^i2=F8)PhDSou?^ zcVZXChmKy%`3+0Kd%;SPCtOmAJPQ-!eDYFfb_Z8QRu3|&OgF!{JJqN6Sgs*AvQ^*M zSR?Jc>5>LI|K!h)LtNWAMx;yaYc?`2Z=5|Ao_{^+OaAF$s{y~)jk9wM>C@~SXHRxT ztv)+dapM-5_mrwmr=0ZqEKT}nNTY^>$+#Csah_{uUb5#vMt7`rj`h(q<2!?Ex?uHU zT|L#3b5=Ayzqsu9u*3e;nu>}z0E{n%ld%t7q6_=h(R8cYyDXi2m^-Wu=HP>{J~xUI zr^XnC-`Y$}rj_do-{8~wdWS8X;-%{I>cWC^vr9t@u{GBkMW1FDs!_cI;@WG!_L)V&Y&O0yuAc{Q63_??=y(n!iZyPf-71Y6JCTW(TFz5IK8 zmkdH4zbu2T2&s{a%|XHO*nRhq(glrGA8Kn~Rj2SQO*NyfHFJ60rixEp7n{Vz8Z3&| zW>UPpo&w} z-XbsRkH<^Y%lagkc^a!0&b#s0Z|`;oO@jxl(j`vuV51ocok&AMXv`vX1fz9@jOV+D zJ7edodkkZWFGaTgoEA!=pep_CsHi8(WI|Fk;Yi;BA@sa^OJjeZ;lkuXTl3tDRk~eG z%!6~@2NVqhO1@Jz3kY0?koVdl;-zZbN2KR~dqYm)aKM`0@1-UGLm~d}-DxqejI~{R zU5loi6oi63{>l@4`f|KZ8-YS&+bJ>)N&`oBQ8)FYvIHsq(>Ul%@tB=mCp)CuZnx=X zskNkxY1t72;}YrPgC`bJYtkF)mz>CKBK2s1Qj5#PV9K2~W6CpaQK|*}{jJc@F2(MS z?wX)E`VVqH1gWXRSGR9w38-&=b!YHyVQQFv#=+4V&jS+`52NMXxZF)sW_|-)z+jmG z*zOX2L~ghjHympkbSC)4z4>vW%6IRA{_fZXn%#Bic{h^rdDjG#_Q;UeA4x<|*8%Z$ z0orO1H8iqNxHcw~fDhpbmLFw+yh2Dq;?tiWJCF!)3m@M#gl-{`kpK}Gj~qFou3?=E z&JAL6qLd){(IrgMTpP)jk-z4TMU1~)K2i!Ci~l-_aAomNzP8QGf< zzx(O=`rO{%KYu;vO^@q(T-SNt$8j9@<1AeLwgM48Ej|W=AyQJ5)5Kt~MKPFDN_aT% zlW>K<58yvI7AgvIm}B%;MqS=>42B7#BzHs0BV}pqvEB>qW2v>>ucZQS@lWAb(-CIP zJY$;)c^z+9<6YtMrC<40(FMWxMow?OYK`)$e(RIF^UzDRiL#2Hy5-EZ9b+1^p@f6o z&~uy5_2g2My!>|;Kh(B(iLZKP*hr>4OlKuMeFH;^erfz8VWAYqI{E)N_OcFU7WkFr z87xnH@}mK6oL3*zeSIZaSXn10C$Y%MpWDWWyD!naP`)_hB8o#uqrU9S6_2kFO^=1C zuBmBkZq6RIbR4e@U;I&mjoIH>Ca|-!tNi6Z%zj6{0kL%P!qA6AfL>WGotgLzliPNL$p-xAKk5i=lF5m~Ba;#my zS@GlKYxY-@BI)l|!fU2COOaM(UKWCwes#^yj-AE9;V;WZyBFsW++Wxc9u*Z_S0@pntV~)vjjA)up2A`26>=+WZqdLtx3^(-r$CR=mm14KtgE7`8Wac+4%Lm3y^PsfwGwbyJ(QW%z7qh;mbm)J&28ojBhX^QAW988a6 z$NlO@ONGzP)FPV)^2ONOZm#V(HSSkD8G3Jh?(5gDEz#&2x#7*3Nn7)FHM5?H#?tb4 zt%lQ*0Z*P}7B`XFv;^kUiIm$3C@U-Ty38mpjn>>*QbgC}NC=(NB|kmkZiI95(=}`y zoRG^-`L}JSW(WmpF20LD-|_96yn_SJ*Kgliqvg>B2AY;}4@EbGvo5qqI2)L;_}pW! zYM^7~V7ht@*L1(z;t6_wt!||^BN|4U0fCv5AHK@u2<=3F>#41v^ObTpD0wY;62jl! zRCb6SJUY$)-o-U3SaI>iPp*`YuEt9==v#8+g{Va3wtIi79R6g76)fy7yzHXcClI^O zd*_FKm@A8r1bM-q!@noLSHgAc`r4PP^-MynxHY0bd8MZ~m8&E3XeOtx0e zw%_|E|4nv(+2p~gCAyI?hQKiRRi2?4xA`Y8o#oP{v&7$w*8Rw>BjWxf<-dDTP5$@{ zrf0B@wMTVEI_HOGAAd`hf(=O%O8M5jR8vGWmN-uy z{S4U+lA!i$r_Y?39L-(q;Nc8b`BG^y%hLC%nc0w(^6`1hS_z{cii^Sn8LsUa)mMUN zCI0P8xjhkhIHgFquz1jaHf_81!`n}vbUt)g|LDM}Gw~(WETF*t=WS-+c|qai(){5| zIq8G4cZDNVlm%VVDsqEgTl~r0Uo+4iSb0CLYhU3oPWaF3Q_vmi;;grC3H?Hjqpsm@ z$+6PC$@_5oS7UbRPP=JbT;rSzT8V{h<>moMv<`El8fS&4*Y8`|hxf{ww!UJNm9sf^ z{+28%(_49lI5?G3TsgkQv#90%kH#*l=fBPeI9@vp7(G0-?ViUTwZ3BbZ~fo7`mECY z>cJn2^W!*{q4lq$^3kfW6~`*Se-qw0tQj4ijVsF)ANKMFOI$N=RUAF1b9a|#WHe5%!P zk9x4?nVzuD%S!N)>UU{HWlLIfdtBl(Vhpb^zg&!iNvY$5z}iC@5h0tWVTQX-3x~5@ zBSl<{m_!=7>{V`KCcIOGR{SDE*JNB4WDlJ;PHhn2+qtb`CVGo6z7iWU-_4xX9`7#l z;`4YYtwt~9b@oq^e)nHvwAEQ3R%+5aFOi>aY2nCL!Mz44iNUNZ70ma4Vt9LbW_~;u zJDOf9wC-5qEbhmfG&=^}w%rHQ{qh*;N-j0)Gtp&nq@JGjVUHPOnv=0TnKpKOE+6^| z$PkCvls8~)%~iY-^dxyDIy5Oge|~AykMQ;_+R3r1eQb=C=B05)2^#d~o8wYzBXQ)_ z9{*MrXgxNtyTfSmX04vpNo0xMU*U|h zg9+9W-A@e0+jJpJr}&ohQosE6)@MJ{kHHKFF_SkQ$P}#*U=F>-n!5&Rt&gMC9q-Kk zo*XKOMBy&^$=KFs^;;@~hPqx@-@A8QBRaE^`@R*fZl1#l0Vhe42}#1Pmi9FNpe#1Y zAj>B&!OktNP>1&Kt}vFg$&JkYYySpPci*nanKWj`UcvbP2#t}YlW*rpW4(4QIJ}IH z?AiCY*p!f_rYzc+bOATjTzeJlD(#PTT#&V?BD?3FMiEYa&&B?#8f9+A6sWbIKW;x( zd&ys+Rk!BdJAR{Dm+UsXBXLuL1cfsS<{GU(woJZe%M=8^w~U|Dc3Y)#5wMz@(HU{~ zy)l_ReL*}FJu_{wvixmzbrMWQQPZ12`P;^BD}8R)!*0oxB$&1wIG($SqVe%>N29vO z)OWSFXti{!wW^FwYXsdIhRm@`4|<$a{&Lf%UN===<-SU>M61urEV`UD!nu0pT}7pw z=7Q|@&W6dmfPhy7pWEZ(&kEgkZP>mZ4qrU)Ol!!Y@cgqEWm?GSXIgs-hTfaKq%Pcx z2I&bYJ0#*ejIs@djLvg6=x-8lG|zo+%krXDqdC7YF!NB`^W~3)<;;If&y}1v9R&Rq z-X~!BpO$VqW@nsV9FebM@X&f&U14Lg=>Em)!RSbNe(BioLhrs<)oS~om}4au^=tOF zft70lKIJ8q&4e+_zn;Xj#RL@<*%t|rKr`1E)A)18`{qp?_a&(ixbY>qzCYHWPZf^U0DoJgfKEm?-Zx5fnY6He&$IlNa zcaEKD{Zc-Jt)xn<+r}jDW>{Z4bY5V&ZCt(a^~b+!J|0DvXMgkZ3Cn!c;fYO7Dj>j1 zcuidGx=bZj;fKXAIx^AB_qy71n&9ll9&lT;<`CaVW;G z8Hn*`Ma4r*RoL(7@NbMGuaHn{lsKQ_m;5oU+M6LE^SjEr<*v^Aze0N~%Ar}LITT@C z_bioQ7G)Uw=#iD}m8=j&+YuX^5-m9dj){!kUf;l;hzViFeo@tK8 z+-ms~sT-H9>p%D{d9pBs>gGVgkHXSYnk&XB6EkL^Dx_6wyaEK!LYiO&=ZGGdJS_k`AtTSdb8%Mj| zw{P(?zHZWVh$c?EKqK&aV^a$?53RhhSUj0o4%*5P`Zh`;pHhy#ASK9#VcCm2N4r+J zKI95SGSOVr^%wTu|6eET3_EE&j51rtndF5P@uoh9EtW5f$3ptIR2g!5-!dFTn-2#~ z;z?}k23tECC5twEiW90l-Z3Tpeu5$;&ExLGd^IXnUcxhY{yaI9(N7{Mvt*~C>zAyC zmJuO0!NaZX{tb_hRg-iaVbLlj#D7s)8r&CSi7?=K)XsCpP$WBhPvScsH+ zXF!!DOt54_Oq4%~P`B9VhO^SympBfx_XfXr z8(qYy$rT#r6pLU8+|v8qtVKgho0(rqvRZ%qX~-E}sHjqys@>4jfB_(dAm^J`A1a&T zdZzVC&9GkIPUUi^NA0%n87(!zpZxogo5w~9{edFJv&?<3DzBtWey<>h#=`bLNS{SJ z7y44szpU9bF!f5hW9EmVxQdJ848j<)@FAs>ZyQOg#*0oWILjV=;j{p`B_@WQovBbo z^=TBZ^A=M_O~)0q!GY>;;w6{qewr=pnTOT;kxP{v(|&&HYCp%~$s?nG9@Fr-iJmdq zhk)2UO71q=3%L)PZ&+|KWER=ZPCs<4a`KpF+J%;Snd`JPHJ?W2#h8zLxLf)%*{rQ4 z&dI%;TAT`n-XHa%$P{7w;SB5UG>m^$`_tfp@bF50iRU?c_hI`GgNdbJyy8wz*7@y? z-{ewyXlzniM32vix$OfsceaydDWkR4^-774rzE~`J?vZZHOr-;vJP&}rnu?3HMJJ| z7l&_j=T@TLaGF|fDcSu1UUUi*YpCB`roU+$^QBXh<*#3>igu(=;ky?Lc0Yw|@U>rR z<}FL{T;icEd{9}kxI<*c|CH_;@%p1;X3t*2xrLav;Ye{65;0$ z$Quh$G~MaYIowAq&cc@s%6E=M{bc@4)_vG{&)=t^rIlZ{^F>*OIlj|&%vZE*#gu$G znTD^*6fQ)`awJE)YlMGPV=hRLT(}So zg_&I$B!WrJ58M5K$E1;(Om-<@PR;p(h?VbJ@jpX0fC`WXUhm zO|lp`Ql47+;C(}PclV)cXV!qgz%uLZ3sEM?=pht;ttZhlC@LvEiHszOh=?d0{gto5 znv<7jS39;o-9FuuDGQ&He$IYN(ANjOBa1|fwTtWI$JI}=w<>0UF*>h~OGH(Dv>o6| zOiD7}nCYag7e>z!_sJqI`>wvezTQW7`&?`13mnfo1&@q~F?t3D76&`aA|7i@l@4Qt z>x-x%kp5w%5;drFUpAQe{d>^ksTP;Khex%}@b3D0V>_eY(-^5E8a_Tg1Dg|qtC(Ke z(aUdbZSA@|OnEC=So`WJgRX`KOj}zU=cP-H@Zc3M%{!l6baHlXgeQ#zlvz}9qCe!E z6Iu6<{PKvtJT=iwv(_HvL2|aPMo6 zf%r-Eun_QhZ1CkM&36ALq!T9W>gp1+`|;%q!RGI8%}YPuPtMKZo;!Cgwc-}~bdCyr z@oUS@&dyjxwk*{@*S&Tgcszb={xy+r;I{S2C$xT)b@Z0yzI0k&lkf&BkOs8bTR;!#?>OOf6Ua3Q^7_6Cya-j@R&Qd_J5mY-DGB_a!0Wtt^$K5BR(5D;D2ySOOh7>3!OwSR z%+1a5@bSt1cOwByxKTkt!Tr%{J~rrY8ig~<%S3*De%>3M9AS_R+mFe@PlG~n{I}b$ z^ubU5?V7V^&kjK{-+W4g<>?TDyv;qi^*k)jXV= znrIBH_S`OnduwZIo^o__g!VnP_2+wRrM!;ITHu@Z+f( zc6Rm(hm%d2SAtDZH!yfLPA!U=SXqe zli6Jv<8xoqqoAPp-@fJ#qFWeqx#VbG@;zKvXlZYs21SbEzXjANf}qO$pfv*+;Iv+i z%SD~HdQZqXZvVfFb>Lz}8=Fg@1=C)0xa|HSkKerMG$Df+#dLd&0+h!8@Jm=)7=$kr zo$R-7-`<8zHLUlQ*x20Uvm3lzmVFX_5~L7*N-8SY{$lzwLqC6-Pq#(k5fisL*8gMw z-<#0Fv&3y}xnkVjcVPK92I5+dRy!y1-^JI|)U@@1K6Sxgp?MoEW!Ond<>cnx;EHb= z9MoCj?0zs%sLd-N&^*)m+|KO~3J9NN8zDy8)m8A*(Y~Egjf;FT=}9)KZ}V^GINR9R zlvY#(tFn`ee-?OOUf!~^Jkp!3aPGm_2TIHK=%V#KlRD2#-4YWBwmuCRw9+g)GxMs( z$&w$$Bqb&H_NHUj*V`Fc+1YQ_)s>d6C6(xFYG#GsN6{mP8G4>AaQ^&xH_!ax>6bjl z5zrWG4^}=dY(LD(&gS#kcNGy8o&0v~mmKs-yV{#tS`@wq%hw|$QBt$0_aB1nm(yw~cB~DuBzZ-X57YB*HBwW>%dh}mW#FY^-x~0I+&u?aF$?8rR zwZ5^@RQq_77mAiqy|3|-$jM{bLUOVR2)rLB)aAbRfAx%$i|c>+M2dm5yYtJ_1=H|< zm8{fb-Qs^~iDed-Es&Iyv|S7vq*G*o5=ZZy)ma{+YNr1-k%db1LzWiT;+Ru~3#0EI zGs^#}U%qr)@yh=_M1KS9GFH5Da8_29=g;TZfJ#qG9j;;X3kV>1@ZT3{V7Id<;KJ)* zGRAjiF#gY9FEgO%}fOpAy%k7vB~YknXOpz5Y>N`0y*JSpjX<%6FArKP-}$}&qx zFvx}ywU47OL*;sF~p&8U$_Jp^pJSLzKo2}9UUDdiaul?9UXO=?_tJB>+7Ef zW@nmY8^ zvu9i}m$dwJi3#1 z#tsPJ2}HG?o?aM(taZpa#%ONc;tT)-P*?{pB&4UuJe=$L!+RcmTIe*|2o8?C8wSx_!hMovY=Zoz-D{*HPx_x0V~g)xHF)-Ht> zW@i3>H|JOd1)SVbp5{_y8YeNlJ7g)QDtR(4<0nS-S+wF4(uJJS-Eaas_rX+gx}tdJ3c->RAEOA#lv~3 zU>;v#pxkP2S2*!UwFw;=eIuaT`c(SHMI41!9~8RA~N z2tvQl&#$?phn~O_t^4fE%zP~e2M-Uiudna@{(RC($6wJ@R}ALMefnzQzh(fE(T@)g z4@s!0IsRj-b*(H4UtA@Ou0UcUDhcx|QW_dOSZlzY!*}iNqudq;8~ggmuyKilAalhL zCx|maP#lJ^E;UBh%ePWQNY7ok-tfU~Q7*kVO0au$l-_Re?G115>+H8)vHe$)>VWoq z+1S`HyLa#P^UB*PB2S>Sne}GLJGr>@DJAE40Z7yYAD?|NB0GAt&EwYg@SOj_#U# z&2j3xn$&=&p^(jR`K6~(QQQwyZr`T5bn74R!E|OWE`0GvD-`1RCoc$y@Gev-u|J*q zfF1U?{$T6s>K0XLKZixaxs!KW`&gWr84F4u3wvR`k+E@ao*LtWq0$@w6?B+pLmpUE z8@Raeccn|kTpRb0eY1SM=*0^P@2y@1sC^{j%5~cSRHsL49s*k;{qJkXp`qRVKvV1v z(V>{D32n7Vr`@#L2@B8$;uk!|AA(X+=)a~(&@GQtwePJ@JMXR-V;VqZ=zXJmH7FiA1?^y8y*fByW@YwxM` z`+r`5YNt%9m6Y1LI_7v~Q7km3@3n*ylEf2{V)vss6Q}|&A78=1`qjEEvT@JFD=%KY z%rgGyURqk(5O9V7Ns!e< z6B-(p5Yo8kF65W|nV853AIZ$K0$}@s@8<{JP`?ti34m2+mXcyDGOTKY z!b1X8#NQwDlwR~*(|Wq!ktocxK~TY-IQA>`?ZaJa_wqtF3oHL7y@c7;2iA|7w~q-` zWp4)c1d2ddo!9Ou45%GXL4c6uNofB0v!-bs;zhkc3+W*6esQU(L=zh4Vqn^T`SN9q z^gq>e77FOoyI7ts5C=RWoQ=kl$c6&%(@^j znYXJ=jhHIK6L^<=lUO|ZuO&iNj=$OnuGada77h@`z|OiQjI`tP=cWis?()HqVg?CM zige$7o4_PmQOIMoI-niw_s{tPQ7e7-t_jL(c`U*Y`CVs0WW7>IAR)^3>3{j_?T(X6It?ry7s7H6R2otShA1fJ%}q_GplaOz z{+bArQC&>~y-)^v25O{QT)l7g1yECfgb;UM9gl+Mgz?xqj8o2SN|W$f1O8oDg(VG? zGin*$oI&N~I4BgG5__+r~1+A1z1EX9;1%nJLoaf$e}xNrF%%5TI;^K z3NZl6Q&?;)2?Z6^)b208c0i6!K+9=pXv{zZpb>Wm#qT~ACQ;CajQw^>7_467*RO;i z4ofaAEtPuzb%DY(GdGtF3h4ri+fb=F(59PE>oL+i2}tT3s(F~_N#{>H68B{`QNl|Y z!eo83TS}HCj!8_67L>5W>yP=N1NK%sTT-160on!@g-D<%bRb&H50$af)6Pn1()FI;HqrN!G??t zKdu-@FHLa?&n;-65^X~@TU^Yiy(xmP$=A*S<7t0_jmrxqwPSW8 z&o)mC+Mqu?it4Rf<_-?Ttl;s{(?ir{aBvV%B*axfW+pT2j;gAvnXN58bXf>u&@xeW zwY9x&sxK-k8u)=~T_N{gr#xW82-vN?e#S(lhroHKrlw-k(@kxL%=Gn>JcoaWo@2}c zMZ~~Pfh#`CwBFb0;X_$zX-rp|L?qBnv&MikG}k@W1gZ={S|X+segL7v3KM2t3{uyBYGPbfqgkZIs^*mib{ z%6_^P-`d)607iBD#2^Gx0XQNuA~Mqa{(VmOr#MAW<2f&1mUD9Ag@HlgxG#u$DdLZa z;XzOrxSe)A9GcOaU1~~7?dsc)GYzz~PQ!A&h>yRKtBHj$ubMGEI zTD0kDXDGAV^`O{j7#SJ;8HT4uI0zI3LXbXy{AW5(w=GC%+gq-pVkN=t8_|>X!C(e2 z!2g%;0TKAcq#r!EoRXTl-X*?j))C9>ImWGRk~lLvTaX^OfNHO>eMA)9HI|DP&!A}i zcd!e$d9wQHfY^Sxyb*NWCqj0E4a38$gknJ{=!wI}jLf!?$llPy&(0g_H*}q zXu+Z!a1k(baBx6FZX6h(Bqk0MHeaED#vnupDxQJ;|EI^K9T?i8t)I60+Z01tR4HZf6e`4>owlp$EdLqi#W!|wllcM*1Ka%M(r@>g_pwC9+i z63RrfCg`<=V1NV2vJS{rduRtn3Q%8gKmuaPWhv}{{TWmFUT`PRHinyr=O!?4s4S@D z?(KQBw6-z}3sZ5ZrO822zaZ(IFF6i?zPPy9$=y8!h$f^^PnJAEx$OWxgs9fJt7z)= zuMN`wGc*_#6Yup_t}1G3YTcnnu<)1l-<<|$BQW}*avO4>^tx9EPjhi|%j)P*`701a zZD8$v_yUw1-QUeEI3`9dz8w;anVlU6)JM>06(IF2EG<#{1lY|iCT8&OFutay=EH;y zs6D|Y0P9USLh}G64k#RMy_Sb*#`ctKH>jBEMn+GeIu=xY1Go$-5$eLsaf&2xr+|O} zG1HmRdOt=GM>W;1m%7X`pU+Twc_76Bj~78!jkFQ<5;Q`FQHOu+4p z_qD6*CNw19qrcWqFG$edd97MNea869%PV{!e*vmDT;i-4Ji|S+HupM zicCzH;RR&${=!QaaX$6>^=psSx?QeikZTx0EP{!I|9k6h3#f50SAODYuF^+zgTx6M zh4)$`u7yG-7YN%rhM0>NFHQmgt@b$(b{P8*3=*|DEEv3WFcTiHpr9PA?!@He6QIS} zJ_kGVJ(<{EzL2A~Fy)~7W1E@+7eP;9=I1B7aNz=N&F`P@t>eItQP5E1GK-JO4M-cR zsbZ8s*;}AwPOh%T47|~O52F+iG4i#zJiNRj8G_R^|GpRiKwMlZ0X+s3nrgR2DhF^K zBc`91?%M=KUqeR+hk$^>DnwlEG zj3$A$oaX#Vo$O&>`MC?UWnaRDJU0U&jnZX;S6`=~3iRCJic_~_9i*M$|F%15O8W~r0U%FJT+=@{6GG2aikRZXW(y8PO`@@ z(9uN#dxQ)*1sb+IV1&UULlEo85)%`pGu1NyyUvf+2z+q;a|+_frZ+q+fBZRK08G{r@V-Pbx5B}Z@0HLlPk8-s5dQWJGczW{^?}HPZHQ;vQtg-fV)YYoCd;Gv9EP<2k^C zpdn=5KBr`^c6%FHknet`K+=|+-2B_v*ZX<-`FOB?tuSthsz2H%@H_r@4HW_yI}19D z>Wz(jM|pj?@ddyDsW?CIF8fjrg=7TFY-|eN?+_9Zp;0YFwB=+|upUtTu3`2u{SU5} zicRaI*Vde zXo;5Hj)ivY?d=VuG<*DIYHCy@_0=hunj@tRG9JW7JOu>>6tpnm0Dc^pkU*Wj3_(OR5VCPr7eY*{!e{riv$Wu_xetoKS=;NcS2tmSU%2+k9Eo^Mg zN=Zp=2fT(RfzX7EtgQ3zujjpCe2bV00CUi=Ua$Q5GhQbS7+(H17z2n%rT*H+CB}&R zKvlXw`1U+f_N%K!E*n*o01C;P&9qKPObkOP2*lcU=o0!9vGcIf1LPEY2M5_u{{ajx zq@{s8j730o88Rd8Lmo`fCD*pMgP|=AtwXe9O;n${LjKS_3y>ho_3QMY8vr!C3E>QI zT<(jkT3$v529gu-JTH`HfQj%eCeJ#p5MKg>9Ran1NmB9x5D;m=+)#9!L5ts7{l!>W zSvf!U@!Ct!aAA3~K%H2chk>G68HYmTmig2NVg@nj5vM?~1G+#!LPi$AEE|&H`0Eo` z4pLZjx&Z`39i$g`Cxnf=bM6ihR$7kp8DhDQH-F!Uf#Ue%IR=b0@G&Gn!(0gh7z@%_ z`;BmR$qI~;L$$VoMP8ZYHkM$l&mW}Z!H=BoEgvK1Oovam$bKm4GA83Qc_Yf3kZ;d;1B9b0Jsd0#o?Ufp8w*6 zdY$X>kq?+QSOf(rpq2*0+yNXY-abAsOAm!PO6sByu)g>B2+slp4M1r|)vKg`!SO`d`X?ah?Mb-UWlT~gbmL4AC+a# z7hz%JFo6Vde7LEWnut{l@xti6h9&Gc{*;JL7|o9?6j%x$fu!ZUGAioU3qH|-I9^U9Zgwe47XXE6jnW)yG;-32ArL7%WA;7AYoPqi3bQ4X)J)J+7JJ3 z!U+Yf66hMbiSNV!QovD~M(BKHFjzy%B6JAQ(Zx)>mW5nb$LqRkLrLIZ2NaS>&#igX zXIXi9)t5Lu0T|;tI5^Ponqi~Ozk^p&O_yXqlMookwaSx(yJkSmf@p7pFK}L) zxB)7CBM_mIPy1XU#0(Me@(obEmJ-4E&I9(9*B2x_QS*dwBo&VsPXg&55ejJfja^;0 zmqfZBz!T9(cnZQfAb(Tezh{8U!TQ*$uZ-74f*D{MDH`CY0r3(EqJ-!9PY2=14ScrGX-c?ns@F% zloCR3N7f-!1WY{tT!iZhs~pD2#JQle!!WFTK3>=4283Oi=e8~EETEYla2o-HFoH@u zPPiWN^r_tO(LZxgAXycnGl1rxNAH0QcY-QJFXDvx_-GZO@zJp{KJPszB$KVItXL=j z!d^C4ixw{Peft22kcis?#bB|qp(8#$2C`J-;cr4D?JX{T`9(_}{uPEyFaXf2u%!f4 z@3b+a1m*x-NZSC=ib6wie|M+HKt`8kL)1|9r>iA-<`>HI(DCPLo<&9lsB$2Bz8P{B z>;lRIQ3Q?e(uYT@s6CXil9FP;sibT2_zcXk01MLJ20$$7vp)eI5p;8C_~`y1bVV}6 zgU28Ubnvo{zsFRU^)NR8@sTjOQxug|RJ0;eoM7sE9&t4AC!vEmF`#p;fjy^C`wa=; z1+a0UGK6S@iO1v_=tpNj&6BgR$QsfJ51SW-(Etmv?!mFcuTN2+53vA;gyLb*-pd6< zA3+hMAHXce$U3isy>J!$2Iwt7$1)jf-ULEqiTph7?mE=6AZc1CTx7}0Lo>i{h`Zw{FRcLq3y zNKs4-oJt5Agem=XGFsXQ6r12Vy08A`nF<@(*K{dH9v&WaJDBM~yexPMO`g*5v3l^} zDROH8Xh32mG?Oy>5n;j$*J%KacpNNO@i~l&0KN=Ei(A;JW@yk7j_VIN3QeS-tI7i| zZ;fV+0P_kH8{28%Ms0?Ub-{;+-mrBVS3nmr2M+@t0f8)#_j_Lxp8#q_s2QS2?~}JE z=mag%z#)&(@PyVg?mZN09efIso-%Z7$spnN3ilyim>^iVK^+1lk56NSa`8 zX##NAQ(>oj`t)f;by;sdm4@u`S_==`It{GAuH(4*3mn1%F2winw`#e~_nS*1S>W75 z9yn*H+%Rc_CT0Q$uC@rlR)oq8GR1+E3^F4&(AnN9N7Hiq5h_sL6qJ=ChWNt6Xu)Y@ z!1lZoY$;ssguUK>XA?kbOBAq<9@5wfk;!a;aVP8`V2oyY+3*FC8lz}XdyqvBA$T;w z@ZS7(4d#F5tHh*nVNjprGBeEsQ^V%~f*^kzs22F#+}vf;^OR8WQh>>Th~)uagP;Q} zYaC9w-49}*B(jhWKrCZpV>4F9{(_ANWS&bfdd$Ls!6Vgq8XC}h^_FO|&^+_{_3ON_ zl~5%v($UfV&t^D3QgwA_=_e~p=K4tP+_{71G?0LR(xstnlpzHOmKvu0Fai!5A`K5) zkbrl$x_^I&DKZ5q&%5=@m3jh0(1+gHFE0ev9MQ)&G5aY_rB4h*oCPl z0IkrxJPsuB+mBQV4UPMOc$ax_JqTUQL5?B>(2oYTy+U^?U3V}5Vi;P6dC^WB7^b;7~i1;1V z4y}Wi0mV;$Pskg9>qvFjoa?3*S56i3SThR>4rT@{2CT0Q(zg{#lmR;kR^XhN23cvS z%8?NgWjEsy>z5VZ~laoqdqua$sq<|&~X)uu;BPj!K(Heax4|p;Wl2v46P9axs z40GM-p--M*0bmUQZgX;{d;paqE;D$=Mm47Zmpr8v!bej=fTn#J)Jg0tg;&tCsi$=+ zsj0QY-6mF7(R{mXx}R<~GNcj&9T;WJWM#?`#X?lLuZ*H2wZJS1U{>?w<41AeNo^LP zVfi>GTR^C52y7JQ#mL!ImfbH7BBThM(?zpVK-mEG4Iz;|uX1z0Y5ONHFYmUJfCF%0 z=#!5)hzx-+(3q;*9S?-20Ry&{=$l%31MRy6p3pIXI252!7=zMPB{ni3B!_cS$Bl4GmjvdVH%Qi+&q-x~`{IAx3xL#H-&k>& z?!q=SL(O4|Q-r?@>yDu4@WC!`J42U87D)`|(aLCnQ`C(>oJkO4DZA8^lp6aB?(kW4 zkf306tlOq~t(G3$-`{UL@^)Div1|oKpBx82V51_g^CXxFAj)Xe2(=3kc$rn_Gun~2 z00T+^5Fjo9+zmi~D{M;o;;Zgt#G*jF1s^TQ(HimX)>c-?V+`c&Nl8gb^mp5vkyKYo z0lmq=FaoMHx-|@DofrBqm1PfH(ylYbNJB6osECBtk&%y0qTcl$!rt{vy?8l7F#RPx z(Qq=g!Q4g;77A_(YU)Ua+#7n850Ws_YrDJDfU&TcK*}p@5{BlFl8FikQ}Cbfc?5v0 zPGQWq7Y5?H9=W+urft1UjE!Z&kRan7==9n{x;i@Sh<0M2)v##|Jv!WTMzSr^b&(PJ z2FdBnoE*8mm5-~Zp>U!ljUV=W`49x8(-;{*D8WX+5J|yk!*gQM^{d$axBH9W_wU~_ z;0ua{+6lfsErg~44ZMk4l(LJ4tRukK9!kE16I8HjYKJC2D8U^=4gmj5xExFE-`~ki zu(++!S{z#2z8XAWhQlyHbuJ#>{OhaX1?v*GH1+6KBvf{elP)T22e^?NMqhY9>B+^F z12>>aHqTrprM}@7bO&G=%8tml6rp8BagAx*S;}6D0M==>-UmaxkZ03lkA4M126^Id0*ST&uW!efFS208 zc&(Zg05UuiSm!`DQFrvZ-VVXK4g=^+psb)UfP3*Yur_4Q0a+3oY!wQSV1PrgK#2v1 zvm~FTo2V97{64hC6*K4}kgGSK4mg3m-nL{H(x(Xop>*#JrBvSrJ+62pZlZZt2;upF zb5e&SpqaFr`u)4;E?9W+lj+*O071p2PdWQb$&Q~$Aun2@GbzEQM`BQ6+d8Sq^3i!Z z1Y|xI5L^hrt8URwN}0x63ocBAUIN0y!<}GYt9+1lZ?E(D(e+}0H349AJGNZ?)coqh zxQM@>Bbg3fY}h+3Ejc5%ey(9tW3AS3Hw%pxtYX_S{0IZ;Y7&h`vBCkaY7T7zO@%S9 ziJbhf4$U3*>BA$%a29T+P9i7C?&EcwyJ_8X;8+ZY$pv#zEWk|*D=RsJ$_M)QlcyFwRL_Cj+W}fHMU2<>f;>apQ%q0>D`r=%4bQx8$Ug$P7@(;7YhZ zxHYDhmfm_4iJ@@%0~QT)nh?;_Ux7AP#_+6-^GzM+qT8@ZNAFHRpP8#_G#AXLNTG{V zHs`y7bSr0Qm<;VHd0CxPMiIsfFeD%TY%l)cUQpzUCj+q9G`)^* zpZ!(-3rq!-Yx4<+iP?ilppeL7wDzVxmPfu+YtI3_mlOjaAh$>Os_7>-NC6JmQcA9R zF2Ju=pb_MNqLKsZ^$Y--2cbEn{Yf)DI{}r{6x{QP^xB2RV*|(?4GM}SbdLw2GFZIV zuC)U|1Aa*ab4{@8B`wDa_CaIp0Vi=q46_}QYwG=Cpn+MKQb)GJtg8X~>$d!Xw=Foa z5Ojd41~T)H);_-T#{MS=05s)Q(+NOPX!eB1EPq5q!;8UC{F}A| zyYtF)LMsSB>&L*jPhqb6?3ecl1HVv!Nslco=AT(R{19`9=K$)fCK-OmM>JxtoM_a~ z!0^rez82qouNxx-g&RF=3c-bz&)^IM9cb6oprX58j>dt(1|AWSe2Gb2577D#(i7HD zlrl?7VlJw@%&%Jl;|w+i5QWatA~5m*IhICn>g~dNtpG1=fVKffl_)sE0MeRKfrlfE z5dZ}?g1G&3azmB&bWKf7Yg4TR-rEDZd*GO-w7aSC>PaPad0hqZ4kgI5@Tay2s5jzwc~n20vtles2Xq5#a|)1`B8dY!Dg2 zmD;}v1O)`1W0*ohLV_iK1a%x_QxiV@=jtpWgI5EGvS8-R0W4T&i6dS)yrkqR%sh$z z9<0_!zR-^I0rC=Y#Se@@5d;YU%sJCcVO|T7HUp^~F$WUvSqvE&nGA$`o*v#RIMdj~ z#88bP#pJ-~Joqy@I-iH8-bg)%bbgZTSo{1K_9 zJ;?Q?5FL)_%eaC4EFqy?`LEVrip#Vx@EW3Zo?G7R?#g?=PO*EHpZ{IY(Ou(mR4%}9 z%e4c{LHAG*kOjfik@R_G6Lb%m z*%%tW0PrA^$lyKRnkW4We_{wl$$2_Da+p&+OHI86FfBYc_u}J&ouVl{Pz+jndSW4H zo`9Dr6;;DbbwwOvdPQ*WOM{0nXRquY^tXsk9w4n@U`!WvnF*WHD>1EShe5m?8yP83 zWb}s55)&1{WYh_Tj)L!2B5$SRd#Xs6_mDJ*e{ikg~a z`k%aOvW%`?y$ZsDEbwkw3<)+i_SEw7EjZxuB09Ga{MF1r2RV#*61Jei^mt{8asTvS7t8<)7xLQ)$!73rMJM#WN^`*1vXIn{=F<92xxhj z4%{vwuvm1bOI>wOe`n%|RK!}3%pKo;= z0gz|D@ke0*7emCsTu{w%L5#!#^pj5-8S;B}R@>uO#8*5yZOIVx&xg9b1a}Ip(AW{` zHXn=~(U2O@?ZvJ^ZR4JutcC35GupIb5-;&74{c3GA24exfja>W+`y?O4PABFDc|KC zsCzJc>;Xe8I!%ZE7^OIU%fd*Y^hg^9OCJrt6&@Jgl*c4t@Bn%#f@biiE)~4yEa>Yo zZTp%1=1n9t&Med;z>bCv%%D~P1>~N%V0^~1`~cLPLc#Knukh=9|IvcC07*GwuJf1- zTX)?}kPrdYfnk6sQ$4@ZrQkCq91QUNw+|H-3tE~y6p$g}@GMRuN|@{eXkxy6nGjj& zfb)vb5C09!%I6An*S(Xw&|`e-GLD|Cg@NG z$a;u5jxcLb8qo4Ve4oQ296O40bvBJ}eM z3pzj9IN;U6P|yx|@+Wa{p_LI2H5L|@9yp$nTNa$|6;Ydjhq<@60n-`5p`|Nj_r(l! z7vf`M{gE;7%a_Pyb-`#j34nB9m>45FFJNw-&42no=jU!bE-&VcU>D_TpWBRIDb|T4 zRLa%V4#^ROcItinZx@a{-4&-$mPh&ytSI8c5U+uHmvxjxffyr+8A`wTg9k=S!pKeo z$IsBC^^J_>QzG_mduJZF@YcEVMc?kny=z3qvSX*fJ--x8w z4k0q@fho2WW>Cs8aFX~G>=xh|r$>(zAUkstGHnT)8$ee96{gyG8V9@*q!%s}_wn0| zWO~z=FXs;z%F<;z_t#@=zQG((3C5NbF_}}^FS%7bJVXG0f~(13_O8Yf0TIzfU^Xp{ zjZduZ-Yv*^3TH}K$ikISJ)szjz^LlNhjfkaWFfO7{wyxC!U3C}hp#?;b^(yS zv-&Fr^uAsmKUG|DaeP*;533-5`~r7XTaO3r{K3UiA;N7UzR;`l>$QWv}dZ6(m0rd3|o(^CvYg%XeAoQ;JaB{A^3 zmN2Ny*DbjK(Tw~mG<0;+=wK++?9bP!?=F;=@5(8mNevj~Ef)FZ_RJ;b$63Z`|KC>ne9zbv&b-PQd0FpG^=AD^YTL%m?R`p!4{AX_5T^1tjpca zw#T&>$LoDsIsLu*vsioI)D`IwJROARg+ce6nwe>Fr%@iM_mi4*sbS;diU895a&~q$ z4hA!E(0F3OH1?tfrg)2IF&l?}AGSUOAPX!al8u9dTt?Ejj7o4V2%yg?CYW~xS3dr` zaq+yAuXk`eZjoudiF4*fz~!JC0F(-@)c7qk3n)9{Np1S^|A#UI-_OBsH5d+#`2TVB z-tkzs;s5YuMua3}uVf_@A!LNCG7H%vL`Er!A}c%DQbNP1P)H$Uk4hn$c1pu2MT+Nr z-rw)<^*qn-e%*iFcX3^x&pD3cy$;pslA6VIm6?}gvfmzFnl2q#qE^UGfQE?8$LCu> zo`rvf)r7hk;sL0`NIh9>X$YZ&0sPA53wEe-5q*1$515YB39i7 zbSr?#3-+=Y>AWSUC69&Y^WsO}BxLVi>XMOmc%xUIl+U* z!uZf3UVvRhT?i>=BzlL;NB_`&&NQY+_ruW3=jK#6m{ zxrqvg2$jD9!r)jXEaJ!r@*5~H_6u*@R;KOxhHM!~z|OOoPYj1q(NHvBsOO_~-56K3 zW4fqxZ0z92I_Z+@fBuAcQ#H$>E$w*ze6?VHt>uLJ9C}M_AvULvJ=wTsb)khuLz7;T z2aZi-=HGtCC~eC8LU0L_Y$mC-T(Ht-fBlM1O|?kzFV6dKQ9Qn0w?A+7r@vbj|Hgm3 zFO<%Q?&vg=4-Ne%b)WkadPD!ou8qX(fQR785baD<9Yp*=(hE?=7~1WqytaM?e0;Z6 zG&J!P@cj3Bf8f?dP=iy{!GjH`*#LCW0WOhosN<#%zoy-8U}*RRw|?Be3hnntZHr6p z#uXY&+1p^1b1Qjs78WYt5uI&UjIX(*`S z&Krwcs0nV|7==bNE$A!UB-ENIj*Dubw89OUratrh+^FoLxonoMxp=DN0mp+kzgrcH%)|o0 zxP~f}fss)Q`D36CpxeA|`4938QANe77FRB2xt-g$FBklHX-QPmP@i7Bbm=_mIzmFV z9@BrzGJx+npH_eL;DNfz#9oTjw{`j{(BG#(l~K;GmkmL~1=tSe78Xdc6@eo;35fSa z7|P*p|EXP8AF^}_JtlP+=|9mP-2>66fM?n2H1=P|`c~%u)3JvBhL2z`g$;YF#YMj2 z{(VuHHYUiy95;Y=jshu*@L_DOxYijc)3jhOH$baJrOBh{W$Dp?-Mv90Z$EBq9fp)` zY_AawLLCOPWD@xLOD=rxi$aUBPrRlZqJr~JQ9h#h%L9C zau`?@6cOj)`;+1fKoEvZ%3iU}uMRf-$V(ar7$*_vbj;tdr0?E`4M5LnDW1L8!d(Q_ z)zw>T{(~kxJocix!&ZPRe*;BVLz<2p?xOS?x1hC0Eh)mr&;cSlu5KSW3fKxZ`RM!m zr(3$)%mH+HsdU~xrUqB9z3uDR4GS-DI}v?3aNdY-riL4#<;Ss2?y%m$!6Uo)40Z4k z^3in=-Urtl@x8V#>x!`x3Ni?XAoWP{~r}Tn$#j&2TsT6M;9N>AZac4k-#Lk2*PIS*G zm|aj|BOc=JSmw@@y|gGs&_j+w6YBvPGco=MBm**Aqyo~W4eowrW!bmC^dKv`g5DP| znxLR0*f=6VSOrbX9MrK$rLFS7QKNnetne=?yo<$Vn9xx9L-y0N8iK)kARyWR=je*QSA$RL{5AiGMiH+B?eXb@W~K?g8(xP|M8}CmI1i{ zq8$r)2G)T3{!QA}-*=ln^%;K54?Qikhwi)5%mdNzVv+pU(&d|4HKO<<#!xoxS%}Y| zd&>ECQNmCZR|G4_Qt%DZ^EuF`A(*xtxh0$;uI+fh75E@TLX6%jN#&ZC+8e#19%of? z{Z_O-;G5SjY=1@U;x$k&LwiZuHj%ZQ>lC4c#-cR*dbDgUUHqN&Uo+4>c*X0dj`v4$ zOIcW{<$+n^M;!@UZb<%=FI1y|3KfoQy?u;m0-y@T#gL}_c)wLi*RuYGG4i)k#q(Hw zL@i_5Pf`Z1>=nbIsB`-CBNr)6llw;pW&7_O;zBKX7#-+Zc_dw+9gaXHnGd>6`SeeT zuiw5|IUKWkxOAJ*-%Fc0y;$mMGOht)-G=aK6d?=|j~{CQ6Ca(NjKZ$f?)&?S16tl_ zXhI2H0!bi5hqs?>&q~*~3SRZl&dp)daeF!OBi|=-x_6g~)B(BG;3noxIeH=~PDB*8 z^B7-^o$T9DQ>z3`AyQJx0s2u^Rwe+7cg!9$o?Gy9lKvbz032N_i{q>ijuxO*@s4TB zA#^kf@HRBB1TC*Sw|W)tDo(L7(R3Yo_`oL^AOz*-p&^K_u2xz>PcQPN#*{f7pU@zA5n#;>O5F#SI6b z`z|p69?H07!}p54?AWCOu__UBK!-w>s5`9Nv1pRU(YQb;vIdab>;EvATflnIDG~~U z@B!$-{^w&k<73g77zYpu+sfbIH&%(}+2qbcp$X$bE0#|@L~P_~7&iMqwDZO`#50J( zUdDcZ`rkJ*``;e{g)41nXecwcT-Z0$BCj66{+ZhxqfI5qh9Q1~|M@BM%n?LziMyS9 z_gOW7aRekPlDZ(S;&m<7;2EBa9CXAuVDIs-wR)PJlQZ`6h7mb6i39`B;=ebu)*3P@ z6lhUI01b}YI|hk`8+B6o6xr5B@uW|HDIdXCAYb)uzNum)Rw|Jp;0Keu0vI}u{P!Fw zSVs_{(IY~KI97*PV(>{Ku@4pTbe~|C46*2p;yL0>4u|)a{HP09UU)z;K%L1-xDXrL z1pIx7B_2Q*+IcnN??mH8WMw#H$ipv^3M1dm^ZfwXlxUg5o=@G!cWVZCLi}oQs)DoA zgHlY%dx*jk^TG0_M9E>amh{Q(&9Gl`E7Y0RNofTVCZnG|T>!k`3Oo-&GZvgVK<_**71C?xvr()CGetS$*K=~} z&5W7yOoG~N!tHwzEi7U`I8jRKp{audg<6}iZ7B9yPG$i_yoj??7-sYcX$EF`B*h26pANhQdj9)t!n$VO`KJQ< zHKAc}=0+2EpQjxS){BO7=MK2y7qMx}0f~p;jnlx7qA8Lx0>wV`h_1q_aRobGgGwfX zNAv?V9XzTiB?-?6^4K~qsX(F{)Zn_x%1R-QwR0=~&b)FVibI?!o(B~TMNzvjocZ-N z+)`?*KQDS3R5Uu`sm1aNIF+b(?%YY-H4wO4*Ph%;83Eiz1dNEI@I#e=Z4x~xAsJ`B z1*aD%*jSg35=sOmfAr`PQLk9XhZn@9!gzvBk@oOIl%-TFYP^bbt7jE;^~@}-6p){U zL%*@PnTF{5aRdQdVGte(cnF2fkzIS@3i`<*f?S3t(^ql`{4BOMg@- zSZu^3fg0TJ;X8A#RZIiWfZlt68xPZ}702%^aw~`uM<`O_)vXQjtwWCjOZ8+XHOID#uW!NbklDF*1~Vrhf0IAs;K#>DM58lju*0NLz}31F6D+q34=BR zL@+UEHeDQ_cXl?RfFI!7NZy@6QcijH1a37ERn;7Em9)0)a&nC=u^Z-oezqXqB8WOE zncY!923>d|1%?k%SW+^!b+2UHOEIhqJeioXGT+#@2spZZ=s{A_m%<{R^Z5Et+%FxU z8`H&?B$QA3ykv>U)dwdmwX0zkPdp@&Z=y1yBhtD5r0T z%SE!)0qqAMY~gyCL$@MIn2!H|WcpeLhDIV46_IPLtfH5 zw{Ber$dII603hf;aLi3LaBY_2<&gzxddI+?_MPvKzxeqjB+JIo*-4D1(D{n&;PW&` z`<^)?XBGBe>wMgkRCvv28y=rf+7V|Sl`BKv$!J%1x+H2jGth6@*Pd-t#L^E$22p9` zePeiyQ!ScQZ@9B4Vf(iQVmD$tN3w5`xF%6zt9;uzGiD$&HkOf6RA2iBt&tW(6KueR zuL7iX4vRVYvNH3~_8(1IvwHEj^`l*kEUx#R`t+D}Qa)~0=Fl~pzJ6=J>7e0xS(*U3 zP!p+YN5jV1;?J`-`+F-l`TbRXyZ#=@93YS<;-wgXRQf6Nwc6UbRB!|7-UAM)+YE=k z;ZJrmlOe+ZqA|o>*z~T>EAi0cvhOvWH6v~bw9>yeJGVXJ$g<%aZqEU+a;dLx%VLuv z(`9XTXV=S#`8wfIOh-Qs=|(%tp#%cP>x4rdNgx>owLx$8~#he<#`F@J%x zhPKq==tCUrcLVO1d>f+r`I)J5I+tmNMzHtq(Wg20p$FR=r{Q`y zdvJk`LF0fJ4U5k?N}wIR@)-8^G0;+H<>VAVlSW_@Qj4JDOa4|aQDWZzb^n7L8-ze9 zb_TDmsH2-N?YROUKhh8yoxM5rjJqM2i7P1b$`4)+>0#a5cF(`fx{iBx-ep%oGMbfg zq2F`nhm4@!+!vDJdQBnS{@tiMjb2l4rc6?-yLU6t?m&yzKGQi0--*)y?^M z;sH{Z6dpsw;7aM`sZu7vIBpV;d;7N;&9N9l;D&AsCdJ19<_?3!yg z-7GW7-+t4~EB%Vu{Xcu$Z}hQRn+$}SvV4s`?Am55wCi%cGOQ#oU!Y(kj;<6g(5kT;9^k9j~`Z^jFo# zcM@Dv^2(2_!stZ3T#dIZ-08FG3sQd7CU|t6m93sP2kVwopLL|@e*NJVlDvB8?!PNX zjtFf|clu_#k+)gxwngX#CdM z%<|z2G5y5;D-+mEpicFCCawC<(`k-#x|~?fkKXrBrF23VZDTRkdxlwdpL-la zY5(vzP26U#S-#a+p22Lxb0TK1Q4CpeE{?~wx>S;ub?la83y#K(O}AdZN^7fHvYh|1 zbE^;EI~mQ@7q3SG!kaeSEAQ)OWAsg5<=wQYTkb>v{j0k)=KXaJ{#xe?Z`_E2{|Bgw zT|JfQB=8<0vQ<`9bwS}w)Z^&eEFa=#O$2{%F*P-ULoxI|p=5dTCR$o3N1jnw#Df8t}J{Ut+Lkyw#@M$kmjOu}D zr(DaZRe_!oEz`iF*wlo9=E3B~YJCAT3_KS)!gg^5mUq17zABoSa-}#s+s~YjtGB0D zN=EW3Q(0%##SG&HagA2JdbUj?I~^Hng;nI(IQLodDs5=)F*m<^Ea3DXTK>}$W1n7h zB~=Dtb0O$S$JUld>DW64plu5r##T8{TV8|enV`3r=R}Iyv;vhYZAZh-i;%Mq$&3446G;@Hs0R(RXHHJ#TC4- z>N?0q^t0M|FSjyKvP7 z`n@9Noh7};g_-Ni{~E!(D@>|v11Y0Z1H~SH(`))0?Ekf0GXJt$ zJ=@~$=@f%--uy>3XAHO) zoIJhBd1TQz%SLK&^`6g^8m&yfj)TCp!or_l*WDA zbcTL)v|5(!xUK$&ijUq}=^3|5HuIZP&n@i!G95LN_q5>=-)cl7-1yhID!)*FL~RR= ztNKfMS9_`NcD+q?!c{3(yQuavDWAHK{JX-=POUg*)I#CK$6B@7pzHk#>B8yM4K>`N zTjHCpyLob}Y^PC=aTA>k9j)MX*q|Hq&zPxj%H1jFl+=ek9?s2t!8>m|TPQGH$y8XD za2)b6=e4KX%p@L)o7-P5aA`vg1LN3u`{zx53&!nl z_X?bp*=e3;@lsx)&#-KD{uNUU(@^Ec@i9jA>zg|p4jjLiWj;(}vx)D<^YLjLdrH{C z!PW=qCU4_}Q>h16%vV@qt(fG+Tt=3T-#Ar#5m^#|$Ded|##YHIL;u$T5Wz;WRu!c) z(XF5|h{35`!OxY`m-KTf0DMTj|mos%F8l znj#+u#=@79H?Y)OKhkM`n(mZyGUoiMe^_U~$^CCCCeKaNHa~MM?|#d6>+GPu;4PU* z{*R(xUM<;=be7z`uf|yE4pLP z%F?(Wg(m-YkIksmz2{Q`4g>TT>fUltDj#&~UMr)zI^0JeX8vX5vwbi>_vNlH$37hn zerfhrcunmf_t?UAhwSzXLD!>xz5fw+XEp9>VTH|Tb;0f5jTJugzcp>9s3_aMAC2(U zY}_hLTli+)I`91pdhrY%_mrUVu`nf{w70iJNd6sAFdVA`8_lDE@Murne`|%4RBrq7 zeD=npIq<@eK!z;X#=#1)%4Y5}*7lS|1DOq+_yZI`^8qDYJzgrW!;%D+?FZbKZ*rc_ z}hI7kJd^02)(D*J%;S4?f<W{or$>e}7ehQXOm2Fg z`RJ1QK=^~+Jlm2r@4}tUv&;8?{@Z=fuj20S*D95tgjP4Nw1zI(7o7K3qb^L2J*(UO zw?9I9e%@r7^YQFT$U3PQf18e{?tV%alY4fsv@FfnYThXB(8xRb!(IbBruBWi>Zc)T zt~aap=c=bA_A8{7L_9y+;LN>roz>HzE0jd4AI0t;qc)bQ8OIGG`$XEA+e%_C@I%k&RGM0)bXqWUh73Yea;?`K05#74@jNSRkHqTGH zqm)7+Z8`-oG=xl`Mv3RxacaHsnxInE0# z^2N|}7(N`ePg!1|GuUM2`~$V44dRnYfmtGbJr^=klBKb?`*OC$+Kh}0FtV*EZ#^&R zcs-)%Ha}3! zM9+Cn^h`pGCWNK4cP3 zp?rJPH7;qbGu6lBn4Kp1Tf;4-Gwjp3VCz5$M%Q+JAAzV?45VaTyAIS>Rhf4%*SO|u>p>v{)0L>uOn?}fD z7!b75Pd%j#Af(37^$V5sT#}q-Lc#jqE1Cc9_4@T|glG*e8m#FmwYnAZlke)fY)cKF zlE?YX76-03by(B8ZZ94id`WN8D(5SqwXNNoa)UKZ_E@8z0nJ^>1$m`ZmxME_)sDt{ z@W1$2udRgIkwOH|t%S5x;OvN1AsHwlky)dHg|g+No& zQ+W=qlkS~)sfJyr5}prc74NN|_zRAr@jybxwpu}LUMc zE(uL&1|(h%=@e-spwtTsNz6oy%s4E1px@{zBruX<3iSsvr>@`I|FDgK^TfJO1nUwU zRwDmUMp*;zx+MedY4J$ z9ixQu?rye)JFQs_Ds;>=9-rA{-lj(WI{C0>NU)$ot z{rok7O09{e?-jm`cndk2ozj1>_TrVMk>~1v_O5J;ecSMY5_zq~)vrdPVkKRr{rQ*8 z+hUqt_{{IplblguSg{vWU^EP6^$RwgR+~S*>4ab!4dp_R>EGqmCH1Xxd?V*V1JX*) z1w!2edT$DwJX70P>B3ezmX8aBWMZ<@qtb9F0>>>i!y&*Pl96% zgle4Y%T7AW!jYLjp7s`LH=P;k2a66Q3+~oKJk;#soNM}>bb^s0(1M@b;+R8Yuqi#t zIqZMHuZM;+Kf1YH4_{ZXJ~u}5p*^6&_2GIH9Ziv&JwHumB?k8CW<3_K>ZLn)Oeun? zec@tuo7N{S4U~Lw#a>iS9@~#w@30V$Sd=ljv zQqyD9?i@<>jDM`qrZyz2Kb_OiPs)`|Ue_}&|7q9v=trM>&a~F>1)7Uje~$?8n~M}U zIlP$F;a8*Sb6gUD4Xdlg=_6Ij76)&*yx8l?oZS(Q>6;Zh*Rxi!p{Fwze7bC=E0U6S zCe9hlq$fv_32(R!S`9K-fZ$9>GSPyQ6B=GKi6%zG2m!Gsu_i>U>d$q-{LcA?(me$$ zC!-6}bWwN!+9q*4hV7FOK_KPy_M2yp!=uY~+?NgfJBBIrBAam1_Aa-3%lON;{c_XG znzjm3{q&N4eP(rnsSQoMWZb&E58?egx>`P}Kjfqc{iF!i>m6=36V95o;qP~!;;g@@ zT&GJZ<#a(ymjCorFdouIwL8=P6?fOJ^A;n0*%6wBQ8}dznfpFD-`sCl1*&L?|2)h? z&BMcj?a6{`C`8B#pcMS6CbWl4KC7e6WCG0?ytzozHcL3Q4$%exqM4Z|TtBXD`$?H+ z41@K}ar%^7Ac<=N%ub#L$rp5T5+ZodjlvDA<(dOWCtKRuGE_g4R9$fKOwhn%;L8YD z0g}Demc$2B%D?KB%H!+gd3 zgj+3E42^_>lk%2S$&-zNChVV%hHZIJ`Qmtx(rK1%g1Av#)ic~bY-F}so~>&HNKixc z=CQ`Ypdi7$Pu?X;;si3;z_&T;Ri+Cr{~9%?*_Ix!^brokEkwr0$Ghk$!dEvv9fys; z`T8V8#cT|;ByW>QbMfEE-AZ!%8H5S8MIpjyObgi!hczSAbU=UA&i=K&Gj+oNI{01$ zHi4WgIUynz=dz(bwV4_?J0w=dZtcldu`tOn5Fl@pp-Za%cnEo1R1)yjB zq5KnBo}QlO3{F}XEwZWYG{>C&jb++x7fh8uBT!f1q~TuQaOf+$ZUqk8i;CK$o&N5_Hza)5oBiY(0jcPU0nR z9ml5Vhx<`IGgtqAU&wYHzxfrdG zaOh$Zy;AG*IE$C(&%4M+eEg<(v_dNmymKZL-cXW|`wk&lPvD^=S`{eCp~?mdNDuCK z54J-J7B~{32w(uKN?6EeMobAJN$Qa5ktF~H7K=t+a$TJoNO}<@BLGnjLzV<$hm5{G z)iP70GYPPEa&x1@z$L_Pr0>{+gd9XI%KScy&DV}>4HBWZ!+VG=Ead6=m+nRRk8>@l zgZ7uuxgM9iS!O`jO!Z7~T+}IleUHoy58q6tvRDzeCr%%3a_rsQIOO}Z+&PKu?76Nj z4zrh~_vo!%Q_6DIR7^Kmh55u1?XG%ec|W@h7sJuX@uSYKm0f8g#iZWnt+s~x>}?6_ zOUQX0l?(-p2y~K&JzER?8sfJinny`dPekQp&4bP#M0p{QtWVIop3 z0|OEh$Q`YP2t&}UO;bNAKvR$;cW~;|6l07tfLoKqf6-D?UB0!?>^3=JNm?)^Xkj|t z@KP=!^44xv!KtYaa>yU@6q4sd7;y+A@PMV&^`;BApIMljs8&+S3uXP-@WJW**^9}- zUcZLz(>3C<)uitmN|M zs9I6##?)PJJciZFdKRyn3;9J*-3-3fR1#J5Zi=f;UvN<8)0;gUx&vxJ?P`usu@Ifa zXa{c)q)8N_zN1jU*qWY@pYl_N1SRTG<*i${MAg;#NthhmLKxG_?BnC3cKRm+qz|o- z=ftN$V{VVp111Bv^v!q@#Z_eR7umi?M)m(6EkNuZJP4~Bg1e9aMxuWW1E5XAzeGO> zApjyvN8nx~A|>_5H`ZbB^Rlv)V&nPMB>#Jj@TD<1LX3zQ8 zqXI_@A6M=1Z@(qNvc!8W@Rr(%?|BxZuFy>l>;ch6PXS>ib#*-8VU)NzqI-$a@n>Y0S7@=18ffwh}OhT0Jk;*Zlb)p`oO!ne2EgdoQZN6at$}9vT_*H*`$a~ zS&*?m6u8)y<6R33Gc&{-D;1UEV)UNIH(1R7-|zs}~SF>zJe;nsdjaprJdS`Y8G-6wo z`-ziVt&9u}sVQU!!%irD@-phPo-4b!u)m4_X%3l7oTuNzErw2*6a>T-hG*;qnuutj z5Vn<)iXy)L$uOfrwz2=zg#s_BKa5V$)u$VNpMZ6kYH=QL`@Y8k!KfP_D~p@xF|hrP zes9;{ygk_E_=!@=o?mlIEvM|>)r#e1xun*zu`~AGEv-9a({z&V=skgpNzK09YbYW^Ji}`N9l#G_9>o?|W{(mJ9TIbbJ5%kV`#1(Si-AJZ#y)Pt}Hv0C1 z%Fv(q^$8|!<=;}6Qcs)neGt(!S6g%E@l*XCBLNXXPv;Le)7V{4pD2h(eleAOe&6{=S)N$Uiz&B2EH{rj)86F9_7-9pcppqrxJqT&Za|b>}ZhqLB z)&dO8vSH0XI^{eg@)j<_?Opi=%7tzKY4dY%N_7Hwb$cW)C1)Ewp6ub z4>-IVY%9rwL&9x#(fviO5>BoTes7m{-SYYr?!9lD-ue zcOK6Sbw=T*eM*q}c)zJ+B*AJ(%ansko(usb)fEX)#*JF@@F7t|lFTj~zC=_ElZ+GM z4@e##$(V#BxEX2uaNtQEcdvqBY!tW?B@BPZ8GmYVcy;BVOQvdENgN(JiIPBQyC~)W zK|+^)@?9?965aCDzvD*tPkc6)v42$g(#7-!swch=E@6U44r@9|tS%ThN$3D;`uamr zlPiXgJ1%b3YI;8J^e0uhHKBU$T+6DL!HAU~J`xaH_M1OF@o}L>n@^vfokaewKWcYd zLQSR(D@Fz{&AbV@`+0C_XjRDJ^Eb#)`#Xt{VzU>_MnHKFjD3L{5uCjiJ54f7Kvhcz{#Cl`IGzb?qP+?%WNb4sOR zckSX1-2XpCo0Y-OeJn;mit_K=NrWbJ=*^o0Z~U1qu?dt-?DoMO4=n);q8pI~xu>Ee zzyN{ReqV>=Ax$g29&aD4)^JGfeZXj~V%6&9vntcUb5mEYbT$oM`7tYgK8EXe)!e5X zmZX{9c7P^;iN_oNbog!kTm6I~v4LI3Nvyfz8}-+EepdU(0rBqUY4`4J=bl~nX}RRy z;Ty-V`R;A!>_34PH5v&g(2g_WZY0Mca$?uyIsRpJf@Z^UN2fC*+G(T*pc^wQZcpDU zHsYtec^|)x+##c2^d3Z4Mj>9_ZE-V3o40g5H=2FjnxH9hE|=Tl?;+b^)94C;`8mVa zo0tiy*yv8fH{?x8iDg!g>8?G$^mHR`qzJ1uyThC(`c$VT& zkv?xzXB9_k#)Ojfa@qKR-wY3H#ABJpl+|gMk(MWGzu3!9Jcs-?1=45WzNr65_$|U$ zdpe!vG_;3KDAKw3`VI^2+ZR}{;5SkZG^+ z=hw%B7fa9owcBW?RqZ+g)s<25L=R}0umN*c>O z>4xryC#TUWt*hpPt7mPzZC3;m;t+f_hG9Ew@vUfL#1GBfp^{j2xIF)ZzHknYk#1~c zMCkYyeId3Tp9QaQR=fEY4WGN%{d{X}+U4y2vyTk(QlduY8Oj{%zLkAG8gMONcj!xZ z%yS7*4z7Tb9__Y8<%hvq8-&k!XiHptmTfvHG`r4i`<>}|v#s1Yao0+0m24lj>}I<2 zcJN7`ueRkt-mrIfTMb=sdRo!$*yjV~@AU;g41d&4JoIb%TH%(qPby1`XAf-W;5w_8 zCV>MvyMWExyC34LU{zf6Sh$6)L3 zQv=Rle*Q{a*nO%$za)As)A_hPZha9YBHML0W*3)iG&dA{dpEw~$B%Na{o=2#KF0eprtgXt&h1=Gbf+Cak#LIz(R&sng;S~ zUVknbs(R!-H@q!0K$w&2*2!sY-6$arzga2Ubq9WFLo>v>)knTSplJNN)w)QBfv}ku z%zuCH{!S&(ci_w>DGk^8=MYxbX4cO2L62xN%9DdW1KP=v?UtwLzG zqd{}pqzXg*&G78JY^NzH^}3|8`hWJSx1+*S*UI^u39#ptIzK(EQx|eTiM{p+jo`8R zc=;nbEJ}I%V%Dy)-u0SIcZsj&msC?im7BcVA3c#C>&p?Fjvm*fmD%J;dsWF(S6;;z&0Nv9-z?W{ZJ_|GgR;mj+*wgjSm5fuW|jD z{!fYds-2Kc{Pe5M>nLINZ@csp`BfQ&IH^`^d-TGV7rtPAFarqDj>->XV=k01>%XJE zKOhAZi+A}AFb(p5K=SFH%gq0|BjlXI%w@TsIi>ZT$cyHs!nRwJ_q-julY~EI_Z#f7 zaz9|9cYP_8Z~6-B<8bbV+XE5)$JaZwmpT`n3K!GiF;&{{XmMFgR7LNj%e(rL@P>V! z`NwBmiheMJ>TPTqD6Baj{nCZyRmcGI`(>_iw@RK|JEPHhg`gQ*_1Cb zQ`%g0aPp}Azm<5F4|SG0!}7jdaRb{qTqkaaEN&ZUe^{?d_OPD2+sVFD#=<9lws@?1 zGP(#Hzv1;G^n=L$P0`I%L1eCoJzGNyVJo@ zgZ*mh=+f@#^2!ZQW}dUYElHY>nVXOK8IY@*nW(CE=BIDnhX&Sa)ziMikw$MnE*m?h zMufTjA!~Tye$K2zI*kc`iT%HsBPJ7Rn=Ly!C2Fmr-plOTy&*oXuTfb_Fr)P7d&6ryz_V>2{ zRr=3|h0$Xp-Pe#twG$1(f1#S+lMx4hiZ@~UH4r)YQHkzW*T`9}BuYhPZ5x_>5T2rmqI zwN8yWZ|S6AY1~@VP8mtYM7Q~U!HzlEir%}BjMoTl1DV8*6MBcYYXUuqnL+4V3z+$Q zTl-$=hFM*<%yn^Q&A+!O z%-+~9o3WA`Wp;dCT1-w}ZhfK9gXwKGYB%>c{LvDwvHIPwNxRwdi$#B$iBFgJw&H$tXu!TDqj{gr-hu&-Ztyxdw;aeUxht@_P+FGG;Y*2X(VIm&e=Tx}mklmlgTPjGYD ztlmu9Z(PUVNPK z?iE?1kolELO-e_%)Y1DU{4ZZipD*WC3wg8dVw2A;J)n{aU?5#q{w^tpEXskH;CXBo zZ7eKu+qP;gVqZWB*A-04aaWxu&lrpVoWR+wlZez~hoRdp9a{{!7*?arh-{jj6?YIh^1o_7&vhg0l1PG!EZZqeKC z^QaCcyPzy;TuBU>a4&Hh&G7b^W^>Rh9i3IOr)b(GpC!; z<)G{?saQc(B=%_EOuv@1z?yU42QzNR2L9Byw;p1sIO#|);~h10uaUyEX480ZjjC<+ z`fD>;+SkR3@+I;j`f9uM;_tn*^jq2Kle?sQtL>3dyh{1%p0F`(ma`7UF$;Y;&P&QV zJzsp8X9RB6Y_L>PeRb5sMs?{^d6)jRJdKI-h14N$9E1Cwac%VTLz$;H)RUV?_AT!6^UVDyNG%{E&Tf;WIK#5J?z>76SzS#eNt8 zA>qUijHN$4o#@IlbWynRekS#zz<%Fee>b53giWQ-%RRHZ@k=>U6W6{f=(tY?OSQlHx~iM-*D2^)%6A9dXbDcih1YWX`-r7&Qnvo-#@Zy zGMqrtR2!HiGM9o_#sTJAREp6TR_7k>8{kNI^hlM=l-#sQ^8O!+CYmswEmO-gRGBPx zcDqX@gN?eJv*zbpr6NO**dqt8ruEb zekbYOjn^c3OCQR$C}pv58k76rkxVU9b7~oE{-4Mmsu1( z6ma`-K6m`T`?GI;?mZ+2kLhb_J+C8EML))-|--24}yPp0n@ zUnf*nM0C|HR=-0>iETX*qeA9GLm}w}fQKZlH%6G9vNld)7c$xc3Dr&c&4QG$z%LZ5 zBBy0`8q;xxEiQ80CKrNv@5?)BQ{BD#^2yJu;?^3k*bZ@W3&~!Jr`vtFdA@!~`sf+q zwrxGlH=5tDF@}FR=ixG%A;Ybh8R~V^W<842^Upm-^>=H2X;!qEjE1)y^dyPE2bAXw z8QxI+nVp!muyIQqRk&stz*NHXfCg^sND(CpAwE96!YG_3R7m$H6V=Sj*6&bM{9<)* z>5~bJKOpDbg!jvG^H6iiQV>C|#33UA0V(d+zWPv!g;&;y>^m-hVFv5}71ok=x z9^PhzL4;^HetzX*U?6$<88-*Vb*Ihu$Yfu{LInN!DM>;jP)3v4TL@DT!sw_nk6>oK z`u|-A%pgs{+kqnJFTFc2yb4&-W^bzW-Mg@bH%2nP>FpIyxSBeM1VuTl3X#}HWl2wu zUv{2#8mXO`7(R<{jtG7Vl0Y!~L2LhhR%CNhCAUXhy($1{{{8Vey zsPK3j)zVVqR~6Nu4Q0_Py!V^2P@LZE7@6NJG+X(8rC-(c=4oo6}*z zp>LJ6T^sS@y7RuIOG2d+G=NOt5KVm3Nfq{?KK#t@umfBBXTR;y`CBtY@pZTtu8#*$ zlvF^?^6dMIw!de^FR%&)wvESox(w$Q0uFj%ss*}y4TU(FW<}B|&?<@U+QkdQ zWOF3rhQ{#<-8`yB@FEZeA(Wx0;087t&#(R>F)~F1cJoXFdr0{cKXg)2cI^_9U{F5A zviOHr;N!xe=Aj4XC*~!ho44GMyU(t5a6dE4@qtauZTg1J8f&sUOD@EZiF~DJMdu#Y z!Y5nxZW(%D@efpkn4I>$2f(%?b_`<+R zSXT1Emi)^UyNGTftE#(ih3K(>(`2z z&X&46!k$v9shwCwnDs0)P1Y?|?s9JNV|l!&{fjA79A4_(Z)8lnLj6EOHI4^xC(xf)Q87FZc?wu)I7G!ap#0gQuh*5v%Ce@sLULKo>m%AQ zk~afWFXq0*^6xs^hQL{kj$GTv$6O(o1UV}rG%%&fD`&Qc8vyaT8;noOI276`w#+|;9 z%4xrf;T}LbbMS$m9_+(3Y-oDAG}jl(|Ck<-#-+UxoC+YZr*lw}%TL|BiiAZiM@PpO z3L2-u)gvu6l2{zU|JNWG9GW;GHipJgKPPW*W{_Yg0-7)z2~0}wz(CZ^o9EH& zmT1S*r@9!8OhyHAacx=Jg$Lp3=_#>wYkHbw*)YB%tW+dtACf6pF{9zxZ^TG!=(vfF zu4ajhP7YOT*yy8>|(;X9W{yNFT?Ym=YOD`ya#fcbcHz)CCAc!GR1ej)q zR5c+mi152Z5zzz`ee>H7t>90_4rgPYBpwuu&g4tutwJJ^Us}opQx`HTBc4y)Quw&~ zf3yID-TR`n^IPPl2UBx%6VT<6Dc=}G(u(gQEmPh(K8G=B5r_yFMIN9w5;v|N%QM>< zq$U6dOM5Ohez?#24qDYfBlaVtuA!A++6;g43F`F#v8&>5?P zOl3@)_X%*_j3{Ikm4NHzf8QNGX{k*VeV z1FuC&?up}YC#Jc|1o7vP2mBY5gj{k=O5j%Z6Cz`^(Rv>YT0E$PCrt98e0=P23Fzux zDh?nEy{oHab`{&2;$~Cx;fm~USu~`*{r!79Jtbk+g*H@TQ?C9Z*B18cZ4XZceG7{X zIXOAlV|N0xF|V9#Fc7jhk=vn+Btt#%MT&}xn>srg@d;b;?h`Q-9%FD*-qWPRS{Ne~rBegZT(UF}N2~D50 z?Sa%76G=~Q!muOY4nJ)T3Pdtxqb*r*^+j8Xo4dOR#s&f5A}j*PMK%Ee+1RQb3JPR4 zA(`ZMLxrh>U@_jAo}Dn>3#7y1?FtQGu8^G*Fr}|KKWQ zy*-_tE-fuBPOw0eN2syDiL?nq*o4b|s zXo@t7G)z$^dkAmoWviXqohZABBG5^3vLT69Gzkh+j z%gYOCW#{kQ*@~Jr8ER!X-Wc(`A=^22>FB(il#8=78N~sYVyub^b2KxlrbZ!s#Luq_ znxh8kY(9XzU}lz>=1Z(Qs1UA!ARqGLib_kz(E&z+i9IuGMuDw^(wLij+T7AI64lxm ztjqt+*h1}6>G4AQ#l4cv+qSV{3M8hCryMqt-an9g^=cBvQG$*)`c+@MFB|Kf$elne zlNrbuREG>JVp$*_cSs}dF<)RtXgDt!`Vht7&Nq(;IeyxD4At~Ee`bK!ll=9DB3nOT`4V{{F(L6$X=FqM)=?|COMFV*?%hW{4`Xr8@IMFt3UZrOz`Y@<3~ya> z7yG+iw?erp*n{i;ES{8n^jA&!%^P_teJ*Y2Ai8CGa_K7B^kp=@`KkhaX(^twY5Ml< z+ky9_*dbrG?d)--UdGt9G|?!h)k*f3MI!ETsgcb!k6%sNU5##zdzEZ$$Aey$v5EaO z^aPD!3ChYVSFQ}k=B!K{Uw}UxwAI|)+@L!#P#Wy+-e$?b4*WwYgu3B?f6(`;^2hvC z0+%az=hPnQ!-fxUf_hN)3~%0eaq5*u#77-INiLhcbkvkn9CtrrJihQ8qH*j zGSe@7d?#5#D;g0pwR+dcs^;Y_{+X?vn6KKXzKE1DK&G785F0C%1|L*5pz<{1lY5R@ zOA$NZt|V=;w^{i$Ht&(TW9Fdtq_~dlYjWLCW8@a@qbD|L5%K27}nZtOekB70zzk+7o7_K?A@lk;@0ZQXN;_g`*) zXFG)-z7$mqjC5pK8(qgY^l}KMx;MT6+#ZUs9+%M&YFvju$8qClO!3FpcTLC+;%0*f zgYrN9&@iL;A~WU}~wY_E#S z8tqih@2?Y3OE#m{o&Kh6uew{)H*Ulpodb215AsJ;rdX)&-m^yuwY9WeBs=k-XJ)M- zAR2Xs+qhk!p$+{50`Tali=x$Hm+Gp5z9oKHvJ+c+eeAhwkGou^7?<|JMqQ~r75VM^ zav%Gz4h{8WM~Ey$2SPyXa&y=t&Bi0iiX;I@QTpUyks0A`H;gVg_{jeG)P)rfk9Lv~ z5qbwK4>KHQ?lGefy3Kw?o<ai?#R{U zMPg%P2UBYC;A-^pRl{(KMGS@u8ln+uNau_^V|Ik*Drebs{U)GtW)F?IawG6AtVgN- zt8#hG#vh?K8Bff}$S@m|o7zOoc+kVKIwqVRTZHNbF&Zx9nq*f;N7^fK)x>b5W8c0S zh!<&*&v)9jWF>*kgY|DL8(5W{y>yjhxLTw7@^L_|=D)nD{o+{&jr4+n z7sx6h7fTX2qOt=)`eZk4IxP>!sFg}d>%Fz~A+^$1_zl1kk$@}*QZ-1E~8{z>1L>|W_8NIo=Qc`D0^*9fA8L#wT%(#;s z^tUMF3=M~+2N()m+o3~?XuC`Ir$o1so&aDi9O;y-cwl8pMEs=)$E{r8o)h^s-kqNQw$_xc} zL;}SKu1Er=B0OgHb#d6CO@k1}|M5rhqQBhza`EM&vo5 zKCa#nDcFWJ8D5oq2^xe=F3-7uE^%|Q-BBDqxn0SSdWI&i)?ri-8Z5dk`i@i?*Vlq- zjk~~3z_vVu^$~^&GRm3~*%U2?PqO)%t*5dL$f2Pv8!>3x@lCx*o2?F9vH^#b(eM6H zPsr9NN8jW;rSM{^g@yt0^3O-vZrC(&d1pC!qA_KbNr zW6rhyi#{k=x)_}~2pmWpgjklSDLzBNJfVC>Fqs3#Z#)`oEtozwtm2--SOViU`zHt7 zcL!(cMS=bD=q);w-2HvHh8)<)lI~KZUgW*2D#y~b;a9a zt2=nu=-WdxX`HvrOYMO<$!7Ejzgvv>jSL^~5x z>=c6uH|dAFnwiBg&g;NKw;LbELdG!~E0%(Jbcj81{P>=&e}%AguzoO@m7Pgz_XnL? zN3s$kOyR`24q1Bs{Q2p=z78ZsR`W33i!mgn=P*`5`g7S^Jc`+~7QCxY0ye$Mr{rBw zOD1AJ*60jfMitCh*x3`Mo5zTwU@+|}IwVyGn+0Di&WAZxYU%#%; z+IdTElxsO0*l_0RqBD$hF*&fau?fK}B8}C)Jb-@Z?%g|<-$sd^QrxdGJ1?uKuv1*Y z858ovXVo*iNsx#oGxAu0C6MIyPox%nzAmwbd-{SohYEKDZlxzUMwA+R12t)f3-H ze1#uAe(e6Bb(=P}0|Gy97}u`wb?S}kv5zaqx0MGYfROS2UC#0y>>G=qmz`3w8ELZn z>!zxvLFrAd@6_DhYWAtEF`iSWVu|s{Zh#kypj26&XEx?bm9CnyKw9|wHK&A@-Dg;H zs&$RjX-MsO_iphE->cY+(!TZZE3>u;IvZy>vnL#i+AtVQF;M~rA+oJ-utVMF^mXC z3(-C^ZjeotM@Qo-YYOg8e!sfEnUBA@4K5SNXt{RIe-3ql4g8H`ug?ID-tB6nW1osqVpm)Sw*R+QuRplB_n{8NaEgx zX=w)GU2J|CJQWqdsfF*9)#LVj`Sb&)#}#2cP>tIRPitkpn(TP~)~y!YXTMq1ysnaB z$;Ydm;@;0?A&IO}P3DWGq^33^xU8o5T*j7Z%So%Km1IMQCqfvdTu`wcN^y%7NO#*TjO1ug>>_eKO+U>}lo z`gALWFXo`X>6JTNQrz_l9655=-==0}x}Kh;zHiLU&G*k+YwlG%AhhP^*&02?XY9L* z77eWQoH^4Ll)(D)!oXcek1h!DG=B3Y|2T4{(XOtsWi?cL(>*+fa!M)byQ1LHSCqYZ zGaP7jH&@aHr||>F#@wH=xPvUjBLaCdpTlvpA=`Wg{!q?SQT3WW{Wj6h;AiYQhVbeR zrZe=LbHXynSQd&W@7_HGd)9ALI~?6>ws}D31}27kO`X~&w^37beR+iPlw2~5A-lN> zuMQ2VVkLY~xTr7l))* z0*hx-wELG`QPpK}k@fUhvxbAqs7IyUuzE=8pT5MKOweCdmGII^k#zbOA_ zk09e!4>!7;^YGKGN5nsT;>4kan-3e`y?1ZgtXYweedfVO_KYHZ%NkYyJgN)LGYm%p>Mx=byPI^H#`&DH%a=OQ?B{-T!%`&r@-B z`No1f_VFl5_h6)U|I`u-ZqKTtM|*}MXjCG@AOf&XeC+l0(p!7Y%^UC7$-drs%zdTh zANRSl#-PIn_ROn@n*o4cE{~Cz?%53YxT2yf;(^1gaF8b)%<(c~)24)BDNfikdWkNh z2MQ%>L>1?a&2H`&S8l0!3d~;HWG!3V_3mR39YyNL6Mf9h)ri7m(?jlCs;fSJyufgv zwDedobkx;N9200|WmUj7V+Jms6X)UrnP-d>EdsR}M)n7P$@p~yH2$C;v{;+%^3E-wB%D_t<# zQ|i!RS72P)h^MgiG`H)4qKW?QM;<&seWqs2VtQ|ofSAgxU)y~xtE=Dm`TNJ#cQ|{E z?ngyM#mRHh5q%cVDfH)_x%U0nr(2?xluiJqBzE7PQ()a|8C$furcem1!^js6@qxp? zm)HE9vg3`%SJwg8cTNvG)9H$7vYvrKNMEg}>CINiW+|7t$Q7KOKO z%NCJ)r*A1#sQTDHbBEO{qxc?rx`}-6Mef#_zt4l(vF^xHoIT%ljugwn>_S zAwG>yBxX%^KnwMQal-TgJA>{R^CpliiV0^xv((H$wSca_!GbT9%rm}zV~nBG)g>Rf z_Juqa2H`pp5fQN8k@)a-9j9`5`=d)L+Fk)pFX4vdf-S#hK|A<8!swll>=il zR>H;^6ghog86D6#s@q;;?eaIH6d{L1qiZn+ocyUd7f(n=#S@_VlTJE#HV%X*fE%`%>nP9Y+;2F0-9yXhYpRRY~D?yx0P}HO9hjU&RAF0;5;9JZf`9N zxIx+*j;%StiLBOI%d`yaTF4$_3R)0LnUTlXjlj%)>!e1eOIgmFZ3Edk)CQPRhQqKPfLRW`vkdr z?ZRZ)-RRcaG^!VeX;Ki5V4!!#XFOVQQQS-}>!RA89;!Q|H0mh=S{i=FQ*E~pL|@Hj1Bgz?8~v1^K^daa1>m0y-khYf6<|?tim@1L8H-Fi_VL z0_TU3XLrpmVEldOJyUR~A^O&dX+J0wCCNn!-YPsE66xZ;{AG(KCjBjQP{2LsabPVl zOHWBowtju2L+Rg0@A_`n98}w^kH|`78w1c#)`@Fx)2c3VL3-IVnR<682TOQA3f5ev zmEO4CzkC^$vfDh;`H0Ou>HuoU_NzgUnvP!=PT|a=$OfVVOkBw_!)G*H%xU;; zXga>bzKt7WVsx*jC+)wKm*(~9?5B8$WT$gh$EW^ap3r8AvddWd*mb|~p4;D-S46NACfq+n6I*UIxQ{w`` z8+0DP#Z#svl8sM82iB}XnlMtQg;vDsFnIAjcO9e5`z!#cwU19Ft6m%5a+OXiwUV+P zK_2D?;C#r_^OJ*W5x?Zxv&>KP;RXGnZo&3K z321yitMw>X2fx4hBd_nI1HTS*+jimV)GNC_K5**&CG7l$=3GlNAiU-TIvAV#Y1<)S zIK*QoHV+o)o88~67`jA^e^_rj3x78R%Yqhl%CC+o|gxVIQ z&M2EI(|#6Bm8||CBN;q;^u~XZG@5pv=IE%mikb&h2X^e>f`_99ey>ka?5%KH7fSis zko-ItSZF>KXewRM_(I!}_V$g1+~#_U4O~u6j`!1?0U|UcjZQ@n660)V^T{-~EcUE@ z>u~EY2fMI3^3iWlxRZ2d*^hmc59h>mr>c|QlWi6W-ksHrOiWg@a_>NtSJ8-f)0Tdz zf)5;K$ACO#)-lVyi^1bB10S@ri~HEBUAsso)PX7Lh=DYV3x|FoHP+p5quzASQZ9_J zNOVCxmi<};jw*Ls-is$wsYUz!{>@X@tQnT=vah#(vw*HFD}mP|x?81xq`VT#4we;z zOO1VH`hOrklRIP`TN8E|#RoEafxzCsf4}JK+bt6x9d!6#s3Ap-+v{s!3dg@ zlcmI?7n`*c&`X3PyagzP-G~wU10G(uFcdA3d)BUp?(YJ6{PcZdH^)|a+|E#E{67{A zQ4Ad0_@tpMUsf0~qPQZqb9WjSfl_-(RIY<^q!-b+6^&fQs?x26 z7knWPQ78`bn?GMiAzce!ESbF{M&rjH5GWqdsPq1%Hj{?ae9=*LiJPID$mb!_ryid} zV*{w|cl!SCU%%{FBMhbE<>ggCZC$~|mHeKSIVDaE!H17gJ*2$2+Rg3C=|jH5-JZ?~ zP*YdG7o>VUA1m!W6hO|L6xG@ujNdlu-TU~M@}m9YV0|3#wZHgw-&f1cOYe2CsYnmi z-r=n|YE%auT8Yd>RnINs>_?6?Q21jtpyZOW9oJUbt}O;lnK`v3&P?vYq-aHJj%8 z(Z^@4dFcMzY0OWT)dvF~6*TTW=|FVh+2u8sdF8kzUd+#bowW7H7)UA}L|xQnxPs7T zxP&SB`0QWt9=1WYqlm4Ze}$8*QxLe|_m5uO*bvwfqwyX)3JMEjMl>0%7I88jWIz_& zr#*HX2ESume?cchLl2~AVj!Oyl^dM+BmKCM)vk!k9ujdWc>hfGjYah!gd3^6XzVDs z91-t3%vg2)?eM_ZdpT;mhqqpMi6_m0Cr$jPKyEDK-aN_rWLsLMqDpEN$XTEbG*`+6 z&N+dfspeVcJgHQ2!-JFa^$?R3;_ZekAeUsgl8>E0pCj@(85P+Qab@+Ub zuUofysNUiP)E<{fZU&FjeQ%+gK@@dSD0g~56mccjx}RN52*<{4!rmaafTiRA>E*0W zYifo1idC{&OMbpP;Y2iEGU(oy777tu5`l&SxpX;j8ZD{dd9yOF0vw~`HT&uDHD%t< zz$xwlFHchb`hJPmYtzs7D@p{Z5>uhZ7@NqeN>CH?{1tJH@ZOw;k$xh5K zDM*xVbFXIgYL<4YcX@$3+OB)AxeW{a{L=5vnKkR@d9__1=eQl~10h4tqf75vcQDk! zUbuNnIF#w$eKFYOA4Pw=9oBdn9?m+Vx9-bMYXihYS?1T)(tj z0CR^QyDg>ZqU67xvw3OfkfquC_U&1)-*K*m-(kJh8}Dou;ov$A(?mxvxpWH6k%v!Y z3X20}@b2l^DO_oNs<;%8Zx(_9mQzvFou7W~U(a{;?BX+9IAv+xZT()7L+b~Vf4cm` z2F@{c2lGpDvs*Q)pZRy?WC6MpPqb)O-lu!wk%XxVbDzRwWHA5Xgo2DB1j||LT9T7TC5-{`Kc2yot5zR~6-8Gc&W~!twuDZSMag-R$=yf4`>6~p~>dxqupmrahNtG?p1pFjjf;auSSg>;pjO7NCrO78Wi`yHAr)b}qF9JK+m>fm5zIliI}TJ$)Uf`1ex~e$?YJed0J&>g=9tj`F4$b3 zwDhyZ>$jmQ*=l8K;(Vx=@H0v2f zW~D`dlna`>C%Na(n6B4zrh85)TxR?6h#tkB2Sk{YlT$EqyL+eBLIsh@SK%VmC&BEp z6pKPUqDPOaZX&!vjm$$m@Ij}_ zV+SEX#=uO(Do%X7FPMfxZ$3Q6L0OKNn3d2`rA`_#WwPwMk;P^JzeC~>n1%pgV zduuMPxGM=j!w*~lJCps5si{jQ4ie3ek&%&iX4}fwR+Ojve%-)&s*XZ--V(@;48Vbc zYIOgRn;-k!tvs9k>wm$XGmk#H371I+!xlP)LVs&TcE@-5i>glt2w0Sx^FLtG%t>E% z@8*H=4JxjDJmh;l+r>(QGsc>X8<*32>C+8d2bq6^ZyU;RQv+iqVd2ns|kIX%@5)%@F6&sEnJC?i_u1Ehx zN@iv(s0P>QUixydo1qFLBE8EFEM5+Fp1KvTS-^UU->T7m58S;@rTZQ=%*G+@<6*t{ zMF|OFuHYeQ*t`DVyQZ{dmz!rB!r6OXJ?-QMnh=GKMD^71$k&iH!R7n{Z^1 zkx>|Ap|#{tGIs&bt`V>d{iaY9WIX*Lds*h2Am|{gTIzIU_*>E+Ro{4@W{nzk1Zv~@ z?;$cUc-8?gk2?_IMt%EkMld%-5q7cUHlP_r&)TrCcvg?jm{D`9ct>d7qAzn5XFF|N zsuO7v_CRyOTm5(kz58P>=ft^AurZHGWJOcqkH+b~%_5G6b-mki__o*w8)MEi%DALC z&Td0})o_zrl`ZDJh%;R0~r~P&L@WG zeV55V4D7GDFzKO>zD$yDA-bLXGsS=j>W}I5f;ywE9Dj1&ZjZ;30ZB|J0v)G(-P(8Y z$7GPdLnn)g!zsYtjf3w}efgl@Ddf>fX))-R)2U^@1G9GS0zB%hyy6HUMH+@6p9$EH z=mp4{+0#|OFIR86QeAX*LH6svmy9c>2(ssJr7!9*C(b_R*|O<@v-n6>RAz{XxBI>8 zZr_rJyoYCQ(e~-K=Qo-OH;?WZHGz(0RPM=nC3PtcL!laVkT0PmxqwY+LDqtq z>n02ysV>`hx%DDhCXTe!wk;R3a>9fOv!9%F_E*zAR)mR{Z2YAJUyznZrg+9q@+mNp zA$eSnpQ+=I87~8?jaX}+e82ePQnmC6t6m?A-L$FALOgcg|MoI#i+n2cdCqq3lMZW% z;R-!hjBbAG>8#S{&sS>qTR0UZyombwkfJYd-M70_|BctKCDp=lRWcHAQk&) zF6RB=>$?~m#~?6B;SXuLhSB4L?o>x#3h>LkNTbnY!i2{~TfJ_;+fr<}j2NBfBVuy| znMs@thaJVv+e8mqFze3A`JQZR+?d^a?vQSqXaI-K4Dus*3dw?KE+T4Zvg%fIpOxTh zo_PPU>phQmS7EUi4e6QBCkf}sYtu73h1yNZe)Y4FV$rCmcp;7ivmxEJM-yMGC<>e*W)x{SnQP>cDKx3HoEy3mJgU6m-Hh?C?bm}>U$c}&?5|ez+@W`{* zyaPa2OKqDl2;pgTJ|Yq&*IVc56grSXSD|Eql6Gb(biw>EbM?xr1+BSjHvazR{PneC zXQQK4#bg>ilKVWf@=w*(d61ei;?cTqjT@B-XJW@_3Lma!_$^ynczt=+N!xwLc@9rH9|N5!4JEe=l3rUq^#G?!o2M1Si8PynG&La?%9v&+>V{Ws17>KNy zmRn?QfP4{AdSa3&fr46A@N)E{mlJIwH=ugvrdAocsl&wD0)A9z49(`jpXcvyJ#XIO z3M1?B;Wo{q{SIF#&}HsJjCK!$jrqMh)VaExP8+3cJDwlxpf=w8;sQ3?j2jt}l%ywv zkn9pd-qVrb!*u@|LU2WxC2n|*FC(?GCPqPIKS!`9vq(0aBx6D$@SP!tTi;SSI@dEg z$&1!4!{?46mF=ck%SWEIWSA`eCWsZxcTJ?(mNi)1kJX4gJ)sI^(YERDo@qO? zv$JouaIY5(K$*mk5zkJ`_bGpVIg&IsJ=!X{zKLJvb>;-tBGvJPJS%@R<)6xH4_U>I zSazMk@OT71ZvNYqtIO){7!73$ z{IqW$O2k8&`c|G3O*soVRBzw0!^T#pa1n337Ccq>Yp#FXtmOeY-8*5tvTMo5#sj~X zwOXs$%Yipm2a3Ek@MqKJ&6|s57#j{l$Y`bD>oNcYB5q@=-~Y|kI_HYD6r=Zy7=pek zD>ScV!M+`AU}s6{)NP|`zvOM^Z05mgZAbGL_cQ&?QVVXH25X5?-iwuF!F*%usz<$f z=p({wt@argdj`(&LhYe1b4<+@{(o|`o;|#fDw;y->uUr0$A?Ysh7PmSu&yd;btAR9 z(n5El;%hoQ-lTKUpVy0~-2D6L(op76WRMSZC$ZG%9>1+_RR~Jj6fuLiRKiMy@Pq)S zTH7YKnL5hR{^_!xU4s^c#gmNV8!S@{CdZ}{x>te2q(Iz0VqOFq~Bp*>Rzsh)cv)^puArWtFq z2LqC*VztTR2aP<$aASCs-Jz)ry$~Mw{$Wvxt$pwB4;Q=|?Wjy*D+b5ti(w9L=DEI2 z!EhnnC!O*?FZx056q-rB?%mh(yd1Ghj>Bvi2&$nEn%eIFR5FBAh<0Be!S~hT;>mS8 zQeX`y)j)mlxe_lvqf-gL3cW)F?2GI0A_{zGFy`;3aH{2*0O}SY!b?x=?**={cnQ#S4#;3n3y;r&tuxI1gl(oPe%I zM#1{_sjg`*Av2mbX;M$|ge{FE<%*Mf{$)^+5yFYnIz7q_MS%wM6 zXP+097>Ek-JtE3(ZYug9l*jQE8on2iW)&9~v-`58KxM>}Au;-lW5mA68LT_W%g3j& zeT-U9wSmlsL9z%ZqS>GbpfOzP{nlGR7~Le7Wb& z^n+_t)6RdMzH|dsxp0Ka_!&r=BADb0!Y>$J&96@_7){)(-2JezgRieKpQ{0thonR% z@vOP39`WXrCR}lC2U?tQWwGPfu^Mz#t7IEB2td_}cXDtSzTN!H-}?2wMYK@0NBjnlUU3%Tu#vMa{PqP9VMQS<>hK|A_9tkNBOf--`?Oj zuOQ&AzzXC$m-1+k*DeK?7dE*IqHa=jkakO#9l>)yh%YXL*gJ$hYM9asU zx=vW*doMjZb0#$6o(q29PG_&{?n_7zpz}QELym&9F=N-9_=19*q9u{MF9;aCLNMv_ z85u{&5^|}orLJ2r8CVyF`Fe{lr|2~6fu!f6v{opXSoC_oi4kOm9MjD-qjQ;KT~zXP zDp^8)rDMmzm(#*B8xm#QQykR4{rKTQNyTc|XphrbSr%vv9KP&P+jy;9{r*Nh`<8TE z)rH4;>DynRZUpoFrF`{6Z;tebJOA zt4(8|(iAP9T}=G}*==gtMqevG(>=7J0T|cqPC5qw;BsKO8Uw{C}Es=>F$6>AQ#EvFb4;BPCg)r^HtE0`a3(F?n)+ zJ&@y}{FnQvKHtx;t^zr|vZL;OKpLjMl~B92X}c#Zrj(|_2lVGxt>z5%+G=j z^}^XM)^j=f%@1c}te}Zwt@o6~J`EKzxy3dem+Tg1TlQaw*bdQk_V9^|*iujqi*JLF zWUZTcwthPIPQs9S1o(!;$8+2t6*aZhi1Z_)Y>jEfd+`{4$k9^32gnH))tl?oH4Kzfs-6&7ZnxRB9>3n=@g^y z_Ta5AIFDEH+WmX?hQdme(1~gEUv#+e&^As3MU3CO3C!%t;0F@}qWfXZwb*19Qn;eUN%icR@@#~PA86Pd9m+t@KILX9XXgX13ZIZ9?z z#SdOaBT#ppnRoX2mpSwcw?U66xOH@OhZb!6l-~B0T^|`hg`7?FOl{V#T|36(Js@&Z z+IKNAxddxaKUvphF3_sMvin6vc_*GOYvS(iJ|sLWvq_|uw%Pxrs+l)UzHCO`1Z#Ux z!_Ng$SZ9C*(gA}R9t84G$fl_F10s-~Wq&q&(dWyD5-SS~)nVW7<-teI54C zErpIi|E0%x1X;^D(6DeeSdJ9qHhE56(AfWyQUMHIMpHc?Qn1m_- zmgUqrYwJfLoG?>rUWxP6k_4mUmpo4tG8No^ z@8?-{1~cCXs=t=XfHacCG&fcDm=UwK(Y3(n?dB9f?sdz^8OiWh7yVW~IaEEsutWTt z9y1U8>@8*!BrZWor%fvv9yBW4|K6u-P1SDgnc?ki)I&9TVYGestyY^eWAyDSZtt$w zv{|!jX9jrz;Yqn2JhRUeNFJB8qT?q{ zBZ`HR)B`-`a(h2GtYbK0b1-K28cf@%$A@OxGI1s2FyOmx6@e~W-}0=`e>Wt%)Fo@s z2ZTpiCokkPL}TWI?%c8W;N*$B3I2C!>)^T80o=hp%8+Cg694LXu;4OYhBPuIY5*b- zHjan482ByWdfrpl|9tOBbqO4CaUd-m|HFQ*6I z&x#&m@~Ll!Eu9wzS-;jH3DNIaftsUtU$MrxV9)<(0VoXGbeq1-+${HlUp;y~Tha;i}@|SE^Cx}xRJ7@WoyG(ip*42G`ZWadv_9WFg zX^qREU&CWogSya}ZL+MS%Bteg6xj)a7=A!$AmGK4s>^K-A3Yj$0CI6bLqJLNvAIp! zX!T;BeX{+=+Sot$*Ut^_(eBFoG2$dM`C(B>X41b91Nj?nNHb&+l(G#ME?j7kleuW8 zzCHEt8j@$kog53Vzu=&`vs_Gj*0viqWU) zY9a&h5IgWC>j@Y%<24~R4?odrhEx{^wV>ebZZa=o%hs*s6OMKy*s&`TpiKXL;q>>S0Yl)`StOdOOp*PeY$)t^G-z|_l=DROq5G>z2Dts9Ds;G zKKz0VZV2;y*;8sc8hoK^u9W-n57lbC_m#-G`I#q9gwjY-7b+2O*!-F~bmk(xcgGOI z2tFW1(wL=LPA1S5#QYUFl<mBi2Nl?bygXZft+?ByE#}xuo;_3W_b}!c&<~wET9rp) zk;IhleyBNPudatCYjdbl#~jcEzm>W{r^&jQ_r4ft&>+tP*hq0acCa~!X;n#RheSda zsc4JK%yD;>y3bl~r^SS+)__1Y+PERLMXqx{6slIp?M%rC2xgvfOw_f*Q-@}Bvb_kP zm4pzpqA;ZiL(VpNs?QO><^jOXM~@s8-f0>vu2Ytumvr=OOHt9b_2JY?&@3yP4lt?; zEzecxb=>x~pfsYZ0yU`*?p`qe?jstxl9w+-QOC8XHBr|NO--Elyw1oNo=?VH&cV-f zL~sAl@$K%yon#!WBMu^n^h!-^^+%vaW7c#HEq{1aqWRM$pPI=c0pSTbiHW7D1CHAY zN_pYNjb?N%xjc2&c?BXI*xYAsdyzy@goQ_0xvKW93N{?a$`d+1RSk{QqjI8CuYg0# z@b9#%F$RgH8=BAJ^6~}rEY}oDvB@NuIB7N6?&lGtAFppOLLFg}xcL!LNv2X2^YSvW z^6TKXO!Y`D_7?7pLI5gLD`)Cx1am*vwCE5;v|&TDc^b%K^oVq zSC70988y^09gXLrZy$8XG&+{~6C&5I&ts}voTWJa1@q0DKh`q^4yuR2hPn6c!TxIN zq;w_l0YlsQt0gL**R`!jmHwZ?e!PX-yKQE@dk*GxO4xe0D7HO5-L?tho0UWiPuP9^i6-4j#V}RRj^Bsh(Lla!}h$d=Q`6 zQeIA7y#eE&z015H8ds2_n1AC+Y2PDZtgC7L89kJ^F*22GV_V=od%f!$BrP;s$0{^5 zZ{52WcW7Lp0~hri5lev0Wf#XQvC#?0__@TbqzzHNMF+KcO($k~Ec4r)rX*9GV@zUR z%Nx00?F7}lQnbG%^=g+#3f(yuxOz{{)&i>3u3f1y_|p{sriIRoSa%Z#G>%hP)W_V2 zr-LSb`}8hjfXfb2^q)Ddb#rFePoSApAT=njyP!McjbTroOe`?{_kzb6yIml1TiY)7 zL?x!!nZ2pv9oJ?DShf5z@P$pg!O0`XmN{iLWAGBV-|69e>#c)<$ZylN5EMkU+y09(mQQOcG5TUDq|sxl z0@~s>hH4DMTg|#ZNcW!8m#3#FN@QUJWSrzX*M*aGNAB)!sql%SsuQ<`&UwHx4Z-5_ zBZld_@b3qAkK-PI+A(rVyF^kd+Y3>hmI@{6O7Bz1xlV z@JK9N)-XEXw?%Pg_TpBsFFh&ING;AS7)4MVpzcS#hTlZ(|PPTv~JRYdi_ z=jf#y1bDY#9jmPO-Spr-&@{bL#*RdnyCuDbTbEzlsio~q#$}933zH*}6q9siDK}u8 zSiehQX=m3+Dq=uT>a4mzo-5Gp5zj?ihE}U1S6>{~7#CR0AD=P90an`Z#mybUK8To9 z);`gxIcQgt6g+u0#Y+zwm?#>?g?0Qq+UoOtKJsb+>=GU@nuc5Lb_jLj_1AJBKrDWq z8cq(OC=@KF!W8B#uu5W#6)i)-ptU zj#+MIXKU**o+_<@OElmDE`UifO&9_+ChlTvUtNji4z3h3wk?QTUjURX?}m(fm%Hy^ zHJy>)G5hiW=5_?q*VMJ!I{fjJqt>pPbh{EJQ1W$o-$tNqIp^dVs1J1rXNvj~zPb3dbXfX1+U4fn5qn4N zVh7rmEpZQu$pwmAfkT69vTH_;7@_u)U1xRA4WGH@=ijHRP89|H>0iUAGQeqpZqNoo zB7bSqv_W&0-Dv6jaBP_h{JEIQFl84TxIZ=3j?s=Ao&c&ir$E%4`H}_lf#uzv<5rem zKJWbfihW~UGycJZLNWEPzy4hCMEX|e%;fHsk0(|JXj%FU+nz#ROGrb z$%M-~3S=%W9zW^86$fDvGPS=c>u?g*}alaw+403$o|&u@50%S zzHK!~+yr0bxpUxBWd$Bi&a*$E?%^H@IC_1vX+55U%P|JSrG>dX!kK{Ft?KNpU2nd- zosYaV!O7{vg;Ep*E5uEZAm&i>JIwkujTr3Z(?$J%^CYa}$WwBAyedo)MXmH~m=cLj z)4}oQi(zXuR{UMOxFK~KZWsqWeu6692JCW~b*+ERiXN<7q{4H3vd?Ku(1*%}NoW3j z@{w!{@F2Jp(rTIB1#k1fAPQ}3#jO54lMiOT>oviIo?Br%NhL01wB4I9?%Xrtio5>~ z92+|D0Lo6^Y15}ml_PEgg!DaiMsaX&>6{YD8}kh0pLB81WI-&;4KMUaAk<> z`iP8E+A~$2e#9fE7qpHDq=<)?fIUmF_{o~soCb?8y62MaUIpA?p!j6b+ER~ydw1fg z+RtC72N>ctJM;?0U z!F9MqJyd6~>EfXT#VM!X=@gDJd-eTL3%joBqvn3E45|BSi}Z&u7!OVb?1T4{(F*u& z^u}Kce@{1S%j(FQ-0Xo&6`cbB+r8Fs2@pEiV-LEu>iaJPcOr5i)ues?l-~2WZMxpZ zs~=>w+s5PQ>&Rc?K(f3{)M3F0-Rou+2am+#DceLyTMc6|yF28%ci04C_of_?q?hMv6o$@w*NUVWU^ z8~>*N%E@5#P>$0OW*QlVk341AmiGJ$E?5tlyZ`;wj?>qlf!>vH&PGv>I~ zw(HZpG!T?z>*W=~XmZN-;hUx$932Cmf$trz&$;2}^ z>Krpef*_0q9dIk{l!+9WpDW%8P$^o6$-AUQ0`r5hdT zIZz|>8DTq4O`{JH)}4YJ-CEm3;L&lDM(5`#>zH7*D1Zuy$ zcAfR3HM}x-+C`fCD#m?v*e~d>Rl;=NIuPG!1b2rm%_35D#Ta4vud2}8rgeT2)}zTT ze0aJ!=LhY>d%|{P&MIn-OG?4!>Mf_FiKTrp<*)hh#;Zqo>`O4^nJgf+!vl(ao2F^6 zIX1!-&~-~;0ltHK_F#YgM6(?h3zVR=&(ST4LyDzi){;**`UnI=ff@|D92u4Lm3M8^ zu3Zx@4-LYW4iBz%rUYYRNcfV1bhj=DRE&oj1^JnWF&QpiNpvwCd-QPf*AijND$sFH zBuu{s5Ai)f|H*-Gfc(k5z&U>o|F`$uR8`D?;*KkY_x23ZpDfGfK~1LqySLzQMPJ`a z#`kIwC=3@{_f5VB)Tx1iAyW^vO98X|QY!iypCBoT0UtC^VR&4s{U>zZbwz+eiE3lp zxs7n+1YZ~jcx+oVZWVUEk?*2^e0#BN?g!v4Ps+H7{eK#8hC-1yBf;2K{d~iH65tBX z++hfDT4+7EOn}WqJ-3t8JoW3>7yBtnp>qFC$)@NksI-%vtFsuDpGHS4i$xjAi)PQN zWz2Yv-ANiF716daMZGnQD}cI9o^JX)`rXaMY4Hi#&zv@yg|B!oJ-}aJNPb2B zhQQT#*{{B5O%2wW^T^?HF0g81(wTUGRvA?k%$3_Js1D-Df{fKX&ut;pu+Z7RxxYYj z%ytD=jJ=@Mcb)=*^D;=uVVUcmdRR-xl|cdgbETChH0t| zSEnbIX1BJFk-eUTmA6l_T5)Sc(DCc{@5Q6L` zp~S1(M`wILAfo!Q&>y z0Igdq?R9o>L&ueQNB>{=d-39z+B}2lON%}Out)#5RTTeOv~%J@DLCZ8enZCVaIWaS zZ2kO>yQ|jl~VS`JVy_!2906x5=bm?3l!OIJ(!mOcK4+!K;*)$QeC5GpG2d%w9 zR{~lm4<-R1l)5Vdz6FKEKSOD>Fo79UE4;ngjz&jfQ6D`E5=&uH*ZtV zK-VP>fU%T^LNuo$R}!|4!f+U;9*i)A=z8Rwj_%Ea!>p5yQ9p|Zv3Na+WJ>n5VsIb^ zpVB^%2~ksT;-!-fDgIT{ZWEp4noxFwuq9`p$uA)(z{K%vdxEIq#@bRlGVE@nNG)Am zb+p@!Kx=y~H1^Sr{tuoIxf~iRD|%ekZ0lOKR!!59?`#Y)x*ENLCNc2>cUTl%8ke*C z(7NPG{fzQv%(oFi*2!75pX32w5Qn*tS|D_2@>hW!nD3!t|R=I;93il31#ew(fcHHrh4;A}D58bK2xG zPLmJc*bI6>Bt-PzK!J@nv40&pX4;hvI?}oF*=x1AtyO)D2JDLMQ|1oo0yRzIdchr88nx>CS%)pO`He6g1ISdY2p_aR{ss7$${1H z*U`ifwooP}DhK@j5g?}q;^8tuQT)*0qU9D`J1n=L9=S1(Hsde{SF*SD{K}EYmgZF! z55RHwX!V*f*sHC2L^V09T|2mfli3wBmrsBMX8`iQZIkyE##b zo~qGNQ>Ee_CfqDZMkK5>!}8pqM=8y6BcM=Soacjcz_oer*PDzck5H zS#`q2&yMFLh^I4OSA6==ThiSvkW16B_H_0{EP9@(l>sft&EyV$cHr!zxL%1!-*MN# zb)84=0lUf_2YTFRuU<8V*f_Yu%J&_=?F~GOP($Regr`Irt=cc*DW7N! z4Ga9_{PKKtneFC&xrkPmc2@SS;lcVgTSxcU1>MjvyO9?C`gu~H$#vzeyYi?El8SamP*O!l1A_*fkIPu-+y+N)nWwL>-;rAaHc95>AKk=RVj+2bYcKKImANGN zG1Gw8uE-KTeea}u%wVIweM{|?VPTWsVYz&s#!wbO@E-)Cj+EX+Xbp336>0kJABaQ> z9RH{iv|a#zxlILl79&&@*ml@`_^^||exVa2HxDiNhHXI*NAXqdN9hQuCpD}%rolRO z%$eP0phD3m$KHB~t7UVa1WB-gb2+jZ%o#!pMi85m)oQ=CD|PP&E}agxv-Iw*v_s7$ zmeB1QwPOY8b`e!g{( zT+$~nN}@jc{H93kyc0_sx&PAoFgc}WF-(#;szFQF+8k59g{b#k@+aI*TcTUxJnkGo zK^Fpf11bPC;&^FY;$qcNFxDiD-?=Tk@zVTM_T5`*j^yW1ZZs@5FUs zc1`Xcn9znfo52EE62RGeV&ku`gJ*{jj64hBQMyk+O?!ZALj-r1Dn~d#>LAyOUf+MU ziau2vx(q}bj528|OQA%qfoRsm(j_GS+O?ok&nC*sv74=bGZfbX%vmM_Fi^Y=d7m<6 z)vZCQX+0(JjGvBkr!YohuZtA49S;xfz-Gi!&w&A~p%CO(nEt3t`Pqr(Db$ODhYeHW z8QhZ?n@*b^LfD#0*(hm^TR9EG9i5`*pGH}~9$|<^)v~^-YUfe-(aHG&wMVW#X#7#X z>=A|+=~Dk?0`C@r^N8dfg(Y1{5}Z(?o~mki%lz<$VPTnTCw3E6q%*2M=Kh*>Rde8CYz7#;q+h|L1)0dsbmrkgPr+=c`1C)>-^`B&(&z79bP9S9GT%QnQN0dxbaJ#0vHWqa| z$6nu(r(t~Q0B)?M_}8DCxofEsQ%um}uKrrap^!ESbSeU1{iv>7fAVDC)_tw5%g;%` z($?8{6-lS^bT2Ql_du#Tj8tZwm)NFJvu2m+w8grZ%K@n{+Mm(ub?0k;OLx;Xv8X2K zAX%%&W1yxT{->NfQt{8}K5Lq`T`AUJ-MU3_a|&kFW(QCDvWz*8dm~D00Nj`dj%#h3 zScCXD1Q-T@Ms~05XM;2XF1gq~T5?YVxza&oemW$=0eeNsm_y!(W`aoi}kGZI559N&NGI3*TD(|J0Ji~~I153&~QbvW9Y zobj!E)07?zYWB6Tm`1yRs=F06q1~Mjzvp$hYGW8^p^p;h5)pY*XrekfuxJUG*@vns zary?#aEpHf-NJ^#C28-FpBOB=dFz(Dn|{h^Mj0Tq!|CZoVvFZxFi5Ec+wm(d-s84U zgau~XTsYX+l5ZuU2sKm{Q^C!|0~$iB2acO@Gc=LtPd9rwyxJ5Q6U&X#w+=o;Tvpk& z>_=zNi^(raVKjL{y$h2Y|8K3AL4)9;+uWTwF~vD5Z|cz%v_}`If)G|?>prw(Ose-w&=@03IgZ?j{pYs?Z}b5pwb0l6Dn&vhfH9>*U`O ztTCb{)r(CJ#6bjM#6=X*NirT25#ch<5XFZqJ8*4}4jFcp-7@=3u3eKAQVe(D{%v4r zB^AoGa_K(>gaWo`G{W9Gxo7RG2V>WnKxKnKD|lvyvZSOWwwR4o_a30&h0y0 ze(z1+|J*hf>`^vf1x}m485If$g;YGoj&z8p|NEMJ*joESK*THMA2%Hw!fa|3fj(WI zu$O&!dCfAum10rvfX^a{gtUsASmTAZqyXF#V)5*UKRVAI9zNy}XIw&t^}Sb*Y@Sm4 z+B-45Qm2ZMp)_=wO0d+D+B z=MV$qCRV%QX}caQFZN-n!N#j<4H8*>A>=a|^xV^@Q|_6DhxxY*-KVNrFnj>wU8y(8 z=^J8V>Y^n;CBhp$A5`=GR+q|BX9=qycCsf7)xNvhfJ7Pgp_X&|WwP;V0t74?aWSgq z6c8AOP`MP~?bYUBHJ@HxH*u|vW=do zvx<&RBEzZLGVEYvSlGFm-%o2wK;)k7T76>lzA;@qs z=j+NWrpIX!Th4jwly*eD>DsmRQ${ZjY0zNN$G+{VD~ogQoH^5Sy)*_uU*Eti8>Hyz z7W&ktp8|9V;&X@~qUNvY-S_)7dsdj(%s1zbV$Cp*%EblxF3zfdb?Wr|`v;|4 zpYKT*y?@f_){uX7TeVbuRU5-kS;y!L$cR)w>3 z)i>z1Mskf~mw#xTNY$Ki=XsX8x;xHG)8fu3nKaPkqXvqKtK}amirb8w^wjNYQ=e1% zU3)bSvRZ!e;`N`3V1eww4k+FCJeyAHSRonypv;P}%toQ;VP!{RxN*#0wTuU?SHD;W?ju!lMTKanr)McHr$#v_1kRA_o%4EYP9y} z`_%rt2@3Lz=21%nI++J7b)TpEkL$xz%1?^lz;0r-j842uUudO;wY}kG=zLB~r&YGq z1Gn3ZOo)Hif0~zP6NRj7L2c2Xldf*zm|o=V*_f zHVfL!^bh#Em3H}o0X3D;J9c#K(O5P5YhfF0mT?#lt4gkCcBVM9pvlarRrNc5v^MeE zS(X^#=4Jl({52WTs`~arFRast(jBVQ_VbtW!^bjKdl@vT|L08WEiKwlyKv?F$@t9? z|ESu=eGGcwp0Vcigq~;KJW_jA-BWe`=d$I?yC;ln(q>WSEZIr5dCS&>BOz@SQy*47 zp8sXW5toBgT2E-##eB}#HgR3Am%P8g6BW24CZ@PS^Mz-KH$?mQuqfw*td{t*rb1RCVRyP_AwK zl_C{=NO8teQzw0)=rG7y1}*5+8N2MM&J2ev!yu$G$rBZaC)F{KD+SQkO!LBOY z$O=GY0_c`sPnc{4MBn>)Rcgz@=8f?{v-ypV0N&%X67nyFAk9)2Q5%9A#SaO8#o2;CV-Ic_i)sxqyb_{=tt}ECCWUZqc9ub~tcUHln@aM?P2;~&SoFpJy zEB%S3Zs;K)I~%@BkDG_Rm=Z6-r6?q`pz}!r_^W8aU)A?^h*(==%Ua`eQTjM?&KrH` zBuE9OCU6|mWRtKPlh(NmfPQLcbLnGyR=t~#S9UK3i9Bo+?+>yTa*Cd{t`1iiSo%j#Pih{ZVLJ98Do ztr&00lH2S?^nLABG>+g@yxXA0Nij31^(to>C{IE7MR7>qQP7pql1RcOjtkOy*L zjpd;ssA?vj>57gY17zn5!(v&X68oSdYwOF-q#2+dNG(xE{ZC!ZG^0eO72n`X043Hd ze*8*>8O^R5_lwBdQj6YOG?SHs`5`A4UvYwJ^CR^pFsz>mF6=0uAcT#bj=%hIntY5> z<9hS-#phYC>cu1Pn+!{(a+}`To1~L6_CYCs6y#W^@(VF;3D~hoa@{W>%l~g*qZ0= z{Ggc83MS=A%&>0kK2eAt&W!RWQr%V;8<+cvEtAfOH8gL1Xc;=_mEZQ2IdTMO2fd#m zZoZ=%&DnyxS-+DqR9HLp`(R-{r63h7sDlFgx^S$Qd*7Yb8wf!L8ArgJEPwm^s7&I0 zgYOev+ftE~nBm|hbyy|BCf2^kedd!d6*m|ruJN<#yHb)3?pS-}D z>_>(%hmUg{wca-!)q9`k>`fW1bn#21H}+E&BP-QlIo=&@d5R?*l=!lkcGBjOB<3Hr z@>%Xw@T=Vg{o`KwU;DNVs;eG8hg~nCb^?W-6d3Z^+IK)8nUws@qkwk4k`TzHHCA1R z9z9w;R&N0Lo|`jLzocm_I_RaYr2%|%@;DqDhFQ{?6|3aLBT)+>aX;D*$CJ^`C1_F1 zD9nCl$Eu&>H@g_#dAEV}Y*saMKGmp|27R z851hjX=B}Ck>-j1N!!$n@l3AFjYguFaCmsYBE6=?P@;0+>(azCgARAE70mO>r}I^x zS5?5#VZ65oP(2@frwlgJnd}He+!1jhsLkwothWfiJ0Y1BVKNxYOP&9gRaSVA(AnjR z@x=23U%UGJen{HmG{`{;R@BGEkwJMSd46$Q^~*o@3MM#g>qdA?5)Ty0Hsv`h*rjK< zbEBkV=O;SCB5%xil@(*6Izz-TOBwx>BXe@5cuYS@kO{jSkfR{8$NG z$XZ`rS)co2yut|}<8^rs-3q`iR}?7V*F2z+u?^f%2vSwC|NA}>dsAFS7%~wS21mgG z2z5!@NKe|KZi=**m6!qoTGAZ!sfsGz?$KIPxzB^xHU z%@5}(ImHF2Yi<`PIX3U;+F_zkifAP7h__waAUYb^Vfxl%>@)(j+DUG?6`k1`j_xID z5ols&=J?Kk)NFq(Um#6z=a&clur3YMDQ3cn3GVE+8)r_l&YuBcMwSp(G11ZmbFvcg zNfmD5ck9Eykaa)OeGj?>oz1j6!R5b!@I2Z*(0}3LkZhelu~XZ2X;WEgaw0P(B2=+q z;Yhxf&bZKEup!-?mHhCp zhnzXoSg9;{rSH#3Q{*R??_$!(^3N($O}qRI1A0eau1Z-{Y+s#&G0ky_i2#}*F?5t52R@kWpq-&fA2!~}4-oPQ^}!doV>)g_)C1}{_UDre0mz70tID+^rfVp+g|bX_H)Ws z{gx!!#=qi68L&S%%fVg?`f#{SKJ0}l(&9YGhmFiE9Tibd-dZ2$g{#`XX{iQ4eP{E* z54sCdTcQ1;Ti>`GgigFg&G53ax}S`)uI?~X6Aqur-RlD|FUhzt&gTQdsqMK@4T?S! zbwZKdvwfZj$z-`~7h6fmRB&N=&weCCAM^bak0{q1Yh7*JCq3E+Vi2-0Gh|B(>JbuY z@p*n-01XWBYxLd?sr$eD#_kc};~o@K51iZs8V-w=et}OX-T-Ip3mE`j6;vx!s284+ zReL2`+TPYX@Ml(!Bm_WZfzf(NCmD|qDVoOP(}w`OgMhda%F3!G8#+rQnC!%gAb$H%94$RhB!fhB0Q4e zqpd^0RWvk`L5dTLw*_L8 zdmRaaj_n2j9q-G)K=0U?1wai4g%gQ}?b)Cz7X(E;KsXTuWRn2hOrSPc`Jw`GfTlq> mP<2sdxOqmy;IUjk63xC$Gu~37zJV6j`&z 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