25 Commits

Author SHA1 Message Date
20d5212710 Merge pull request 'satrs-core v0.1.0-alpha.3' (#108) from core-v0.1.0-alpha.3 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #108
2024-02-12 13:13:12 +01:00
2dd38c163f better name for new trait
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-12 12:54:25 +01:00
79d095b1f7 some doc improvements
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-12 12:48:28 +01:00
377ffc052c bump release
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-12 12:43:17 +01:00
c39ce99e2c Merge pull request 'Add Static Pool Spillover feature' (#107) from add-pool-spillover into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #107
2024-02-12 12:21:00 +01:00
c0692a3523 Added static pool spillover feature
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
- Allows to utilize the full pool even if some subpools are full.
2024-02-12 11:35:10 +01:00
712dc718f9 Merge pull request 'Pool docs improvements' (#105) from pool-docs-improvements into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #105
2024-02-12 11:30:48 +01:00
a69347af7b Merge remote-tracking branch 'origin/main' into pool-docs-improvements
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-12 10:56:13 +01:00
3c7113c231 Merge pull request 'Refactor and improve pool abstraction' (#104) from refactor-pool-impl into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #104
2024-02-10 15:39:38 +01:00
9c310e7a36 Merge branch 'main' into refactor-pool-impl
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-10 12:54:36 +01:00
0bab5799e5 Merge pull request 'Graph for static pools' (#103) from graph-for-static-pools into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #103
2024-02-10 12:54:29 +01:00
b56bbc8c41 update pool docs
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-10 12:54:13 +01:00
12ac5913aa Merge branch 'graph-for-static-pools' into refactor-pool-impl 2024-02-10 12:30:35 +01:00
d017b9c179 Refactored pool abstraction
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
- Redesigned PoolProvider and PoolProviderWithGuards to allow
  easer optimizations and increase flexbility
2024-02-10 11:59:26 +01:00
f3ca570e53 Update pool docs in sat-rs book
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
- Add simple graph to show how it works
2024-02-10 11:57:19 +01:00
18a5095d0f Merge pull request 'come on.. show it' (#102) from deployment-correction into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #102
2024-02-09 00:44:35 +01:00
66d9da23b3 come on.. show it
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-02-09 00:43:20 +01:00
930da294ad Merge pull request 'prepare next alpha release' (#101) from prep-satrs-core-v0.1.0-alpha.2 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #101
2024-02-08 18:11:06 +01:00
729ef4be05 prepare next alpha release
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-02-08 17:55:37 +01:00
0eb1a9cb08 Merge pull request 'Example Update 2' (#100) from update-example-2 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #100
2024-02-08 17:50:24 +01:00
5ccc50d9ec link correction
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-02-08 17:45:03 +01:00
176a9f1612 Improvements for example and documentation
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
- Added diagrams for sat-rs example for both structure and data flow.
- Added explanations for those diagrams as well.
- Some renaming: Use `Pool` instead of `Store` for pool components.
- General improvements for satrs-book.
2024-02-08 17:42:36 +01:00
cb8405ca65 Merge pull request 'Update Example' (#99) from update-example into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #99
2024-02-07 18:21:03 +01:00
f68221a73f small link replacements
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-02-07 18:17:14 +01:00
0fd70c08c2 Major example update
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
- Increased example modularization by moving the majority
  of app logic inside dedicated modules
- Added a new `dyn_tmtc` feature for the satrs-example which is used
  to configure the heap as the backing store for TMTC packages instead
  of static stores.
- Added dedicated satrs-example chapter in satrs-book
2024-02-07 18:10:47 +01:00
39 changed files with 4598 additions and 490 deletions

View File

@ -6,8 +6,8 @@ sat-rs
This is the repository of the sat-rs framework. Its primary goal is to provide re-usable components
to write on-board software for remote systems like rovers or satellites. It is specifically written
for the special requirements for these systems. You can find an overview of the project and the
link to the [more high-level sat-rs book](https://documentation.irs.uni-stuttgart.de/projects/sat-rs/)
at the [IRS documentation website](https://documentation.irs.uni-stuttgart.de/sat-rs.html).
link to the [more high-level sat-rs book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
at the [IRS documentation website](https://absatsw.irs.uni-stuttgart.de/sat-rs.html).
A lot of the architecture and general design considerations are based on the
[FSFW](https://egit.irs.uni-stuttgart.de/fsfw/fsfw) C++ framework which has flight heritage

View File

@ -14,8 +14,13 @@ RUN rustup install nightly && \
rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
rustup component add rustfmt clippy
WORKDIR "/tmp"
# RUN cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.34/mdbook-v0.4.34-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory /usr/local/bin
RUN curl -sSL https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip && \
unzip mdbook-linkcheck.zip && \
chmod +x mdbook-linkcheck && \
cp mdbook-linkcheck /usr/local/bin
# SSH stuff to allow deployment to doc server
RUN adduser --uid 114 jenkins

View File

@ -67,7 +67,7 @@ pipeline {
sh 'mdbook build'
sshagent(credentials: ['documentation-buildfix']) {
// Deploy to Apache webserver
sh 'rsync -r --delete book buildfix@documentation.irs.uni-stuttgart.de:/projects/sat-rs'
sh 'rsync -r --delete book/html/ buildfix@documentation.irs.uni-stuttgart.de:/projects/sat-rs/book/'
}
}
}

View File

@ -0,0 +1,991 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.23.2-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="111.16238125000103" width="641.0000000000002" x="809.2454000000014" y="463.9111499999988"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="641.0000000000002" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="6" leftF="6.400000000000091" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 7</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n0:">
<node id="n0::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="77.16238125000098" width="598.8461000000009" x="835.7654000000015" y="482.9111499999988"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="297.42305000000044" y="36.58119062500049">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n0::n1" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="54.0" width="157.5999999999999" x="830.6454000000015" y="503.13804374999916"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="157.5999999999999" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="8" rightF="7.67999999999995" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 5</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n0::n1:">
<node id="n0::n1::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="845.6454000000015" y="522.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n0::n1::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="952.5654000000014" y="522.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n0::n2" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="56.25" width="171.68000000000018" x="965.5654000000014" y="502.88804374999916"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.68000000000018" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="7" rightF="7.039999999999964" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="1565.2374000000002" y="273.72953125000004"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 8</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n0::n2:">
<node id="n0::n2::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="980.5654000000014" y="521.8880437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n0::n2::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="1102.2054000000016" y="524.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n0::n3" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="50.0" width="164.0" x="1271.2454000000016" y="509.13804374999916"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="164.0" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 8</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n0::n3:">
<node id="n0::n3::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="1286.2454000000016" y="524.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n0::n3::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="1407.2454000000016" y="524.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n0::n4" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="50.0" width="178.3169499999999" x="1107.9284500000017" y="509.13804374999916"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="178.3169499999999" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="5" leftF="5.120000000000118" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 9</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n0::n4:">
<node id="n0::n4::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="1128.0484500000018" y="524.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n0::n4::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="20.0" width="13.0" x="1258.2454000000016" y="524.1380437499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="4.5" y="8.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
</graph>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="415.0" width="739.300200000002" x="695.3113000000005" y="568.8694874999993"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="367.650100000001" y="205.5">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="290.08000000000004" width="195.36000000000013" x="1210.809400000001" y="618.4077874999996"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="195.36000000000013" x="13.645244799999773" xml:space="preserve" y="10.027339123239472">PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4301533333333345" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46543250440140804" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="10" leftF="10.240000000000236" right="5" rightF="5.119999999999891" top="26" topF="26.431249999999977"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n2:">
<node id="n2::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="703.4877874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.39453125" x="10.802734375" xml:space="preserve" y="6.015625">PUS 3 Housekeeping<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="743.4877874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.529296875" x="33.2353515625" xml:space="preserve" y="6.015625">PUS 5 Events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="783.4877874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.9453125" x="31.52734375" xml:space="preserve" y="6.015625">PUS 8 Actions<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="863.4877874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.205078125" x="36.8974609375" xml:space="preserve" y="6.015625">PUS 17 Test<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="823.4877874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="116.8515625" x="16.57421875" xml:space="preserve" y="6.015625">PUS 11 Scheduling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1236.0494000000012" y="660.8077874999996"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.255859375" x="19.3720703125" xml:space="preserve" y="-0.96875">PUS 1 Verification
Reporter<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n3" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="105.13453125000012" width="155.4000000000001" x="711.4787000000007" y="632.7428312499998"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="155.4000000000001" x="12.272399999999493" xml:space="preserve" y="3.249732314531343">Application
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="5" rightF="5.400000000000091" top="45" topF="45.13453125000012"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n3:">
<node id="n3::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="726.4787000000007" y="692.8773624999999"/>
<y:Fill color="#FFCC99" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="69.2216796875" x="25.38916015625" xml:space="preserve" y="4.8515625">ACS Task<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n4" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="227.60000000000014" width="310.3248000000002" x="882.3347000000007" y="682.1055312499999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="310.3248000000002" x="23.48672560479929" xml:space="preserve" y="6.686613155747068">TMTC Components<y:LabelModel><y:SmartNodeLabelModel distance="0.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.42431566666666864" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4706212075758038" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="5" bottomF="5.1200000000000045" left="0" leftF="0.0" right="5" rightF="5.324800000000096" top="26" topF="26.432000000000016"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 7</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n4:">
<node id="n4::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="1052.3347000000008" y="788.14553125"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.93359375" x="27.033203125" xml:space="preserve" y="6.015625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="902.3347000000007" y="859.58553125"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.42578125" x="23.787109375" xml:space="preserve" y="6.015625">UDP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="1047.3347000000006" y="859.58553125"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="70.111328125" x="24.9443359375" xml:space="preserve" y="6.015625">TCP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="902.3347000000007" y="788.14553125"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.001953125" x="27.4990234375" xml:space="preserve" y="6.015625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="897.3347000000007" y="723.5375312499999"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.904296875" x="18.0478515625" xml:space="preserve" y="6.015625">PUS Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="130.44000000000005" x="958.2147000000009" y="946.9855312500001"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="44.5302734375" x="42.95486328125003" xml:space="preserve" y="4.8515625">Client<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="127.03999999999996" x="1030.4707000000008" y="629.3055312499998"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="8.558574218749982" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="40.0" width="120.0" x="896.8787000000008" y="621.6432750000002"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="17.91748046875" xml:space="preserve" y="1.703125">Shared
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n8" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="106.85641458333345" width="167.16000000000008" x="678.4854000000014" y="467.9111499999987"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="167.16000000000008" x="0.0" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 9</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n8:">
<node id="n8::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="76.8564145833335" width="137.16000000000008" x="693.4854000000014" y="482.9111499999987"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="59.875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="118.25" x="9.455000000000041" xml:space="preserve" y="8.490707291666752">satrs-example
Data Flow
Diagram<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<edge id="n0::n1::e0" source="n0::n1::n0" target="n0::n1::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="4.0"/>
<y:Arrows source="standard" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="48.5234375" x="22.12435476073938" xml:space="preserve" y="-29.312517773438344">TMTC<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="18.0" distanceToCenter="true" position="left" ratio="0.48378541003594444" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e0" source="n4::n1" target="n4::n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999983203125566" y="-22.719979003906246">
<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e1" source="n4::n2" target="n4::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="5.000000000000227" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="standard" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e2" source="n4::n0" target="n4::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="27.85279999999989" ty="1.470468750000009">
<y:Point x="1112.3347000000008" y="829.09977125"/>
<y:Point x="990.1875000000006" y="829.09977125"/>
</y:Path>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e3" source="n4::n3" target="n4::n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="-16.954559999999674" ty="3.7001374999999825">
<y:Point x="962.3347000000007" y="839.58553125"/>
<y:Point x="1090.380140000001" y="839.58553125"/>
</y:Path>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="standard" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n4::e4" source="n4::n3" target="n4::n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="5.0" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e0" source="n2" target="n4::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="39.69774375000043" tx="0.0" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n2" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-51.37930695586169" sy="-119.14225624999972" tx="60.74632526271894" ty="0.0"/>
<y:LineStyle color="#993300" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n3" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-19.839999999999918" sy="0.0" tx="-38.399999999999864" ty="8.753668750000086">
<y:Point x="769.3387000000008" y="593.0482125000003"/>
<y:Point x="1055.5907000000009" y="593.0482125000003"/>
</y:Path>
<y:LineStyle color="#993300" type="line" width="2.0"/>
<y:Arrows source="standard" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n0::n2::e0" source="n0::n2::n1" target="n0::n2::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="-2.25" tx="0.0" ty="0.0"/>
<y:LineStyle color="#993300" type="line" width="4.0"/>
<y:Arrows source="standard" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.171875" x="-76.64202462463709" xml:space="preserve" y="-31.1845177734383">Events<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="19.872000000000014" distanceToCenter="true" position="right" ratio="0.33295067336054324" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n3" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="52.53735479999898" sy="29.121903125000244" tx="0.1231654464985258" ty="139.1977687499998">
<y:Point x="841.7160547999997" y="918.67033125"/>
<y:Point x="1308.6125654464995" y="918.67033125"/>
</y:Path>
<y:LineStyle color="#0000FF" type="line" width="2.0"/>
<y:Arrows source="standard" target="none"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n0::n3::e0" source="n0::n3::n1" target="n0::n3::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#FF6600" type="line" width="4.0"/>
<y:Arrows source="standard" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.6328125" x="-91.81636757812339" xml:space="preserve" y="-48.84101777343835">Function
Interface<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="28.21599999999995" distanceToCenter="true" position="right" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n4::n4" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="-0.049743750000288856" tx="-82.89626674268379" ty="-24.959999999999923"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n3" target="n2::n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="7.053999999999746" sy="-52.567265625000005" tx="57.5920000000001" ty="0.0">
<y:Point x="796.2327000000005" y="581.6432750000002"/>
<y:Point x="1368.6414000000013" y="581.6432750000002"/>
</y:Path>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999972949219227" y="-30.999879003906244">
<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n2" target="n2::n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="47.144060319997834" ty="0.0">
<y:Point x="1415.2947000000008" y="763.4477874999995"/>
<y:Point x="1415.2947000000008" y="675.8077874999996"/>
</y:Path>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n6" target="n2::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="39.02668799999979" sy="0.0" tx="0.0" ty="-8.276800000000094">
<y:Point x="1133.0173880000004" y="750.2109874999995"/>
</y:Path>
<y:LineStyle color="#993300" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n3" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="-38.78246875000036" tx="0.0" ty="4.884353124999166"/>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n2" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-65.73714292000136" sy="-118.80000000000018" tx="35.35927123999659" ty="-20.0">
<y:Point x="1242.7522570799997" y="601.6432750000002"/>
<y:Point x="992.2379712399974" y="601.6432750000002"/>
</y:Path>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e10" source="n2::n4" target="n4::n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="60.0" ty="0.0">
<y:Point x="1415.2947000000008" y="838.4877874999996"/>
<y:Point x="1415.2947000000008" y="926.9855312500001"/>
<y:Point x="1042.3347000000008" y="926.9855312500001"/>
<y:Point x="1042.3347000000008" y="803.14553125"/>
</y:Path>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e11" source="n5" target="n4::n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-45.90000000000032" sy="0.0" tx="15.199999999999932" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="standard" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e12" source="n5" target="n4::n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="46.63667953667937" sy="0.0" tx="-37.26332046332027" ty="0.0"/>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="standard" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999968403867797" y="-27.220816406249924">
<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e13" source="n3" target="n4::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="66.54799999999989" sy="0.0" tx="-35.74228584000252" ty="-3.0175312500000473">
<y:Point x="855.7267000000006" y="770.8415312499999"/>
<y:Point x="1076.5924141599983" y="770.8415312499999"/>
</y:Path>
<y:LineStyle color="#008000" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e14" source="n4" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="18.251857503227473" sy="-77.84525000000008" tx="-38.24174249677253" ty="3.238468750000152"/>
<y:LineStyle color="#993300" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="n0::n4::e0" source="n0::n4::n1" target="n0::n4::n0">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#0000FF" type="line" width="4.0"/>
<y:Arrows source="standard" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="82.4609375" x="-88.20577865560745" xml:space="preserve" y="-48.84101777343835">Other
Messages<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="28.21599999999995" distanceToCenter="true" position="right" ratio="0.030113173151242907" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e15" source="n4::n4" target="n2::n5">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="-7.129743750000216" tx="0.0" ty="-0.10225624999964111">
<y:Point x="1112.3347000000008" y="731.4077874999997"/>
<y:Point x="1112.3347000000008" y="675.7055312499999"/>
</y:Path>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e16" source="n4" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="-80.61839999999995" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#FF6600" type="line" width="2.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>

View File

@ -0,0 +1,954 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD>
1 0 obj
<<
/Title ()
/Author ()
/Subject ()
/Keywords ()
/Creator (yExport 1.5)
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
/CreationDate (D:20240209121455+01'00')
/ModDate (D:20240209121455+01'00')
/Trapped /False
>>
endobj
2 0 obj
<<
/Type /Catalog
/Pages 3 0 R
/ViewerPreferences 4 0 R
/OpenAction [5 0 R /Fit]
>>
endobj
4 0 obj
<<
/FitWindow true
/CenterWindow false
>>
endobj
5 0 obj
<<
/Parent 3 0 R
/Type /Page
/Contents 6 0 R
>>
endobj
6 0 obj
<<
/Length 7 0 R
/Filter [/ASCII85Decode /FlateDecode]
>>
stream
GauF[]96a;XrZ`X=6'nI']0G-Xc?4G)r9+Z>EKNG2R4%q0^eCF&Ru6"F&ec4BQ"pOe/h16:(d[p&-0iM
R9opj8<89"/O*Y3#Ch."r:0gdGZOb<A#9#>pfj+Mq>To#iqpb<5Q:*ns8CKnJ,8':s.;OT9E+^)s8Q;e
r9++WDnl>Ls7O/,aM7WLDu]Utnc/F"n6>@gGouoFD;Jt3gAG(frg14qpF?3t)"tpuCMf1E/rJd]h<YjS
mGD7bARfQBp#S*OVrM^S]f.Y2^LOJIa,g8s^MJN%[*$]\r9NuN+9200PLa+)s7ij6s.&\UXn'$/;7*dX
+oEqIa/dm=(^TflZ.K$9a1qX&k6':pf*@mSC]/>HO']tADMLCP](!2h-Hu>T=S;^nf6/g>IE'n4kSO5A
XO^DO*WOBfrQ70n4lnhUAY&#A?`_`G?iTXcT<W6\<BY6QH;N:IIs27!`gZi^+8k-f6iT#N'49`8.PLb,
%!'##j^5"I9W$Fp/Ucb%D5^^spiE7n5CT[Me'(m#dlQm1`j3eN8Aa88pLgT5YP$SgY;j:Q[LA5Coc"nD
q7V!b1p47@B7BJdI("=pBYUG0onECUNA@J\[Mn6h[ElC5`Tkj"WSc-R`NL>jC,ho7!j@U:%Li><I:43P
qu;[dKKG1>RUUq3O3bJA*Xe0$4.":CI`4L&QS>f(@'?d&^#S<R)5<g=+Q6BhrFhSs@`t;NVtG%+Xk)@Q
0D(tlctf)aIN4l/96lfRcaF;Ef-9\sj-><nh3Zcr-quV+0@X@r^4nGUIC#5<pP!J6q)#kX3eQfQ;Uc\b
I]dR2M-h:Z+9'H'7cWm]cap1gq!./m07d&_:'8mNTMlZ%;(5!26>\_'?Do]]$S&j(ZhK5RE.3g7(".;t
#/5]GlR6f#HS(`7Qf:!'J:h3oc?I:HCff%HJc?MMSIFn%rLZLaA)5s6?iP5hC7Ri3hU):>1n:0YI4VS,
^A^^S./I]*OjfdZJ+!-k^O1#snoc00R)egK(U)B_TcU'+\WZ\'M"Jtig.VaccitDQRO*8m__2I6'SI7c
mGl=G?`#tlqu9Hn+Ke36_34FdlfI<`\J!K-I"q3Tb$?[umC3J/r]=0Z^V<\0r)0=Dg)m1U.oOZr\T[o<
\2d%WGHCXjDl%nSO3t;HOla'%s-kN]:-5($<mi@nA9Ll4GlQUs'K+I"Ir1H.c1f(.WJOu\]Y<IR=)(Vn
g3AT*8"J75?_VtXJT?2!cW[;T6eSBnA?+V\j(/jFiU=Q8H,\ENqOG=[=7=.*rV@J'O1r3j^Z4ieWp9To
cQh<(hVWOYrr-ndYs,EJY)Et[cRF6jnZi4i[pN=cE<".Co'obq>(-G19+i<@rZ6C4L\7nlh>&krCW[#g
fS?=7Ho,Fs/!Q`E3jHmFlH-ckO1Fe'g3V#7p&;AO)?R<QI<hM!aQh@*1G>j]I4[?1e6#a5lF+U:L[O8g
G+5mZ\BPT9^"H3uGb4U$qhNdpB2/&bk2SN:CRt3/Sn6.XAohk^IgC%qjXO!+qc[Ef8k#fLWVbBQO\Ykm
:bmog(8r8pcFV,BE7GL!jbjY5\,0?1(?puG@HE"P7fB+^g#"9tZ1Y0)ST`?E0L8(Y.'qbe9V`:\,Ti%k
o$8+3WYs@0=b_D1cCZ?A9<`.(!eOG&Z[+K:AMm:(j&nB3@#,Nc=(sEb9P55d::)'lrGXqpa's35If4^!
ZMo<docGqE0`H+&r+BE(WmSmd:L"Bj>r[Ic"'h-Uf:hkQB2%#g^.0JtMupmG@*#\UOO_UJj!r<LA[8Kb
;NgW7&n`d6h*I)1%O<OLpV8ta^7JL234'1\^Z2X-O<07+3i_r@=NBbc1tl:1R+=!ECrA?DF;/IONN!+?
\q6^OOc@s4JTAGsDC8_2s6poMa..J$06h^[[E^Z2>*7H-3%JS+M)YAJWrIg7N/np%k_EmEH(ic>W6`6k
0AN8g=Lt=:?RW7>RM#JuNdeskZ$tUF1]*oaVS9CFLU>We()Vs5f/3L8%Ht#.aMu93iu\5f^Vrr4epk_-
&p2G)A9&tgs,0K6*?R;9>ltG3ZhD._^U`p5%Kq79Umd9:EqD]RgE!Gco5b:Bh'q/nV*j985d2UkG]%]I
6+(l_=<EPYp44#tX:,#tC_sCh=a/N"'hJ0/6$g^>J#U\m`WlCs)^pPG/XX!R8Ze),"jX/]2tmIo:@)u%
1io4&,uDY]mD?K1V#aoU5iBM4p$_QRlH9Z`pK0F_rNGt,b0`q]2u%NY8gkWa1q^>DqV%Nl\<eFGJB^ja
ro(9*EElX><L03%`]1TKMuMc'm_"=`\m,2_VbR#cX)sIVD#eF$jeBpX5#Oc;nr.jhdT!^1Cb3kAKiLSF
D,YBPcF,A\<,>TP/L8$1m%r<Ii("WgC$2-PdX'9:*mJRZ"6[oXRWbcZ.k$u<<C1udoTsANb0eH.$KRC/
&EjYli%dg)s6BVo9pi5*;7]kE.hC`jSZe-0.%TZk)FNi2X^D)$Ied@,a8NFCj]pA]lsS#nNu]dVmoW`m
*WOA)m.5#[m&[@n[?;S<,PK\kruM+qqdn4CAWf3uo%UqZmJV(]2SQ14f:2osI[PVA)V]@ARIEh`YjoAT
U%6f!bP/TBo(A8sf6Lf2EM<o,lPk?-I^pN?hFDqcYh=K9QEt`HIg=9>[q,9A_YaV-c7s\lmoMX0,PCn/
gf*2"(&`EuhOd$X?]H1CH_#<JBAa,$4P/0pI/O7&C+E`a`#:\oLIGH`[WPl&NJ!#FQVI$;YVbrS]+%IX
X,Ges8t4@b"^_k2H6MmW^@8Bi:ENoTHpbkLaak3bZZ(ldm-!9r7Bm3Epl:PMX;Z;sqK]I560S!p^E/UJ
mB&)PC[9W<#:2`[@JiiUZt`ZVi\(tE%*CGdWGL#CBPYlN5/*`Rr0jIAn"(,(nE>kJ'5`i3QL2D:0,c[R
m,.<3%pLjZg%cUeNJGgeHr)S_d.c+Mrn*,N<"-0%^K.J-7a%g?@_ASd_RdY!P-(:(7U^Eq^O"K%K9GoK
Ar4UAbcI9TF1b*3)db_EhnS2@mp%^ld(uR<CQP33ot&RT3>hZq1IbV+(P]@0Bsk.mY$RZEDSn;(PPd7N
MIq%$B!e$Rr0Uj\7)uLsiS0uU4/0DAgc8#?^Jm:,oL8TIAb5+*`"ADM_e*VU'XW/5l@g^7Kq3_\L$bVA
LlR3T`K&Td$R^tNUqA)PjY6R#B[SLG?L0=o\\T^8P*N9GjB#'%`Wpj$HDdgY6l]FB9&[[m*<k\8g5Z'u
e:-VbXZ.terXS3Lf_r\$%^<BU%WA<dbMU'5BZ$hdjb/)aa#intI86qF3cqMs1V\6`:kqXXUS5&_]mI-Q
)4c&e%Lp7$5Yc6,m#gg2S%5PBD2tk.fN1cK2X#R/I##X%a-1jL$I5Yf[o';C$$r('AC9/GdRA*F2`UeI
1>#3Tr!-YsktuqgbmXChp##;*e`-@Kh(!e<ab$=C^_tcn*=POZ[goO6gJDM8B&-'sh*(UWkpk6pHs$G&
3c+BYfng\J%!>GZ]COSO(^68g^C,%/XP<46p\A%[oOIiDS?un`o79nT8DCt]=iYbj-ALuO[c],_s8HnU
m3>WY,4Kf(_535F]OB>T/^@"=SJEU>1$Z*5an_g[VY!rDm[K(0c)`)[8=;ZBJR#pF(Y0/sdZR'.618c)
VQ;E1VO;.jf_j&g]33r>%;)GSAJ&AMZ2^r48W+YHRBiko#o*@*)GU+u?bT!5e?72mW*QK0VAZL+6,AJn
?T5s[HC=G4';t!JFNa$seRf<F"D/Os_%jn)!hpM4nFjGdBl.V:W&B5_-<9Aa4`VQ9MRs(a&%`>.5lD>P
^hEIr;g`_h]159G?Xf&8Id&)bG&KDel;:A,'BSoWUb=.1L6]A.B_[asod4m`dCPu]4#Ukn6#4pf>`CL(
CN,+HN5q5g$Pb)Gf&/$eXgg.Pg)@^]m2)oM/($C7CX>0Q:+INt#3gp4n]^s]Y]E$1#6+TB]/@)2mA%OI
QEUQpeg`O7VB"1F]&plL?gkCB[D4HFq+J$uQT1Vgi0&4ro#M]F20dG#r%>2OnYULnF'#:G6;H6)^@:Ig
-gX4MJ&eSh`t.PG?i06qh$Y4;-t[>/j&c39DG2WZjF'\Vj1o._5aeqA76UY'KA_/>T'3K_+$s"0hD^Z/
ehrqpFEqB4)5Zr*AYTr'5I6r;3hgZ3f6HSE@_g$Z>_"oL`_Tj#Sfiamg2qC5r5>D0$qA"Q_.>$PhCrDb
"V]5u,-CQ=WlQ0'h2-Q3Vm9?KVebYl%lMqYF+1[GPY\R%-Nb>56tH33>`\h$47-DEAF>#Qj[C%JUYOWJ
,PJKUj&aI#]G0l'Dt=N]`t.eWrRg\?!6<<Wre5^CXR5hMTha)GEP7&r+E.)]&;aZ=r^8Y.H4/;6r.WXM
ih0X):HU>ps8=htguhK0`Y2t*0g,*`(]T(!S];5p1=<OR\OQV#I4b!l:X&\%A3k41;/:`mY8p\Y-iHcf
>CXpn)_>j]f:is@m7E>j=?h@0@SD]fceS$!SCajuoKe`!-M]ML]U:?Nbb&Ps$$!',.a?IR`g89^LQanE
`.&?m57!AggNsPBm0]n9T]r>baU_u:[Nn!ojhlUt..f:%OQolG%k.%Oh(ml`9/?GOLa[5W6(1hPBenrZ
YJS7C?h,1&$]ZFW&Tf:G+'fe;haO-[XJ35f@>NoUHmM6iL_S`JBjlt=r+b<2b*[hVZrmOn+S9?1CQnVl
p""W686;uEX<6T.<6N%UbZq*NZb$!,qlMfrDHI7*DJN:>F=2AB._h1W.rP,ss,`#qX@#WBBWQp?l"7rc
ndI@f%u*GU+YVjrLXtDPhm'@P4`c<Z=3nAdSHSP2*TEHngT8U"=nJ#ERj[16\4ShZ!?-i2YqGj??5Q(J
iP4X"hHBCVq##PUkE"C)ngO$Ohof&dMrQ(!qpqaV](X2dIm05S[YduDS]hcrCHpA,ej"B[I_i2UqqLu"
hq"Y'[OqSfofIHaM.<F'F$8cn,$M]8I2K0d4fDh*6(cAbOfrXP5G_fL6PO_d`0/@#o>);r'Fr5)G4r_+
:jQTeFXV0&qk-P3iFF7^42PeiTd:59A(tkiQQ+YE=[?83K42C'bhXrDMia-qrje%24`nF9'\P7c%<9X*
]9B:om0C\mH8'*,!T3j0l]f..#QDVbN7e^(r,`o@JJR*^[c7h8bhZU^f<]p$+3.O9W^_hCo5>+LmW&aV
f6U$R>q=tadQms@2A5s)6^GZds(d9n8$q@S,bC=TF:e3MZX&qO6[pZM\$NFRiKsKTlPEhhmcKo6+6$le
HHdVmqhIpKRqT'c6+Om>]M%]0TR<qYUM3^]_<G2<5XVornSj7[0j7+0UnVPQk'.5U%:sh+Ft<eK;s2Jg
W%9H6\>C>2PM6_=p!D].88fm&"WGDYemi^%OHaFF"r_C@]!O^C;Jgk)C!2i*rX<%mCtPn<P-3'B(fTa5
#,5\fINVPAl>"Y3kfI$&N6HIkf@5aY6^j..j\:AE@?Ne^o4$Z7.a2Rg&#LaVAq'M.(\XD9G;8tc?el]+
PC1U<n%S^#jXM:VX@poK9Cn<=)a$IpDXc-)/h"%:>&@HVk^Bg(EI(9PnXYi1--KU7>C)r<3HP:LBqZu;
V;e>:?\PDX:g6ZgFAC3)qN.+`Zr3Qi\#_j$]D9WB:M5flfeeaIUa*0[')ML0V+GTj%Tt8EkaeR9VrT#/
TQbtCG4RZkDN!1ugmpIfDJ\FtDJeJcGP_)pYdV@i,g)7lX-+-4GIHe];6+F(U/+BCCQplpR>8r:[OFES
8kk20p^<[sWLXQ3%le`F90C_gUKXoa>?ZB`4'Pm8Cj3</CX6`kN`u*Q>.CcBh:]rUNed(!]<e?(EmoD(
^,oQA-oH?[a]^L4E62iimam*Z/+dB7i0e^j^)Ym=-SOl-Cj3<7\V(qkRW<UA%[!+HiZIpU66jnK-0FWS
?fLKiku7_.B1ormgRYH^h=;/fc<U$K`Kk[*[Q@]V2=Aa\)=Gek_q^dpq<mV;s7bEp&GoNbQi>tQZ!;,$
M_e,\F2f(Fi&K2=qX=3ObRc;['mMprbg1Yni5e,MrHJ1cI;NejIlhqu1JX&GIgb^FJQ`!nHi8HRV"O>H
q_+.ps%ts`C!UV%N#h8MO%'J$NFJ;%OcNQ$`UI+fQ9Xrf32bA0b[0ph[+m8$k&%@#:X;PS>7o(s<`;0o
E8_U"4q45^#Fu\ti@[3a=r'sp=WRfZcN"OqN8Y'fB3;pU4c["`WSd2FMgg]tp?9H-P5S:@1jDI[*mD)Y
l!S$A^RQ)"LisRjSab0@V<ZH;hOOGS(`un^>Ha0`h4VY.%7);PH,;"TdFWKArfVMHjj`b9f(Lk@r#4b1
UjpCN2DZ8s8l_&;9rpSuJabG.WF'+YMKUT'KHlkCl`^#u*[q.>MCUl9k+c1XMB:fEb"PXDb&OYhY0C+Y
*YCEOM(oqI&gKiW2]XYf#V$uF8eer%CD8G$0ujop*5[TRPr'TQ3alSLW;\kZ-:d!2o?`m)G:]8:g6KcW
Otm[]?^9+c=2WCp1-*^BaQ*n5S5Jq][tm\kOQ:"4TR(0iPZ4eZNe<(!EWANTLN&2:rtYk<b&=O?3?Id#
?AW^nmB6dNn27MQN2@Oa"?Gu)Y8oG^Jp^D.,01!:O!'d:AIuT&Ps8r)$YLNWb1[dB-s[GGE__aA"Sn_U
&S*%ROFckg>E9%ME9*SZp[?`Za9PM%+J56:O!#OoY6a\:;t:4b6(Uk`eF6]nd]-QKShID\lD+VHRg3km
+DXFY6%7$2A<M5\FqWkKi,r3b,:\fTGJ(bq,q4Ih!3Id+[hRrmSc%&:4H'"(G,^%omZkQ]6B1e9`^cj[
,EMCt4L<Z9TR=F]BX"Qre/`?3h?VRh%H]5WrslAa_&]@$#Hsf@ToBTRS7%A">c!;1`MSL_1L$[97I:MT
oR*bK:%$)VLRftW#WWc6UKqJ;O=nZk3BJQahg)(]Z:1kpq^PU#?6>gIA<ABE&J'Fbb&#E"j2jOHW(r6Q
\QaVX_O$?mFJ>oC+i>]i`MjNN.'<uR1P593\M9r9)-dEsl^*HMJor8$mH<a@6g.C=+3r8dhT&.dTbXVS
2.:-$][jf>GdUSpr&HK9WqtMeJ$mEWfI3R;4*7Vh,4J$$<\6q?jc5\c+Qa!8DBe2>[4<i\H_BVOGgg]p
:!4Eged;?T*P7W<M0rC-Sl*\]2_A)Q>-;U/b,!RY-Za6RPYr`hEqgr.WhOesc];\66ADL\;+7O$rabc@
F6@g))/$KBD^P)RffXWPK3/'-GIci-r.<a3T.M;O(OXLJ(UUsA&JJLC.ggKld<skcDa(L,*om'^ICh,8
<8bk14hXf"=8,<-1u'U(Cr1RFV%0*bPr_rSF2-2/#p$R9(o>A5Ql-ZL]"CoGGP=_#*[g\S*RlaG'X<"]
=IWVG[R@=Kq<G],;X=S#;Q#;nrD!UJb:@_T\ta@!GuGAQ]"Cpd:!Vbg9')m20@gCSYEU`R;Yh]P;VA`P
n4K8?9_V/9qX?S;rhdjhFiIIbd`Te,]ReYqPnHeAWRU-CRdK.W5MRtnhTZ&(PVBaJglKf4Fa>bqYLLD=
]i(cSSgE;>OB:`:;u$17BSUr2`/4%WM,,)b15e<$]d,UA?E$HpU"Nk&qs=R(3oP?<r+3>5-:R[TroR>;
G7MI*oX#(o!pNkJ%^?:Zs4G!VliJorR<qZ8>FTfOphf1(fJ<pU[;37qq_D9?H#IL8UX*6_r)XR*V2Vr:
+QS>m\)b#b%.,m%Z-t2P,\E<L&*'qAZfC!NkO&/='/tp,:tD$fiGGoH>(Ym=g`&Ps,'&\!Nj?2j%rW?B
&'m"$kG=*JH&iK`[j9VE[Kh$$D($p#C--H`ZZUO5L31l<&(St0jkub=bgpE:o/&'^)iFEHrgR4*l.<J`
LS3(J(uZe`(4@LmOuHF]PSqFu)r+;3Scq$q/h'7OM0NKWhY]N$/,:.SDg=2okL.VE*r+8OQc0e;pk(+9
^4="EgHA.#*@Ic[d?5_NF!$4tK718]l"IPXG2\L[^>1j;T5)Fe[,`+LX[=s,Q+D_;+nqCK5=d:=4..^\
J\93^IXuX31ODEIPGL.8EO%>Y@INO5!GEJ/80EoP>%F9_>oH4:";5[$(@*GMXr)Ja3&pY+C$8luNmd&.
8'(E/NX+lnf/O#[9B4B;6B-%_GE$\?>9=brl)l4FS5"q_Q/DJDJbH3cZ8gGm:)6pKc_.oRC7Hhh0o_A3
AMT7/Prh^"P>gOU=H=XeHMVu$)KKq+W]9;c:*nLO#N+olAL1CPXnJT2LgB&2g<DJb>GB-bCe3TBrm3X!
^N@WIOu[bnV:&+k,Bpgj52;iOE5S)#N[&9Ya,0DT/rcVf<hs`"Es[gNM`d'aY;D8/,E6ca5$[-(0$lTM
f=Am]\@BPP09@)B8/b!c46=s0Ig3Z7Y4oM=!uaZb-_7Oe<rM-O-SO;52"hICIFgI.$Ih&b(t:OSI\FcL
EC0tZH;g3f,3<LSQP;G`#BT:8+7fCc7nZB5>(Ii0b-X4\m:cfG-)p4kTKsZ"%UR>M#skOhC`9.aMIq-,
,"J]l1]qg+%\g(&",3$P7ZQ&P:9Y4$UAdK>(:!>=fYT`D*%h#4R%qX71.UZ'FQI2,Y7Jt,3[f^UaQ'NM
i@K7gP9!%ECVFhrN[Kt=[!@a]Q"D0#BF.I\U@*Bji4Zg7-;-rLk6qX2S?.Vg/^[!I>(Nr&(.O>86/2do
+3#%EarcA'X5jdgNB;jCM`W/>FRrDV(YZ]fmS=t?,"LrAXEfnb7[]l#4P,^jFYL.9q&S/kPV62J6[b[\
6%A64BB8SBnY*4f<4<NPE\hg^F:T13.fI7F(HGY05,OBY;pGc!FIVuOZ!VG;jRDJ(\oG,-93H;teU?'b
AVLX,hH)Atg<&^mq-L7J[JRmDS+?E%TV7X<G[e+=CSDU3l6[HuFkc-sok;hSW"kHM<KpX?d\X?o"Wa>n
c0sSkRl1LtFO:m=YAU["p^e+P$Li2+4];\:"-5fG&\FA:O+-W/J&6Gr&1Lqh;Ub'D)V7flG/D^rJ;_md
<,`e(ho@8)044M=+_6tJMe0lDX*`3,nK"/i.Q@NQr3sRVTG2_1Z(ie--l/?Lnkr[<:V3,-hY+[$\n$^'
PNMN%BEkoc%"rUBBdrUdR9hH8lJr^f:$u`h%(Zq?ENS^.:KfKrIE%)*$gIR5Ioc<2#OYQ_oQF_o"WCG0
TZ$BC=PPI(:#;XNA]=2?dRWEE;IY,Ca#qY;M>nHo"<DO$6^I'X;OFpt?6!=[_@nFi?2X__Gq8]QOJY.U
jOkMXYJG4d.=^,-X:b6J]Bm`N`*?i!Mu=W`H&K`)OXsKfcP`gIHB`.RTB<m((K"PH,^]F8X!O<J4$DS[
(&9A:h-\:XoN.@DV`j$$m]U.S`7oE?R?siOo<o.oGa*At2pA_L.CZO`WopS>=I#"eEa!VOCif&-XQ7<u
LM,+_]CK$<2@:Z3GNS#OYHN3kgUp/W>iQ#WBj^UmL@eO_5;-Pnn`CqY>k_\BkhOE]9o)d4[bemi1/#0X
=$/W&5k4>7C'8Eee8]ai`G=sEMA,*T'P64+'G!,lEEZntID,<G19r3gYXjW,HdLhF2"*a%Q$NL_G4Zh4
\"-8iRf(LO29k`qd;,<Y;lQBb.6p#)"7=QZd`T3-IQG^>=sE\^3$,/Z)6rjjBA_Kkcua0W]E_/X3:e2u
+jVe:AFR)#*jeLTQ?^kQK=9Jlq5UqXB\ISUQHmk"9i2jE\L'P3c"</_0^h<VRR.J?<^^nkh'ORf=/Ijq
3j)@FiAd?"iB:lAdB;Iq[c_@i3eofi<^S6_s%mQX&,`eI]4Sso#GaSt#Hc$!:&PU9dZe$rd`Ge8i5hh1
&p091TIj>IjumZOmt'N;H/u*Vlbho<]a*V:H4S`j:)5X^o/P1CSK'ra6.9!n7aQkG".jK(SPZBX>U>Ui
f6@F=mm5DoONpb*OlCTQ.[K4^kK[ka*49;r:EA/R^Q)4DYW_((]a*$ooH$h=d!K`tQ[ecC_EXUMq,cJ?
BP6;E\_euS2fnIPR_gV0r.[umJ_8=-AGHWLP9+@2o(`/?>e0-j\e>_ELpf845Xkg)fmP`[L)O-"I'pje
B9*s4di@EDH5dqh'5((\=bAG<rke/KZsJk._g]R.h6"*Q.oNp(d]K>U>sf;N"C>=Jn]Qn4.GH`Y&C`Zi
G]kPqfcepRTGSjAG@8U8q=a?m@u]*kqIs:IMB&aI;oEW2qs)%dj#h]R)L!TQIXAHEHa&MF;@K&fAN%Uh
pYVCWPX)B[UXXP(J(a1Xd;H9fnuJnG=0l]<V>ddg]#S;2hCnY(J#=5%=os=`[XG:6o6:&0<Qo'XFj-Up
V1\V%^92iPZWb&llr=!r>rEaB[n`E6IMFZ7*YB]iS9iC$B!c'$%IYE"eib7sDVdV1Gd30bbrW7Ra-Qq^
)<QQgNO8<%#MHi=hC]qV\roLH8eu9(lm2at("Q*DEQ]lt7[o8sM?Aj>\fJ)p\r\J<UYU9@&D'^,B5oc]
7S9YdHg_Fg(ND9)SBr3jS!@cuWu\/r9=td-/D#kFaCVeLp.:[V[=G3sVC,t>Nh%mgF,_5aS*;9s$a\5A
^C%\&ce.Op(J!.QG>XgN?Y4r_AGssd<!T2S9\Tt-V6#sKoRZm!ii4OWetu6H9;]N;M%WU2^ht253,;qc
]O[K0GDWeun=nDlc0<Y)s5L0+Y?L(\Sa/30Y;`2"K/c5.G0"KSrR)Xml)N`]kYLf-gKf13'lnXUPdBQ_
*n]+$fNT0tKAR>8CQZ;G]MrG1Aupa+.:27G-Q6/KM*(HHRhE5&rFDk(>E64sZe&"`C)QtG6:#NCD:J0:
FMU"/)j"6s_N(D2W*$`OrIY"-PcRgD&A,rMTG09/KKN2nN,>>4NAEIs2uX_RV+0LM=@TG1P:JC\#O@hu
):Ei\32;\;VJug9Ri9<Z2%D>u%Y%'01S>%Zj4JWbf/R8\So(:@oQY$hNlEr:]g1KoB-u>2-N!M:_PM6a
+`EghR71@d!kH;oVT,n<nQ`BdFO[.,m%uVk3!8J,FUIse)TPU(7k$8429m&g7asD@hV:!6B[fPLEJA"p
)Mcs&%Vo#'%*14^NkL'kBq->pC)VjjUeY@u?/sRQ'2c??IkEnnNC19Rj%PXmBcKXm35j<+i,<<Lb].<d
Z+4nB`j,7l>iU@L6fX9C?/sT9"Kl`0(MOl$U#FEZUKQjPdMs0\a>FG^29q;S]1MsACE,qK2%F2X3b9jW
NSsPRPo=R4j&#/Lj+-"737Jep34F@1`Yl-H;MW70*0E)'r[kQ[*PS<J)fA*4]2NmFm;;1m[.3/X,!V5h
#1lB:Ok>M(j!pcHo]?#ONdc\1cME(sV8tbOFu%gJBE<\9nNKR5:#'W,XqfIa\jUk*M"hm<V,d3,6,,ku
q8>c&n$i+C'tQI!(#Dq@Fon/c:binCcej=cOQH"8L%^aV_A;b[pYCk.^PME+KRQKW_((tS2:O7$`BYN<
Kkr]kr]-US-C`d_]<%1t7,"qPn4/&J/F=SH6j?Lh]Q@l&JNM^0F<]fn.E*,KZ1A*&GfNguWAVb,2Y69N
K3r<6^?"O@eYL*bk7;o\?2GL;n(PUZXBnVoLIPX-?aY#q5pK9VLcihfUb=<oZk@S([Sj2#C2qSEq+WEZ
%V-f9^Dce(*#GH&^Mh?HQZurrLcIS5FU+JA(SKR;%0<p&QOcrq:.-4j]GWgh!4mGiOfas66pn?!nKc#Q
@C3Bf87AtRaQ_&c>Ra`2N*72+PW9Af>b^N!l`j26k,p7,G:c#Ee^%NAX=>5YKA;F584k5L!uap4c$&J5
+%'i4aY0cIQUKV4_MmhI\t2""%g(#%)9K0qKlLJ7429Q08or@,2sh23,J2h([B!tikdCk5c4b92d&OQ8
fU9?:o*>E*,[6P$Vj6bp<nQ:rT'':ejkhaDL)r5-ZcW1JEGYG#mFipfVaG7B*WhV&R6\T`nSG#TH.&iA
DKt3pCXW[-Q71/Y:-\G!qZLc_=IMPmlkZ%@@kCc]p8R1%ZVpo`)K=h6>WjYO\.^a!nK(QS>C.oZ?/V'i
_AR$R;V*de3MZa?&l4]USDu=B0CABrT\V6XjLFDZH")RL4(+3,r(W4/giI&>3fmn(n:i^VQKVbZ8[?AU
/^o+"0f9\lJ+tonP!tD=TVh]j)q;U:&ju#$#Q"1Te\C.brd-RYACK(SnI/r$VF,pLKm0iTo!do!A$>pY
][a@f`f1f#c-^CU`:Pc_eSVtb_V^BS0^HJ`eN]:C%/tb=nq'*>Du\b0N3bE<]^#/GnD[)un;H,47_'\,
-@0B4?cH;aoUf+#-G=T?iDeAQO_%(hm_Oi.ns:B_(#/-Df>(Iu?Y#5Y9149!XL`b+oRJ9(Xn$=;rM&j7
@C9EM'<to(hRd8d:V;#9hB_Cn]h.G>k^d2r^_<b"Ii4L*L:AH!Wsc09p4s:]p300MF+g#dK,@`Yh`fn@
XRR2tNZP_[ZAeWG&`A(HqLOp`@s:15A5sIM0;MenMnQB7YXNm8nU)?f/#M.l87Xg!bdo4ICrq-?mrDb%
NcKX"m^i!2!MR9047;Xk$`Li"I<9enP3?>Qm%nED]//uMmJU]f?$JN<oqPo-b4-4\++$K7U6Kc[0Dg((
CH"l^=#!)-&a*qFotBPW><]M'Y+>-o6(VhVDeU)DLFA;*/RN(@5?rf[2X3/%i9$4_83XL7`'+')3[K\%
LpAes^S"Q<Y@-qg6(r;\PC=KZpuh.i_7?33c)J>fIV2tn*gaHH1e4]+a+iJL2U!7a0K]`9dc*GtR^#6J
bjR5AmFdoL6TmLl=UIT?_98-!B\p@RCqjB7N5GnE+MhlYJis.5mi8a,*&X&P7<&[0/pq)+H=2I\bkQ%'
cr54Ol"9O$*`RI\33Bs`>CpD>IWl4t:8X@m;DL`UrD@>*"%.GFR9jeE^\QVQhO56d&qAU29c)r8c:!Lr
ksdP2/i8+CE:^V7A-a**^9$a$hM)\,:YfW*Xl/[0^;r_a?tPLG'MWLiVp"%cknM-cI-qq0hCD@kP:Mc5
,jhIo"RUA)g#?)ETpBUWWn:mB8_n"3>B\'JN#ZW[gIjKuik4?dC2TsbR)_g%BNVrdr;p2HAWCpY/'RE6
Zh:G1`sK=Whrbmmh4q;c#0csSZW/"dc(;:D"lAn1J7F?&E_V_&p;B*RI\g83js=+$]p5W4X&oB5cUE\$
0%4'/;2l?S3n*+f;TIQ,iiERG3@Oug[5T@Fi]@mMRJIET@=HQBBX5MSG9QfSC6Y0ZKIpnUNq=sO"VPRA
_BiK!^/[[t\*Gp&Kj815E<"YW"/H+WN'nW)G^+,k;G$I(J+-DBnWXd3qMs5I:W[!ldd#ul3/FVhq1W(^
f]PQ(T+J+Pif75]pre\FF11E/BIq9@)DOG<8[?**OuE&=Ba3QZ:WYD,!GO<)%m2$$(Ar4L0Z6bKrg^r'
)cF8<;e<R98O&sXe"qOT#>#`Uc$$tmG3KT0P"q(*?Y]FQ^QN55#aFK>ZG):S.G_;To.R.`2E'lAj_j&'
S&da6NIq%%+8U^1BQ40^@X#Dc,0.u+]*1]139p79eHd.<2!Vhcf^RMehJgt7J)Bd-`RU3oa/eKBh(<H!
ql'Ta>1%+/Gqtl7:.:Z>Z3%T8#`MhGoLiaZDJ:+p1AIk+$[Qp%cFL,6Bn_:*J3P2LAbF[p6qJ.+g9#PP
IHh#B4-_FFT#gdZN#.iVb;7T.m5c9_<DnZ:+pB(\rOOH7\Veq%"5B.t<p9sE24M)QVTOA?IEng4NO-*7
LggLP],,h+$,8U0=PiI77BL<JX"o">F\3AB<G_-.%rEY[s0C:6r+NRlM0saET]SB]\i=5`[gP?%dmqQ(
SIYQIWPfi<"Y]_8igi.M`9N0M?js-6_:B_'e1XU62o_c3<R>'J9X7UmnmWNu3P,_]Bk=aMgQSSPG8&DN
P\MMQ(I.:\\?2[=)`DQ]c>MZ:S'khk!!?e$l@jXK3W0H"<`!T#34k5igPD!<Wh/`4bd:oK</h3,2?&#D
dX6"3:"h1lFGL0])VQcRf'TbIg;c(GTZ/k(S8kZLP-GB^mq(nTOH2%WjTi3"[Tr81KjUh5WE3;$+I.+6
dCAd]S_QTl%HW]O(N.6n.W&qb:uJF'p^6f6:4Q*q]Uu7uQ0Uo8AX4r&*Q2;/$`%B[d$iR?%;!"6k=/0Y
3]"rP6sd1JQF+Ct(Ket+,BFpsJh3QH473J5`@f7,4%oG;rcL`*MgQ,Vd91Lr8T4#:frk%6`X?rbe%+[E
#WIfQFB<0sS"!AtS%IhZBX^O=&V5-p_nuBgb#G(igKi`?g=BSkjEE/h)"]Jom@QPcjFKcP_#*QG3QeD0
ZR't)`D1s<=,,pjFgQ-Hcu,h6P.)rXGKXW5\@Om]a2MD^"FhNh>/B*cF49>%SElpD_3?2?(&Ms]*1]lo
DXe?R$Egf0)&1APXp@aJMujg(":[<c'hD=T[qlJcnN`N&C-d=3C>o)n;XKTj^ZD&2E)S7q]]h\@1%<KO
X^@`bMC>=qre(jmY'G()oY:Y+<ja4?8a?W=R((THZWXXOZWD*<%NFYdjON<p?]'#L/D]_73A9OFRo;u1
RUi6hr3_*e,iJ!Dj)Y"grPNF2S>f[AYc%7:q2Nl3Ga6cGb%rMk=\<eXJSjHo/=3H'93f1j3jXpRCf'Fp
QA$e@Oo9%`6/e0N`_1!3>'l+_[*flhY_-TTG/K658eDRKN#Xri09qboCO=np4KB%Xc+$=?Gk)A*8m7f>
d5\Sd.Z7>`am[)iVfc)KD:h5p?Y^EIi+lB0*aGPq(T6>aL`TofRBU/kk%\Ao04aIr2;5,2=I%(f*jB/1
/i3!@i2(WnmBsmUZDlr"fbMbODCVj!V.4.@0?j.W<+t3jI/FJTJ+b)HVVdnAm[-mXg[DAB]J`o<on2]Q
?;b+;(/2H^3(&_lGj^KilqiqX*SQ&clVWrIe>0`0Xi)/lh,7Qjmo.B0r)S(8;-p<;j#\mT<IV!j2^)8D
1&LE;?MhER=6UKanuDAH;I63-9ZjEKGNb/OI?\b]8i_>L.Bc`p*65)STh.8C00-0[&BAsr.WoKUP!P@Z
90$Z6.49mIb&@:<)kW$OVNa/#.Hd$Gj`O1FN/Q\Ynu<GIjLAsV6Rf40CFDe:jk-BA2T6F.9c6QKD2Qs9
4jJFJq;)'h]jTK1)0&?<BUMcn^lf:V$c$>LBm$KU:1OkQYu?!%"g*MQ=PXb)<PFRZ11Fm+0,_#nHnHYG
hpu*QZ+'`/iRh^p[4*>:M))s:\;[X.`F&-DO0SQM1=F=YIqkC_X:s,g7dQ+.Q][SIrLl`<2B-4\Qnm)&
G9J:X/W4C(egsR7++0p6V4c@(*8@ksDqZ(sN&3sNk"S4b#W[PnEq9h!`)NP%1?-qsUij+W1=DVcFlVo?
oQNfH8-)6=1=GITgB"KL7[cGrP`[:GdGQOB&[r@-]8YPDVi5joHXK7W>q9&^^Vd>IU&H]i?5OH;^#e'.
B@6)Gma_j)NKasshO<[>CT$@"Q`Y/9ojBF3GP!@HK'u00q9esY,rK`QjPDFqm81A>g%I=0qO`+emQ7oM
TcQctd,0qYSo,'-Q`k0?VaL5;5UC\>Rf$RedaUEei5r.X9uRMR0>(K4%#]t57FuFSVjiPW]Bl,-^"r;/
UX7KW;<[9DV4@\!g26EDmd>L(O,0=c*"b<Ar8mI>/oT>t,t+^@DX,BTf\--@RL#\Yddm10n#Dl@0]T4,
TR(_/pIjtr0(HSO>S2DT4E+D@?$c<t<W?g]m8:=Up*apEGD,YHnRWfrl\"i<r,`p,?m`r65+80-ETYH7
>_i+f-ZpbCDCk?+faEJe,;6b:R>%_[()L+fVDeqPF=-fmrA@H"'lS";E`%+Th,LjGBY5E!$K@Etrr*p6
Ihbl:DX70jMY(HORX2"sa+X!G>AoMle6A^)H>?(?B>2ClXfked4j.Yipl%tLf_>=7<Q.XiY(/$Z(LI;=
S>/f3[MiKk9XIS/?Yisjl!lsF2oafV\tnf^Qc`-5]?$Vk=,/p,rBa`>b`JaJ4H%Fr8FRB)`Kp=OiK-Ra
hRjdK>Ai;)+KN-9ZY1F!WIDro$Z?h\dT8_#p#r8+.:-Q:+m5-#i9ekt4/qfD7efTElplN(_3^'B%]lte
j"/-rY,`6uToPJ<R5L`u;_%@&$F9Fp;ab+GZWim5n-N?\mJ.bY:s#2?WGuOTj(eSM0)Y0t0;68CJJ1%g
nWSbb0VakBD58Xn5c<!t(VRtZ.E-Ab8FLgrj"m0h^WfMPBY6IeX$s'!11eb"b9"8Ro_oY=!K#Q1WkE`?
]=tZ0.Gobn&nXYjcf2,5h]+jort`>j9>CAnFsA2dY:<jKWKS+NbW_5NH,Q\:,-@h(*C=sE`GA5S;+i%T
>9**OJsi5l'`@rj+V2?0WW!R:SD.6pEI5kSSo:%7n$r=M=jDM'hu8(jZ^k3fh6WW.4fd8mJU$0'bk#TT
=g"oun=/7?S>Yc040tHT=WO&9c:"6+9[#&#rj#lY#7mh@2]G4-#^XGE'DC%!2"0rh"4[$/h6S:"$dWEm
lcKo-^R4,!n$F.c4m)s][Oc'<;r]"DE[HYU!])Wf^D,eVY%KcdX3>TrO-o%Qrd'kd8*F+$^r*K+0]%$/
n!mMO56jW=i=ZhJp+0k7_44uq2@\df,P,Vo2DVRo#'6&lm5C?^BP4u6^6$/hYL:G%pgmqsC:?@?C,GH%
M*fF,?>hW9B=!'+T&gACQr=E=)pC6I[c\[&NP*Ki3LJj8biThf1>qpi#6>]P?CG"5^J)ssOYru=@YWAW
cBm]:h"-:=[<#6c[E*A8p+5BDHdBtgB7$cAe4)o$+cfQRm!RC#e'E%TKTeJUgYK3+GC#M-lHks\]PL!l
Z^gF=5O/tUd3I`6!I:!;@q7dWY-'S(H"$?:j_XC`**d_(WeT$Vb9*UDf\]@3V+`2V#.'ieoTSN#,%sGX
3SXEa-DO%s7NOlc8L"Hh2lKS'/B\pG*6dQQ=$dB=A>_Ar`,Z#IIIYNV:QF.cZE;PkBMPJKn184!ai*u!
o(a%o9fK8p11iN[H^6I'1Wu1<1!:I+3'>C;??!:!n[)F0R:cJ)ijZ8s8djCEIM2.Ceo/,RbLt/ERf''&
ea(`CR17X(jDSWjmiq#<C:>'U4:_\ACK\sDDpNI4ZZ?@-bLp4A0tlD6/tH.jp4eUt;A)^?M3d&1ZoT>E
:Is26.gap8P"6cf/$k`p8'H"NbV;2!k<M=O083pml]H;Y/8*>A1Wu1)K3?L/Ie!??FKH]DRIAsSXK_R=
lue@rnKc-q%#NlcM<dDABE0d1`q4!fbXfULkS$4A=EfflWdtUF=(H*U;>TP-[_):[I+l3aaQ;Z6;k@Z<
*t(3po>*(/+k>EN4joUqq>_u4b_HQ4BCA?$)eI1V@ZFZDK\KQ@2$J138_RP\PZ$A+"Z[mt?U#On,OsYT
ItO.?8ZP_'^6>*?=`PF%1U?ao$X):0$-s^9k8VR!Wr+/e0Vf(d,kbF*60M2+RP&moQQ#J*ClMo9k:G1b
]?oSp3KmPuf'rZ^cd3eQ74Dcu4:Xr`a.B"aUd%;nA7*R)9]DV>]'86]UT;'HE_-]1O@!E^/5K]U:%)1e
]$071"Y7bsp"qIc_XJfU4L+drd,0'F(m20B4dfHR[c6#H%6JRk3j/$C.2OV_*gH<$C`hPE*';=ZAfP$B
0t@@S]]#Q#Q#ZDfh,J6,*]#c&,mN$PMl"Kbfqb1E<Arr/V9;rN'>9/0.#sj/+^8ZH*,_Em91[SQ.#oku
&lE/d#BB)u?(l)"/T!5iV]'m#?=^^U:76YB;3PNeM3]XWG)jk\')%Wh8aUWL4[qrc(`e(Y)+d-+4gJ'7
of06W-ETf!UFb-*b#X'&W&SrEh]\#N\2qj'L0\fcXSm&4Gk)C*fSMal**X8"m%bXT^C1"5Cm@_O7(.uK
!Vn-:CDO6VbQm1);nl#OM*8.E-GO&>Pp/5t(nDn2p?M[e,4c1#N(<$^/QIVO^Z4arI)kBm8%(g`0>C90
QI0u9jELIkOZT8E[gpf<;1Irm<SBHFoB"Rn/ZjDV`ii-,ntC?P"4<HBgJ`#6Hf?'$f&?)TZ2=*S<R?Na
Z[b]nj2G?:5Kq0Dn`Pg'#PUHFMc$\+8cOVB)j-'+h3W%=?Q+(<LX.Asq=jeBc<P$u'uY`.\b2ggO6^`S
`f,9C#<joWDi@nW![7$+8EVdt?Dp!k9;tiA)Cb!&^@nF0dqi+#@cgcWE/AU(ZB&m6,b)CsAh;#X=j5/^
^:t7Pga*S?j%koiG:`TirY$7L^Ye1kj5u_Gk&iWM@@>a'[)^FMg/cnL(.6,\RFg71X^gs,[rlJW-E^6Y
^m*,UBp)b=q$L%_Ap*4k6dEWsDJ%]q3?Eg]Z?bR-9BLBO?12B\G[9g>ALt;^>KpV(D,QiX`m>KF=L<W=
p?cj0m,Uc,Hb8H`,MLn?fA3\m19]G07&N[[_d0_@NX5LN]^E5G9!KGF=?5b\lt]nGke)"N7:&`ciHa6r
4PK1+gPO:;B\;!Fk&n"RAGW++0UgR2jTl;u-c>15)!>N2,9LQT&imtS]^E3Vb@B=L@:"/Ac_/cQT%EA^
o"+WiAkfX$D^2%Ci:"LpDIeiNk&mH6h=J5"9qKtm#0Zm_\od(W,F><T?8o/o3qKqPG(90O^2%[mfFmOQ
r=Ul`4=/Z@QIej!;g-Q"j`%<_.UB?#qCVH#?T#=$R25lUTKEM30403#/7lN6/(QL_Z19\7nW(<^?P,s,
oD8.k@O\aPg_!0[NnB3UG:m]\^o^^2@jrT'AnC=bV3mgY'h^9,hhn617AKrdYn[+6YnXPaYnVRcYnZh$
YnZgh@O]q((cef@n;^TXIM&iL(>/M\=Gk5TN`aG3Y7XDFlka+[5^0T6g%Z.P_(S5;Zl?97cI/89b)3%F
5h?8dQ?)Ie&N`#5IE+P.5<L-3;8dS071ipgV&3NqOUF6dIunram2Y[h*qQV"a9Rd'LquBXldRT8f+4H@
Nmbe1'ST\6]69sEJ(<uGo,]J\W;%qHNu;94BE&-*/>_$d'h^P&h"c!PI0S0Ip3pPIDWgDQeW?LO7Hb+6
W#VXTZS$,6=O2.Bo&a;e)=ehg^*8k`bo^FEKef?(8te?rQ=>3\X]UJ6Q!:MX6loD\`HJ+_Z'<1m/Qr4*
rUs@9g5f.D+(Qmf@Wp+Y=Gj4">HsF7*[-gN'X8?\`)=grg5;_7oFX`!e2*qGbWo']R*:Rm8tq&</B.>=
>Hnn6&oPZdU`$f%*`sS\h*(dBZ'7Qb;Ck8LSER?b7npaBnF2!<.@FFm=A!mYe:-GVAPuBq_PfPok[NpQ
dBJ2Z(*\D[74`#S9X0gJ3E7VA:l5iXR4R`q\Ks9/R":6W0q3=-oR<!KBa7(1g-q@H?abF47Br(n?Vr@C
8F[;`g24f,bQGNn91b9fO%0Ys.:NmYYnZc<a&3JhfF^bZ>RamR?&W,]J+/b5.>+/CMC<pu3:;nl8D6cg
8`NO8PXp6,/!=?jPPQ8CW%@3lh"7fQ7@[5OP\e<:HsO8GD=*KZ+mGtP'P9R>.1;AJ/1".Slt2'e=_bMf
VoZ?[*Q?lX2grqKX>[MEDI71Vl2o<(_-\*V'X_]rj0otXK-l$&i616P!@%BegJs[a^F]:To@j_cIha@/
eY>CbZfCsJ7tY/`UqH$/ks3!IWPlCL8APOZrOtj*>C5["$0XOYK5b^Xo+pg)Y*e#EnWa&CD!IZ=9BALQ
j+E^XJClCFYo;ONIWrlijJX*h"EKu$+C71jRoa=_)SM"$eaA9K2#li,_g<Z)rW_f4j!n81\>lrFBm-p`
8[C39$C=0<L^J!F>nI+lGXYd6PFa`NZ_#k-"Wf'Y2htB'>,iL#BiDHu[=EOaj?.b3aea!(KHnd]NjZ]@
rm3<<7nC"6E;$K,)^1qG66UpO)ND7:qe[SNesEn)KN-su,Ga7MJ"<@NPr[_A4`Fm>mOHnBqFJ@2]s(eS
9)D:O/%)DO*jQi`R4Ygec[*ZNorj6?d%`ida6]0&'=XR[^/;MJIe6X`7.hTYhPQGa<Pr<ieOqdJ`g(I`
Da^X`<2*A]$DL$$qEeuX1cKm^'Cdg$B&h"s7a3K3eb=mX]m$FK5'GT0\@3T(+#q_uj(@E82_gho3,N]D
DTl4$mH$:]QY2c5h7^m$/s>MR'qm.H@@b3ibhat?\q>H'7/o1DOuNucj)j]'ZF]9Qc'phMgDds(IQA`-
n\1Qn?X++]Edu&GKQU]rAsRJFfkNHh7JNn)nbcdUCe`[L0+l6JQY*@[?$*=DDlW48X]2YK>(;H2_g7l"
5;U4qA8OE_j"95u21<ODL[52XMg!h/oik"hX8=5e@&",)U_B#?XS)k0dE,-R:KiZ'nV1q-:[0XiSjBDo
W*PnQY.7]>gGnAX%`J5W,ua?J9F[Z$ld-,ggjGH'd+]g'I5o7WHXGmNh`RId\^+`dKK*Ghg+U-L`WEgP
NK$.!`f-28>KPTe*X&Y2*\A\90KK[BZo,Q+EE,q2E!h("^<_0kADL^u`.l'/\g%6g6G6YCqeU%nBp9!p
;^.9fPSPbWA,PAr3+DmS2qVUdpO#db62j$j!-.F?_=0TcXk#H)qH&T(?A^?2Gq!4K]O:\^I%R>*TtdZt
NH4QB4OurZNmp\#k^M)%lg<h+E3/YsM*p@p<FOAGCmk7Mo\!3U-Dqn@9qfn5Ae?EXHg!-&/]UjP*l$3R
<p^B2;+>];H]N^FrXS>mfe,/V;S+k.`e-mSI%tc>:>`7EidQ)L26Zn;=R-COJuEd)SiUGjn0S0<ULa[f
&M3@<)I%gCQ)/YB.LsU/B5I@k^nc51*7-<fQ=a%0PeTu\C<aOqbG.ShXhWsP)QhU.Bq`N]*@b.S68$M!
"Y]`Kl"0)PiMW%./SB56r#M7i>FsL>1/9N=pOG'[g[b]>A0g\u2Wl`&aP6:l&7+s,=epibB)/MF1We^_
an+UEB]Y%3b*JXM;r,4)QQG/Thm)>V+9K6E7tCC,Af^u"m<Jgu-:QR6bR@6ja4f0\XHe[2DrqY=`cdb?
JCEkSGm'X7jq6D?bEJr0=W+%_p*b&"F\VSABUa7`pL+O)FBaCr@IG@_("QcV`a4UJF2a+UjGkY]ieRr:
Z&G-Q)m)2j\J:rIe?jpc4r:Cq]9;Q!d7+`,*DpQsBQTis]ja)?%39?7gTu9uN)F<V#T$.-9bF3;;Nt@N
A0\ePp#^O8871oN\(_"DKtFd-:JWB.(T!$JkZjk1AS&;q\(deP(39WB(\L?!QJVK;iO'DsQ7a1@^(XB/
Fk\&*U#OFgm6idV74H:".!:k`PYp]u9>3,2J<>s4aJ77F#@!YN?1IQsH)Y$;pVD^6DleT6+M^`kY+W8_
UoXds=GQ*L)P%R[i[*"p>WZ)TSs0UOPZkRbVSsVU9?:?g[i?hh]cK4GL]/I1YI0[Zbp^!C4epM4q`]p9
GO5\m-,]E!?P92Aiu&.MN_&YgMZgfTn^g4/s2;YMK.+-!h2Ea>E($O>pNPR:jZJlO:u\INQYaolh2U1u
2b#4-+nL*oGmp%,;/6*9qi0[iOJ:8"WhS!)7c=6)..oI3ms+MEc%sL$(O&R(Z1j/RC-/87;"rS[ctg=p
LOP4(-q4I^GB*A8EbO("eGQ>@eEE$he18m'_MD)/?4iI2n\4%/`AA1s%>rdWE#1s!j2@SKV3nL_R?ljO
[TcuK9f>(P.Fr>4bb^\lm6XmmX=!6_)RHVSRc!(=G<NZT^)QO'Tc$^Bi#%ONDh#S8V)Gt#V;CPbU=?W$
>S3g@Q"VU=D6)5ce.CYuQss]6PW"imXPgG@C3:aZ?EP>X^-##9cel4gLhkr&WAifZ:Pr')g,g%G*oq</
WPKYgV)CQV<*Z.rSd&P%90u[g4\DbcOXK9>.r;[eW`bc54#;sgW?$2S2p-l@T1(NKipWa1cP4)kQ9sm'
bqda=r^IlVX!mWp8h*FB.OdtAc)<%uc1P&#HBO&0G-iGc/RCo2HBP$7NLc;07B$O&"B"H5F4>)'NnUqB
o$Y[lP;GZFUXXI=bP5q"UjOmh]edih7joOYN)Hu?UX\$BEJm\/IO@ng<L2X_TD3a*k^T,tlhp,oDlnpM
K>1BMH;GdsV&kkGq!*PIGokH8CBo?-Ir,\l1?p4(M(dBgRoQ!RldX+8\hrunn<:0fHFl7E-DT(`g%6]#
7<X[bMGnmX9'rRaMuL&0]9"e=M]@K29gn2dSG9/7D3Jlt/tnrIQZhZ5H-(J&WBiC1W?DYo,?M$T(o=C/
Xa*/,')m%_*V95^EBKHaIH.32/?Bp@#,fl,%:AM+q#5sl\l!s8=eG4"rrPu(NVeXPc/U6QEnV5D.1Zhj
`sF?N%SqJm=#)5M*gER6+rm1DC$#Y\*Y`=H#JTJ9lMW*EH3DRON"]1-_s]5V%:6UP1f4Y?-I=:a66B9p
Sk/E@R3nS>C(&E*eZR>H=V::kXZ/VsW$d=l'eq:,cR/L]'3(N+Ykh(ki0$R5.+e/5hhdgfDf&,hh3,g]
Yaslc?!"#Mr/s8tEGR[%[5kh8dph-HfVYD]-`Arj7%Bq(ZnQJ5W9lTXg]VAu0m+.CVYn9&<s!AXj!nn^
WULs-kc/pWE9[RkoOfe-mLrQZ4k9oh@@X!*Chjf3`i\?B;1.XU:fomO!Fl7P'53Mf5i0DiO./Bj=8CQg
b312!FD5`7GatG<qTmV/R6):Wf#Y3_WdFI/24W7E[r>LQXiG.8UbUQ32eNP/f_gb!\7lVMFK$S$Xjt/F
pQo?%L:NrC6X)J^Y>/7aBuo=_:a%;jn$:ZdqI^k@WkID"mnlP=fQfh&Gfp0r?C!6&_e*9HJ(feI2fs('
B.<J1cK>2L5F<be4SJr9"2:'p):iM]'H5Fs\tjGSmddX>12J1BoRh]ebPbgkhX+A2aLXl-9ZQ4MJl!hC
s%qLd23r'tdGm=_XiJqL_s+r"l)L6?k_A%jI1%]2P$M+=\4[d8$bcsDBp(rcJ^SZk/+?q>[)D/cL\<4c
26VaS;GI&B?+n!o5Ah;rb`d4.*+F@Nct1W9<j`0-*lF]-(/_#%58]O[_&Q*+TJu$+(&*>X)X%F$bXkHF
m]iQTje^V*9*+<QQPl\YUmi$9<eV=1PRQ.sN3tLFq`;RFW;@95(i&FIDi_<M?h'!eI#XYZ(c.Pn'?/5&
B)<C`%$ES?;4jZuHu@9>bN$4o"Q+IXA?::$Liu=O`@ai%GgeAm:+,hDl[VL8C<rIQ`<j7.?0Na+eo.hW
Vts1/-hsk?pe1$%>NO*K=Pg]ArQY66NltenWVlAap((J;2OU`<!]+f.\87b($h#M,S$E:X*F[ENeNK28
Zsff/ddHE`aS%Y_m`-QLVingE$d<9C?=:cSo`F`F&U8fc"m0TJHuIZuU#,5IG@?NYZ1j0J9"XQa+F5O)
c!]jo]q]:&V:&:6'I*b3>HmlK.E?Vf;ou6."_RBt0/0Rpf*LC?B#?3,>dj1:j*LO/lo*LS,/\6fTEqfL
.Y4)G4#RProG4b"L0D6SCtbcN*G+im28-M75(aX>Z(gD2SSSVIS8:^_Ra'o#[2%GCA>mKUbePqSo0*))
irs&49eS2=p(B#4g\TU_"_RBo0/2,j^?J'kN%a&2m7'%"2O<?horVN+_u6Vo6f''3Q[:4Nq=>7t)-f6<
It0ZG"_QM3=1uC3aQ%Mr)>k)\I`m8CoMjO9[tDm@3Gl4*HReE!>Ei11@Ko.@kc()\*`RG8B46kF;b?&A
9#p(nS@0rIoMeTW@TN5&c:U5u=?8e6VU<&fVimg3(RQe(V!8-G+l12J0VS<R7gPdbn,i2,&,#:f`='oL
jhuKP_,6EGT>h-33hp?YRR$<[4>Rr"oPSKACl?0g^.B5\Znk.g%XqV1KpKMWa36;b.Z)t[akHAdc=5\)
VioqW:Z\Yrh>6U^;Pr,sQhW#sYfL&9U.aZ&ok*OJDmS>&C%@m6U_fC'21Y+]/=7BJF+h8,?1[MaH7D5L
8l):Sj?/6ib%^jWlCLW=ZsK.PjG5ph_UUf7p0tN$?3/$7nZ[9g*U+8IjpK`8\i3Z\`NN6.oMe_Kkc(B%
l!;gT.-U&p(optRol]5jGL[)FK9Di$lEKT@01EKX>Q=>o9U"Y$\B7al'9H4c8muup<WBctErLs"p/cYA
_XY?Sr@JLT,u.`O,Cg"Y=p0itfKG#A#`*m3X<0+*2H'F'YP(J6s2+<CC]5p"I'%+'NhH'!V=bAEY4_>)
U9m,@XphF^4N6'_r^#B5qEa5kqRG!+9!nle2R7UBrI_.dI@_7Kee]6RH:5:7Ue,^2O)m(Mp%9>=rR6P]
>5t7<BmktQ]%7kS@es;nfsUfFrR1Oe02c!RpqsFRI3R;>_U//BQclek61r\&S.04];SVIl9FEFJHIo4)
ZL/tIgSOR_mRLHQkAdHnEQ061^2WfC!=NJKntao]Xfq1al`1hi\)t*&m(EAHqIap;.NSHP/C'$RogK[1
rI_OorBSn?%Ge2[`S[/_Ad5C"W*<Nm1G2"r0E.!\;2m]2=&37'/AAMra:[+OfS%hrh2'MUUbK3*T[GIh
;sX#h+1;)6ldGWm#&&G?e0hqiRh8$RPMTC*G-*F.Y<FnnZb(lA1nOBOoF'"*1tlM']0bjs=0h1pbSNZ]
WU\M:=E/X;[;l_p>H*d6S5-oaj/pi%.+QTMB3-j"?Y<FQV&s>O''Ba;=&N0"X:)WlZZI)%fbNaUSR%A;
K\''/A_hSArG?PGUHV<9c2XG(o<Ts?;WHGq/4o,S&JV8]c`RiVfp1oNMX'hO4M/K!gi&Y0>3(U>.<J07
XK/ZX3O$m7)Q+8!H`Uq&4Ebne?7.on"TNDoAU92:rW?gNq%)3KG&*URZ*.3!?-->Qn/>pG'_M)sfc#4m
I$a+en&hsejB/!Z\bZ+qVmW`:?q,;gO52_?!57FX);1$+hadlg%\D%ALVQ]!<NU7g1"#2>5'1hFH:G?V
8f=I`.pCqjPt=$Xl;%.hClDQVaoa@C)`Yj$a7"!?Eub,V48+X"4=SAfT?N46gis/5*g>UPDPF;R?`lEF
cPa4D-G:++`@<[LMlc7'Vj0O?`7&.fNa<Rqq>Ja`'s&X+SQ0q0CSpE!gle9g8qq<BV,fe9GH!aNo\16h
9A#5cQX#e)h[)qckCf<[XW<u8>dBA-_*gpJ@\55YBAK(T^!=4RV*Jj=;=a6<l_8jUI@'Q64c4JQ2d3(_
LQldo(n?>D%OohAMUoq=.!u[D>$:=56>%fPX4=`NS_>[_Y>\FYm^qiH\&&e]qr4"ULh"%R9Ng)Cq-BT3
`A\7loS>5FNHh#eiZE3jMHqMa&hjC"cFVZZjEG=[%eY^l9,b9A#<@AqiAm]"aa<qsE_`]Uhdr^V1?*DE
C`7>=]Ztirh_Q0UU4*<,dp7Sa`rh)4WD=V4Yf/h_Z-FZ0[g=[gISP`8*b3<jX(Z\`Y,h6+Y17RIg%GWO
[D[;Jf5O3d11dO@p'nR8gJP%f`-eg"^D'J<h;CkP<BQ*ZM<eo)C,q6M7L$[7r<PG`J"$sJWdM>f'Wc]4
n")1E&/=PCpa.##e#BfC+7J9)W8#@NQW)^LWNpD"o,?%/fnf9IBn-&dMo&15(tVhNokNnLJtTV)GWY08
dIK.V;Sk$&.^,K44maC=<Ru]k!U;7F[6B%Bp_C2@pmo[XI.c]tn!Dl5"p_iqqWYNHB:8=ops^rm4;^;[
G:Wc'%[onOBC)m,QCj^fs"<s:KBJo%<-+&..^]M?1Lopk,Q:kXlSV1F2jRFP4iS_X5B,h:ju_6$cJ"!B
gP%X0P2tosDdGJu("Uq1_eY/$C6q[\'\Q..]mm2S\g;^qhgI31N:-]8SDQ!][(%smI)Gs_m?F3L)Y9pu
JM3J%fAO[A3[JR>^.;h,-[TeIE4"!@e"47&s(F,n50Vf/lK,q-4_(#+9#&2d*uALT2Dt!SL\X"O2RR&T
rB;Zq-5V)FS`>!uW@4R5UD_M!"/t'7Fmk2D=K/Y5:7*om)72`AG.qQepVqVUGp(hj^t3h2XK!4HF4?eE
6gQ^RDsXB)O^7:'g!j<.!,ZHmPK&+>PPS\b;D5IRqW^m<BjI*#h&"hB:#-W+D`0IcPW&1t^Q=,"?MVd"
6XFuL1FQH1D\0MSN\;))mIhJk02KB^U!'QU$&cl$]70"9)7HSO!PY#a-b*!c".-PR-,,4mPmK)c8q6-f
6][Ot&+umcjhj^HX-X>9Ilf[!If3/N1pu)7s6#.ckWQN@98`W'!hL<>^3ho!%iTj_"7*,)I)mu2jhh,e
e_>g9OO]%:TMhj4HMJ+Q7(dn\3Fto<+(ftEo>T"4D^O#:kTO:s6uBVbLmFLUCb_0F$d`q90`uatYd1E<
5-0fCN0Q%<EX)I'6dgZA*tDGMF5D`Pkonf9=WI?VG+(b8kuCUKaMuI>HMOe"cM.=YbD.BcMpZ+UW]9(U
d`[D;*+a6.\sN?&f)FV&,VR"3cnFL[@4jp[I$gmCasD3#IVQE9bc(7gbG@36VaKZ#C')*5WkFp*-H!7_
]'(.q'E%iigL'^GiA`[:OhjEO)*DC:Zi3$erP'JekYLR"5KGH<fm;[9(F:6V#'_Ruae\ci^"i/sMP8aT
\tR%^R:::diln9D%RP=Jd#0,3Rf^*c&-!r#:@35-AGU&M*(]bD7c=74at1*Da6]P,%ZV@'QASocr5.q^
>'K?3C5DK8C2hO3hFb+Poq\j"aH-I\&HYb`1WWoUArfnh:3e8_.do$$O1*09qJ5:5U#Bq1-g&1.+#\Se
)XechHC5;$SA"qs&`A*.GQZ9&=gH"Q]H-/Ue#rV;K0";9ph1-A9o_qgqI1<Dp;ST0(UA?fPb.U/QZgF=
eaCZT>CL]9rT66E6@W.IguGXjJ7#Q:DMb9&<ZApNO>1hM@],:Kd>u.C\B4SE8ILL'&8ADQ;UCE<[$PO2
G/rP9m&QKson`4I)(0[QT>+ro)=1N#05jAm,3.i3L#L&#biE6Ih`5Q\3]Z\adm@@Aj=+F%,l0hs_Hf2N
Mj_+#kX4t=\ac6LR3b@,oU74*>H/(4f1ikKP1kVUWLah*4el8F#-#egME#9pedm4&fs!Q%Nu[F+C?du,
BYNS@Q,.$ckG?!^35_6,5rpYN?>fRkkISuE-W2$BYDai#`TF/_*'@^K:)?E'c:=*/E?Fe8VP3'R1m#Dq
S(/Zte$H1elCK;&XhU/!dO9O]mp/.k[J5d(.JneGjo"(#PYBE$`(t%OPo?P@\od,QP?ILM9BPWmRsg)3
_[jFN='a`^K"mRd[&>WXG8W@,_j@+lR8rf:b-Kp7%fY,[%e)A=rFYeVH?KW;?E0_K3H6InlCcQ,%G,K$
8`j'6ONA)QZHjlPE@3sBGtfH'mE/o`$@7'`@S<(;XUCSec'>L)&(_ZC?Xe0V_X+!/gA9tChUtU#m<Ea!
*Z>r*]l?-?En,@SD>+P?Jtr:8j80Yd<KI**a/mV!6@I\fM<phS.^A0=AM[Z(1Oet[SE3W)*l#aBJQEBU
A"DaR9Wm_^G@q!GXt5ra$nX9'CB8^c'2*%SrF"3ljS89A+',,QG68YXYZ/Sho2R:=K`$kT?`'rmYKo#"
o;3BB!G3Dj/_!raR!Xpn)\fV2Y4lm.B4rs:Tr$KRE]qJ_4@:VC$R/1J8#KT-Nm0@5^dko<[o74HjQN\O
SoFr0<fRnqU"-g3,L;sp1*G!16e$T/bfpL5Z.16cQ9'KJJ[j:e1d%2Xlbb`9GqM&U0`+4Q'I0Bk$iUK^
Bu+.gZ$k?(61M:a2Q-=XSd7gtGM6dPc-2r_f.Ue/pnA2flH-qr?6#^,UV6M")1aAS?9Ol\Sm))<<nU:R
68&`<2?s&4r.H4&be!&1^YJKd.rG,4&\b!pMaj]^jsO]@#rBrZ\KPA4!m'[%"V3\?p$MF%_=PmUkE?mE
N1EG@1[e<2ihW8&J^5n8SRFI6lUL@dC"49$rHH?IMQ\3g!AcKUgZdWtC/J9/5CY?88'L]_T-N32'O,Ji
>;sO((+j%]C=."-GV%Wr\+#D\hr;JFX1(XTNb5MOI/O3b@EVcgC2'-2?uOeA$[=,-K2#Q1Z?m!#X`i<=
g6<pGq17^sVsgSaL4aY<]9ro<lDNP!pg3qOm^/#V(Z*Dp'm0!V>1aX6C2C7W9$jXpH_L`C^Y\,54)V.5
\>Y?F8r^<BpU?:ZHR)[N1$@TrVISd-epk_A+)>G[o]B]GljTJ9Xd!BbTl5Dec?\/<q!FE7I^7V&lee-u
)geYJ8i<N3VnTK<1$2'C_C^6)hY'&nd.Y):@2U>&Ebt.GDur4CfA,&77!^5q$,A,$oJc+IN5eKu/XO,@
%L8hdCg4N1`kr_BI0n[pGbc0=!1Qop$VqAH-4Y>DqkHU@fPCoCqm&HQPl50Ce9'Fe72?Q4J+K/K?TdGq
MXTfXU)T+FH7uFdK5[Z>,?FAYcQ%;GbPpF[,3*LSlgL8EEGXkh/QsGunD01j<aVK!dsEeKqr[?#]s<M-
$tc&tAHpB-au2H?I#G;TX:&6p?F7d)c/?!WU[<9LK%hd@<qi0CKi,0QrC1(=R<WqXgOk7WD&q4#.$Z/3
GqAcsDX6Fd_5EHU;&l.3H/1)6I)"'cETTZ(^aDVlqPrt]ri4<COEsj(4lfNYo,br@)C>:cM.aTT`S4==
haP5:\)D*d^#8:roC=(JX2CmV2e"L3o7>=h?-K6&L1&+uk).O'R4ccEhR*u$Zli^u"hH+!q"r(Z!Q'P'
nXHnZW-7g<dttMFq@M1^X%nsPkK4"S]IeG:gA8*Q3?GQW8/]qEbbTj!Slp0P3]487Y-r./Vp%DT7icNR
4)%ht^0,hdGaS.s-`i*CfW:!r8\0W(f+P'[>8'J&o]BZG=B,)+ON0cU.s2e,qlfh@C+-,ns,*_F1VIbI
Nkir`44gl0Y+m#bjS)7tV0qmsh^%jUr;ci2nb1R(+*OX>lqGX0o-SOkXdK)GB!c7=0hJWkpqA+%9(Z7!
?fNfn\NFiXYAe*Gf]`A62BUF3o4^k*osCRL1SV\]n+a>fp_UE9fV7+uD*;_bepk`,/Tjh;9>2r%V;+/Z
0'`ePdY^A]oq#mXkWWEg!9?u;Vi:d@'/FY!bK4u[g7;CDX"I&a\-Y<]'ncM*]&27Z!Em--r3C>A]4\Z.
_SYXOXGaThf/7,;?$HKks)I4.eFpdnpS($%:oW8Oi_A=6S)U:WIcAtGf7IA(^!So!*3G>U)+O%RpACZg
M@m@"COYK67i&D>HVq$uL=/98,HX\9\?B/nU53-EjV?dWdH/cYU.p8a-f7'ZDuECkd3O!7[^UV26'bS"
QX7;hS%d)PWJpEo7"TtWcGnUV%X;g&V;lg:<aui/0.JZ13gD'V,U90D/U,*S$M-He&2Od`;XGUNp?M'7
,4BDIa*%W7bC`gtD>=co@m#2<%Yl+bn)(DI7f)8'c!FnMPeN9BjDc7['B[/Z,Nhe`R$j)/rjktJlR8$P
C`6b^Q>P3TlpG9#"li8?j#OlakrG@po3T1=`g8o@X'rZAAs>M5K_g%M#;Mrbk?AH<rgr(No_$t^P6Wfd
ATp%m2<[\6H/QVJ1F?l*=rs40C*^MUl\BlXkN5fp'<i=V]TmXZ.R["PlC(EKr#OGeZ-9q-a)h'I]NC'n
n%]j\\[O(L]h\L'LgHuYY68;N[k6KmBI2A<_XO]a]&5MjRZoK$`:4o0_2YWcG!hIAnG-YV"?V^IRik6&
MZh'+0>+&bj'@?t!1RQ.PUVR#-P%9OWNe3sL]rYJ$B&p.e0s0+m\'C`.W<&X\RPb+,?sW8h;c*l/`>BU
2o\!KkM5b0%G#<+VXkUO8dBK+K6&Mk8\?,[>`CI4UPA@.E?B%^g<O+9Is8&h*Qj:/%9M_p\)?cD0;]1G
N1LZCdjIV2n6Og.MO"J8B[q=8^S?%"1&],IDCGCA\O9dhG]RS`g<.0P4)i[$r5RLp3a%M<o,R"dJL^UD
_M.Giig9JPM0Il9>11#"fBA7J"<OQb]K;C7oNkUu`EcR)D7Z3D8Il_OJ$X2^U.QQn]g"b>N$.ZIkJ\#i
J"+gGPOAlW-N*O&a@siEnVirbp11?D_aR8G9Ddb9S\H0CVG'1c,i5I]Gf+-4\41i<T/Qofo$iE%7D+[V
@5P7h#,ed7In0H]S1fi3fbVc:<GO%H=CYqo@T,:*ePGUK((Zkc*&B),V;pPF7cKq^:1U:_B/)laGSZUD
qC-\tqMtBpc?fd#f.odi#3AQV]T=<0fAgQbc1EqS(Sn5;*L+Ad.Lj+h>,Z-b:Oi7hnRc1`MdllS!uL$^
,$p(pVg5kEPdm[5]B<'&0'$10i(9I)O4$W1`@oX!BX^fub11.tW6hsa`ISs`8GPnh!NFo#'jk>8c:qNS
a87q\1>8^?;(DOXGL.Me*W/[lh#d97d7)qG`QtG1_>&9^b2gLhas\e(Zold(HrXaBqbSVKhA9`]f:s,O
>4n$FG)uCVWuY5"HScQgbiE!^e[\M/UJqrbIbOd=<tnF]j]qIDhI#8_SS]IY$e3o?3YZTYW)RFbjSJDB
mt9Uk?HQLSqj.pX=kWVX.hW1Q[ei^<,A6]j-q43l(tWTbRD,EOTOR#?)X$0e^=hCh`L#1VROI(?7ltMl
c?bAO,%j5*9%)VV&?S0A<EEtjm*T^t+]7uWhL;[]$KbR)>#\*P>_P\]aMP!*+VM,<=".\i``o%%>O^5S
D/?0.\iDFgQBa(:'dA#opE<4j2Y/LoE#[qSOr)mGDJ%PmN$\LUIGJB2GF["5:Q.$)'Kn7AQD`8mZI`f5
CQ!+Her.s<kY>=K-nX\hCG*PmR:agQ.VC"]K'$,,<aD?4lZLme/)b1oi#$HJlLb6lO#%Qsg_70WJ[fYH
LM34s4A-2*6g7+j<B:@^lsg,VTIKLJr@n.tA'Un%hu66NQY3rrC,Rg(MM2Ht7^"SX6ndiR`_V.(o)aVU
r6k14*;"TaPSaL03ZWFj#NN8gZ!RgM4gt9KA&!V"!jl=N$2iGZi-`94.l^]".mVdKrQ;s]?TBjSfWX3k
p"6Gf*;^K+r2oo)j-@>c&_;D"[0NH[l.W?!G4jLk0[@c<$7!F9Ve&*d.q`u".b/EQnSJ6=?bp"N1$AMh
Y6\jW,$]AZ_RT:iC=i+s^IjEKe<aFDa*n3db'PQ]fLlgtr7J1mm+/pm#kfbZraB%g4n^DC]]6KD@uqO!
^\97Q3-H=:3*5=L_A4E9YP#NOa"M)tR$</&2bT)lX?.cKA&%Tu5@nBF$Gf:%[4^!TY=hm-.RdL\]e7iT
Y=j].`r+A9Y=l:\lOmZ_YqB&0'Uc-C>jE#bY=l8df;2ZClVai\Xhjl&f[ltRmWF@Lj184eF'[dm'`/mE
\jY73:Y'N,/1DGbhs4/TYEt(+C0fhHq7:[oh`l>=Wr7q+<pf9B;o*t^a4n_2B"PEq:+'4=W*+9^n[J!I
g&6_K@L"L_mltn,$@b&u[j;FSs)-o87gk3rB8nXHDguQ,s6B-V`u&GH=5.DSEUr_AHjT+SP;&H]<VQGA
YD:mL^?_C4c>$$-c3_:].;#up"lf+]F't&8&Ds)BZG:M@HBMLPGmi'i\]?&RfT&NkrGr.a\po3Z+$J=J
-'&]UZS;asok9m"knea,YhLkss+3>Fp]k`%^]'YaYi@M2P*LZ+_L%A5$t_;\@;Y+eM9C%]Gr9fZ:cY7@
FR%G`?1ZKf'[ukh9nrAI51gb+q>-0"%.@-(UNE(K45ru]NfBe0AP^>WTQWEkhtlVr]&T$L8%ft@:k+$M
[PJhe@\g27^@[QOd!8QWW7:4c5Z+c?3K!!IS^loqihc9,JtITG7PR3adDiF$:*5g$JlK[)<V1P;E<N6I
EHYBIVA#9b?^d.8G,mWloi:MbH6abn$h#kqrNb0p:29OMJ!)Ph1XO0tHIV?%)"kB0rYUVaV&61\#s(Jd
TT[#]$DL$$qL[^m667m,//`.;T`#&+N3N:7=>J@m:q:,6g[uVNRFC8>a\91G]k""p2(t,M'9F3#be0<]
PULCh4I=9^ctg=*VK_RIOdS"il7sT[)&.hLn<]@W[d,]dY>Sub,>&.:lS?B*W"^JQb`E;e1SbXpL(pH-
4Qah!YhR!YO;VJGkilT"mX9M4mu+Y^\.@d#DTPe>MI)slHAnN)-r#l=$ImMSmWidFP#UuTV3o?3'JVT^
^EiQZ?0?'RQ(6/A;(C4*QPkfQV&61\#s(JdTT[#]$DL$$qL[_8!?4OZ[o%O*DMGnHqb"j(mo]6i/WKmY
R2!><*-P=p8/8D=_#!M%orf.]8%#9Z$JTu/<t"IVqKO'X6_>q(`c9$n?3aQif<RQ;\b*f^k6N4f/0l78
8g^70Y$KRhS@&f's-cA@B>StNdW8ZEm/jO5nD.4)B04j'#Bc<&NGp/W'=P6WqIMUd=m\8"n>h=k:4LZd
DPmon?_GkTN(0W\Bt(F#U-m8:"[Z2RQA[:$G-f[ehjtEAE3'pTm;gqOQHD5FADa)0.AD$?p8QX'^0DIK
gF.fR6>E8fOcCh12sbi?)dB_]Q9K?YbGq)M>UH=0=!4D"Z/n+3Qd*h'>J&en],Sl+8o"d?]L(5rN*g<G
5Vedp(9BIWnSl3X3!F[`=.IldN.S:@9)j/,\&nG<pl_2R4WJBSS](+&nptWFm(/X<a9<7.WLXP\.+9dc
Nd?<OjME]WB`Z98@Ycp?/$-A!D,tj$0p%DW&"gEBnR_Zidn<b_X'llLS2kA/fn%l+LY!U1+Br,'PK4@)
1qmobmIR!7l&&Iq$PU&p_QL)p_Ct\6OS:BL($)rhi&k)7(!-1MXPh@h%a/U_*k[%\md9b"])uR`A``;;
b?JRm4d*>m-\;Ie!!GS1e$Co5G,s#4T(R5\530`WBM4X*F^<.Bs5<:`FmH]bJo]7"82%*5@7)u,CerM4
cq<'I4MWP*=_S*e<g@[[O<GOW,j/[GL[1!XS22RY3p3S`<T'3P>pB8+-p;i7otn)U*"U\#gu98.qe7?`
;onA^\Z(>m?MMr4;O+)Y0:BG^VUUJrk?+;9hD+sl*09Fh8?K^ngUSeglNCBbNaXnq"0WX/gt,4EN@.eU
A$0,,3i0WpkgMTDiS<3+\4iP/c!+>\%::i-0".tmEL"o<JfYj?`OWm6Bhppi9UN2=&@_4OY\WN`UlN3p
Z@'U^moVM]*Hu%+1=U)Yh*P:@6^9Gg\YIHs?u*;R)FQ[6=7i7<j+#uhJa9j.?arENCad[T`Oc@ul=P!4
Fs,`u<f5:kgi&@P#NGOFg$TWS`nqobIUT.H5Q?a"1Zgma"0uPUXsgcU2Oq&(N)\DB*3#NZa-SC>s)D8c
hrqM5A(Ss(r&YBmC8H$S2gaTIcA1`168!ScW/Y1mXD5hFN?DBA_ceEh.O#6&\^#>V4.c3nH)l,$(G[m_
B!sNZD4HS@I^3Fq^M`'KQJI-nXtT@>Trn8AT.T+\\8)MpbO\nEJ^iSmQrO)sKD49\gJjlm^Ifu$O%@)7
ID9ZW6R)Hdf/9W8I#0r2@S!P+^G\2cX&tu5V'0T9"c<ltDa)?Og0/1[?J!O&eW.6phBV<_TX\d?]r2%4
\SNUZs3a`8kIo>2JVJ[):sRpm5f"V\(leQY,PCec<mXo>-1?spn>7ieDQ2'nla`Y'^<W#bi:l.(ofQ8q
@,sm;PfX:d*OKkL[l\Y3)t$rc`aa0(?.PepUlQ<IWc.X)=fWQnP>eK@@eoEnUG/4[F0CF(M=QrJm"H4c
A3M:&9#GN8#BmWj^OO:p?=s(XOlrO5B7m.hf@")=C.qng.q?M1/dt%\BkE;b#9-s\/E59ma[g.Hh3d_K
L&S>@[?$\P`"bGuQSHA>o/4Z$6u2`@>Z`"`<eNo$Ja%eWGQ=/7X[qAYLjMN_?VI!6dtYYIj:3t!*)&Yl
06jCSMnKkE9^S2/%C5;T$[psiU*,_s@G;q>mK+uL834h/-HFc?`%R5j/)I=?deG;0$$qXZe?&D2Lh"Wi
ZuVrUk9!HS(S]aG<5MqZT]!h]f'0fRcP'o6+44<%&_i(fi$Ve*p^ZbKE+`#[S?87aL/R^!8_3pq*-&'.
7B7H+nm7O5LBWOLd=4\>41nMY^HFZoa**6>*#/#\7P;gX-.Jj-G@XhsqW^?%54;A+o[:%[6l]EGVmT^;
%GB$p7@+G1G-&&97\Yq^Atbo%5G%dIs!bCnkV9^oNO">lI(YHL@1lGs^%,%^/adVFaAD!#(&>kMZ6sHO
>BtQBZaK+2d%j`K=jWHsb>%_0-ToRDpOn?'F[XPrhl!l&-M>Oh8N@Jh4XmCq@q[X:V_V-Ro):ZS_;^e%
4)KIce#_l9".$[qa=2A_pH`d)XrmR&/]X(>2R0hLOR7Ssf6.AHFJGqs7)\F8j1]M0N'MX`Z]-d_,MpV5
:85tjc+`i/4jrV7MW]%)dcXb9gK0R:P!t&LH`MC_?Y,?KmuaC'&7LU=J'!i<]g2mOeZ$J.3<a?tdbi`_
H7U->rLIoAiGG,LncE@MX!=1TT_jiMoL0013F$S'V7t"cqNCYKYs-[KiK"kdQT,5?-2&(!PHY0qD@[Sh
aR5?)_R>-DDVdWr;uYIYJ)`7#QP5`M`_K0O@*6ked=P\:g?P""[:;T^*Qh(LZ-l6!>3p]ATgllk4L<\a
b9:$lVs]OFgYZa`*=\Ug4$M]`M9n=Jj/^K"Q"L/.TCMI,pPc:rN\iqQ5fX.l1>s,n]f+^c1R*.TA(F>4
c^][4\n.us1mi@-lrchB52d>SPD4A#UG/Tir;I]qk&83V6UO("cW;U:P_g`4P*Uf]WKVf^:)qj(T`oK1
n1TAgU:%k)j.,#J2oSR!GS2sn`&4>JA30^phQR'Lndohe^md!9%*.M4#K_JSTR`X6Kq#%Gim*rKBjjS=
G#6+0T!aJ#@lOpV,%K&9j-VE@;S"9n$\-PrT9Ch08W?GM:HraUH8,4`5&/9X<Xs.Qk8:gupa(T^^N\ZK
NI>cH8"TYkdT?AVfrWSG.n@aC3?>!^(H@\I-bq/jEN&c)0?g)W6eb-#F7>h]HrE4QM_R$;RSuOPe84Xp
UIhI2d!mlBR[nS)#?R^M.?,,3`?B)G<BQrr#s#sFUNUkZE^;b^HemE(=)IU,_g=eBk5>_uVpT`>8*[i'
*TFc,'#3]GA<.f(-,mikDOJahgD,pW&[KWXXD55gAuA?hbA>$qka%X3i'h'XB)YG,AlI9U>@nP80\jC2
,<1r9#dX?,iTB\o!a=iZnR$1C$bC]]LZW*><L+$jW;C;\32U7F6h6OlhWpJq^+.<"4`UndoR0Qk/[NQT
*j,F`4fW=YPsSFGq*QW?Y;"VPopm<LJ`Gk<V@keMhAcBhYKFRWO0lXa^"V_AcKV]ZdU0cMH0KT-pCR2d
XH\k6Oe\SZE;+"A(OM/9"s*>9;+Wok$JtCY`OEhs9BR[ABZ:d^J)IO6*7NBH2YC4*(0[3bDFWtGG_d^n
Mt,GG=?:6WN$Bm)Fehih^UVLl"IACC[fe8[H\SB'E$QDZ0BAFLn/2aNq2Rhgq=!4G>6:k;(@Lfp]A@&$
CVTMu3gOU$LbHHN-DcRb]sTJ/Pao)oQ?h!t#@s.%Hfc\-,?p3jeq&)Y*qN#hVKdSEDD1M/=kqSgkJEAc
n..=,`I&F/BMaSJs7^K7<Tkme/`b5IBo=4,2A/)@/&s^b4k?pBPC'2?m)5=UED,`9fG.`=e9i:aX.-&!
;5qX/SqrfTRl'%!%bEAE@kZI+G"Q1lo<Zcsg`=@;hh15tX[rHVCOJ/15M,b/B1-N`=)p)mpDbV$7::?e
n]MM&c"<23=?J[H1:)c0+CC4C2V`&&UZA!?f'PXPq-GRQhe3f8/]8lf?^Z3:T):qnZ@02]Zd'aQV%q8a
ZjT5)*^a,QbpgBPmRJW3dLU\F=6YlbO)$BY^N2QQm$&Tl:^]JKG?+t)QDE"W.c0'd\,PE0`VdLH84CQO
-/^AG^TgGN^cGWNPgGMLn/(O<(!SBnLJ()2Cd[WY8,)A9^>K#uj0T<*GE[HOF8%;dBXeP=MM?.oW>J0(
dIA)MGk)p.V-s6#*D7k#DTek#HF@'.>-IDN!617/.b]\)1OGjOr8M.[cG;ul=^3GFQb(Zk(#cRg/eqkc
+VgX@bHhDUhkAeRJUN3>^@YdBZ@YS/s,NE`BGX-WJYAt-E!*&f1B`boiOR`A>C!_KXUr`]19)G7VdD0"
rd<XQc0WT41)ZK%$oNL#B6:\p0A+j(Y$@q;*.\mmd_IU:_HD<-5sIO4Ng!Mj@m7+Dm37M_^D<X_\?Wc\
iCY/=R6RaFOH6Rp9-$XH_/lpaEtmr]>&3IL'2Zu7m+E#c.Vm;C!fcC0HBn2(3#O4!Xb56B>QqrN=HVnl
eJ#HsSV<<prlr:Rb22/-9$+GmEIqshb0oM^B[J$ARD/?$nsQC@V30Ak\^7:NhqC+>SR15G5Cqe<4j]+$
<Z/V]HbDGdB)SM5YCSWHO(P;q<aM&[+2/*JK3OJ6pC&S*qL;6A8FN'ECNe$`afU`>XUifuKNpD\Y:7cd
E+H(%qSo5`G>bC)M5SJA]A^5irn+=*i+U8RqTF%2+43GhG=1DrlYU`TT@QW8+djcfFO8,6qdafBm:'_A
0/R1BGG4@c41R_JHrK4b[1UNi:T7//rCD_]Z9C+jlKY0(&acDmI,E]#0A2;n5t?[YA`+&gThIV)kX2-0
,d3J-MTb$8DNpI9TDR`ef#8A<-hBd)*0iDnkhfW+@*OC,bkerhas"IQeQTM`$6fe8pRCIadUc&Y-#fi!
,4b)_042J4#sE4A]JPGdb)4Q<QSakdK)"(T>jBrhnG.8Jci:"UoY7L.ojYj(XT53ts,[2s)48*a(I]g\
)h0r@79ImNj*:RlX8qqfp[,.65aaS0AJNLdQjrYSRu9>,`eF6]c%-i/nN8">XLP&(U@8@C4T!qhrM2(f
RQ:;?):oQ4B&LEEW5GgmOg+Dn0WNIj(0A4fI".bce;2>V5$%MNKU&1aI$qDP:(R_d)Njja-@97_UV?j_
:;`X:1GdDl(=].Z,"+Y"DPn5g4n,<U&)D"=gK/]fs3<Of<`=O_52.9X_,ss9.8Q@AqRcCCSEOnPHd.d*
nug\J1N($SO"qrTe_dHs.R"cYC8;`TI[ps/];LTeb-6MgJiPP\OS$eS[j_D(Qq6l3N"pZ%NBA*30C^(.
mT?3m*LLBW`*!U,O?I-RiW=,$A"H`"F$oY60LS*;B!JUCr'@Kk*@8mLb_3*!TV/c5Vrs>[9T@n8^,K&(
\FZmNqAn(\K_SU:Dt7SnJb'^seODa]Q8%Nq<O4<(-V2#1=Ia1iq:h[CD8V\I1Md7b`/14Dag[brb-?@'
M!HTW\-WRn,i<Kddu!8sn200'9kG%n/iD803h/*\;6!e/gZ*Kp..!/1_1kE?8F]%`mb8t;0cuZejbC1+
LHLHe1?rNt2RWZ9@N8>eb]4Jo)3U7#akSWBM=K:8ZCVorpHjM5OPI9Pn^o=-N!$6DTX8UD4f6kN`hfHP
CVX%TN3obKp`+`oEq,5%3X&;T?KVWT[qP&Q>)0R>qSANZiXcRlSO&>5jB%9_PEV)dh\?_ubclu&\U*3.
mk-O;kK::o/%7V"N@1Pn@D"Ok?0UuG]/2rc^1^eWh4tDlnJP5@\ELf^Ua]H3GlAC*AbWZPd(_BR)k=?^
"(@r8YBa@OhFrlT-Aum^(-Ef@1D!?iX^KZZ://"CM3;>1'2;`H&AHl.\gK9<)`G8Q<8[q6agDtF;!2-O
^l?$@^:jj4i%qbLA#j2hLDR*&`!M2mR4\_Z^$+jcMsJ+H^<i`N-naJf[s%WCouk1:ET@KR%YM5XlYg)c
$E`d,18h[.=aU+,6]5l;(qnsShS8fFO4N4CkDkjeGq,"f6PqE$[5QIq6iPPQ2CXPu]'RPDDgKEZ9Y4k!
.r7..2S:ckMi?d<mF%FB2bC53._VZHpa*)J#gp]D"!cAq18E3gbB9WU8d:dH7#qnGBnCQi6J9&cGrQhY
hrr!Ll/F4hG5/\-b8'iUpkE,m&qKuA7G3jhkppf#_ja?!1m1<gW21:nTD?R7^&44]G#iYLC7SJPedHfL
8%U&!``=22/+@\!bt:.?m1Wn4WQHS.S,ludUr1.h$Qh=r/+plYLU_4l(Su\tD`m1rB?!`bd_o],:i-4j
TA`+p0S_;")R0$/J.Ol^d(@[4q*m/[:U_lrE%a(XTndk)R7,]?if]%4&BETdru63h(PVJ.fUA#E]c0T3
[C/n^QUK<HU)/f)Ob/ul@kE`"=m'HR\_4o+lE#2B]Eu+M:NUP.@Rm&<b`a%2"UOCcK*U_q_@');>j3ib
Ph>adcJh)ZpHl@M<hU\in!e=>lD<M1K2QAOidACq.)ubCZb/-d)4od.3LK'(l$Whr8F#U]B-b,KFlt[V
!AcFC&'u5QZ2;2:Bs)ROQO8_:@pJYjNbk@]Pm#5$.CVsaA6DX>D3,G*6jT-$PS9f8![B@WTd8r3o*g/4
Tt2UuD3m;>&eEZ'rKXhD<F_S$/%8Bdn@bB5[a54]AES2UUEhFe@>d8OkopP6jYu\+`DZMD=[%91klN-=
HgM2F5rJ=3dXA\0eLcL>p=@XuL\>HC6co<kSM%43#1#p:XEgEX.BDok/)MkX.4:1"a%TLrDq_EO,4@7e
,Nt[tK4C$,Tg-"]1H'`@dmqi?)J2hlLmLZ]1#9i)e7iL?HDB9gXB&*V>[_9n8L6dc5rq_I)jH5igS$JP
R52=2%uDMm:T`-i.;f1=>U3>5$E;BhGsPn13Y<D._GcUa%8j2C]TF@!L$(X[(SgY)Sm8+_\WlH&@a,iN
f12d.BO8Msfh(o7Q>R]2htt9gSc*oUhS6hJr:>#1jo:;f/,u*_5:US9gG!CB38Sq[3\fq\rk`C>VUrTB
%<^qF(,<OkWW6Q"0?@m8%Aa?*rBHJ@kAS\."M:JHs/?rW^jYHj@BPndrti\WjnZ_$8c6n"jT<Ve`4E["
.[R[F,'cUPefFMYs*TQs9k?1?@PWH4fGUZ4Kte/dAm$%l!U2\#Dl*NtnC:e=4R_0Sa84YBs']_VIeMuf
SY(kA8#=YkK,YGXH,=s_ESU&D:QNYo1jicu;9ls/p0GVUG@&4U2mME=/.!"8?L6P_h]j\2n;@lZhFikM
4*DOmc4N81q*O13OuIc[p[>fUe"aoV"I<9IeoTcc9O3D?XDGfLfaYo^%)%ma+`RY1M`0%XfXkGkIQ*kL
D.JP]A1-PE\Kc;c@!f6bDnWlr)m^H%X'j7_@pseSp0QU6AACj2bmG'"1Ee_^^GLsRakr#)lbp-[pW5'#
V&mH/=1l:7p+.5IhTEbioX9SdGRr[>.`#n.mp4`/'TR:_Jh*qF)7Z.#&\[FuHi:3.%<>S*r!rDbQqhQ`
qq<Q=V4+(-^NC!WLgqH,CS<Bk!BWasNO?#eM&%Sh@MqtY^-GMHd%RpY<)1pr_*,cpX7]rj`E-p%'Baa>
hXp(2'b\3`eq'#UL)Y[RCg50K9jUHR,<-IJ31p+c<l,p.q2u`dfK"EM4Am.`Qt8c-5Btm'*'\\76l\Fq
j`R.9n6mAun,$8h]<?J.X-]A9hh0rNHsb1hSN-qnnW%u$Ln3S<]Op7"*^2#NQ!\Zon,)*ap%NDf7h`"m
]+f9Jl#8VR<-"!H"eF[Fho"uES`B,%'c]I%E"L'N_B(bYO6lG_3.>O::S&X"I0@U*`k"o\T<oW\nU:17
amm(9A;qSd40;7u4OHT9@<+J[koA%D*ffDbS`ABJk@sYaE'-W(XVkd'icnc`4WH\2at/QsN[;_1pmW7a
Xb69`q&[#5*m:f3M\e'[L!AFagLBg.AoY/nbKttp.Ug5A,@m<'MOmPlceoE5'(\<c]f8Hsm'%$3:-'$a
!8@O#8c9BM4_k')-!GEp6,-rEY4Gs(9DV^mJCJr,aSSUmkSDW#1",1P4`oU`SP&6G;"1@NKAqbKgq9j@
D"!+-6_)#bKt;2C0==r]cG",\_2;h6Eo1nZNBfImb<s$$ar"4(i`_30LL8R\W-T.n7hflSG082J/A(,\
*-A4$HUk!KKjV5!Tc-(7'H@2@UZ!kSUjVX)"2F,#@ZR%2&,Ln;9C$0ZrlJ1VP`8$Y6_3[pm7Ofu4lNRV
[qHV%,;-4heS#]g9<7mb-6FeqKCRO/ftftiAf>=Z$G9#Y+[FoG[RF/:?>W+m;MBJVA'$<"oni']A5p7H
;-bp>)fC>R+qC9b:nP"IEBn7`>1=lPFtcm],LW<ie&knC)f!<i3;Ac_B+*m55oM<2Q=C3&ONfm_A-0er
&gcrZ?fMNQnKh(=j'sWON,LHnS]3UtcaAbJE^o_;Huh;"cml`PnL8NVN0!.9(n[pt5q>;Q;-k(]T48th
m-`K8Z#NCVq##!tGVXKUod(1dp9ToLc-D]$#[B<:9U5N[1#a&jCXg^T--'Q"M1:qMd5aEg57QdIUaj0[
Z.A93dIYD"]jV*4*2h?>gGkeK&$a``kPpAL"o/"64;g.g\8sMFJ%^qmD%t[BksL>E`cI"]0==s/R!5Cp
FGfF.;e1+;"Bo,Y;"%.KEuEgSFa6QD/I@-3lU"+m3R7!Mi4/js-+t*M'G>*F>S.!\lB+_Hp@lX?N[@pI
r/fj:&fpH_dNOh:385,N&J[P<T)%@SG4'",/7W/Q`sTuA4k^>i`_Zt6OG\t/1+PgjO+&o1-6N2B54?s(
3'6tJVVN?_c+`c=<Uk`gU_*aKKb9?F?VVs$d<e0/mmEoel_L"ZMp,XAIuf?EBVMo^9;l34T,$h^ER)0\
JthPX9j_G+:C/mVgbJsA9B(Y%l]*k.ct/?P0Id^`1';9/R0gj!!jgH<Ijl=.mgTXs?^6&9*m++RJ&=$g
iMpPDg-4!YbP'U1n$TgpkH"P;>s0AmrC+_G>%[W-/2P!6VF)$A8>Rf)Ob+Bt*u(&QL?H&+p_%BQo]!1#
?5PP^BWhDYf*=Y:#`)mol'J]<o\SN0TFC0;ognC1.,M,[kuTBJP*Qu([Tn3lad]Z]m,YI+A`bDlcD":6
G2j[Rb]I@WU'!F%WJ(*U6,AJnUj2=%dmQ>Vim<W;Cj!PMEEE8Y:,_+eZjNn[G06b*ig'R`F%ZK]\ZUFU
nelT]Sji%+BLSat=opA<CFe^R<.^d>"-hTh&IM/RE/Y#:M;E79\TljjQ6cN)q,fPi-;1#9f<ePu@nb6r
(i__qP92R_lAVbYrolSU*a//McPV.5+!sJ$).bDQa8Btt/\iDA.B#dWn?*bY(<$c\gj[@iT]SEG+A-7j
E(M[QCaL^orlC^PaXTDWI%?t=IYN=SLi'63@Q:)T?`<3>=PFUYbs$(U$9+Ql`RBfeYP@r7K1L1Dn34+"
WQ4D-B%5hNep?l:NE4Pc$7UO`8s]t.9;@,m6*2K8omMq-q?RTYjjPlZEIl!=nb%ZL6hZV?+$<Kmi?4+s
?EML[6PLgOeU;+e'aC+L?fU2,aZnc58ng9?Ir9';GZ&N1cp_sB%TrU_'G$U.MY[AP;"C_10YVtCqT9c:
bKJlSb2A(X4H(=Ed(6_[(fad<,s=RE!aej"3hP4&P)=f6'l-\(+;J\>qUCt5W;,EfW%mF'P]*PBC.@(&
3]o#0MiUt<Tkn]YUgbM181GBI]o\-59a[3^fm_mQ$C3V"=e%,k'j9M:V.+Esh<O1;V'7E;\Q!*>-0N^F
5HilCBN;7e.bP60a@\9Z.b:gd5h@?sK2fMHQTSMM&a3diAkGMN(h(T`4WlkTlR\WW@[nM&-Q\Z,,mfkn
O\R;]asB+Z'!#nA;6-*tB\noWA[gsul.=1Jq`2!*8SWfhm%g"H98G>3rc;JhDt*-RE]-a&S>l!@rr/fT
kbmV$EMK0+3?MLiD+S)0>3!JXVe7YF`IlA1f;;1GUN_XQM/#Dj!XcDVg\$l=pm*tCh,:..XhZP']3ma*
[3E*%UfM')PQt[2We:%TX4Co0UASC&F8HFnE*rMmT:P7#D8T))C54TMgS2lA/kUH]PU1.r\d\+38]!qL
c0Fh^Pme*R<.ZaJ[3`/E]<J.+P08aH17]pOkoKaroJbZR'r*jmDLYiT2,XbLfhTG2]skMJ^5;X.-Fsoa
d-9Q%V4ombhf$PZ.\j>E(Tcrj4bS!>reIYce88:QpsdEEcoAX`nKn#c82]glpB1)]+`Z?\5uID1n/e0X
b]<L50\/,VgJGTIm<(=(1pf_SYE1k/)7g`.8X-KSYZKX9/AL+R/#@!ocr)_^a1aHh<a!sS\@-VUH'Nh-
B?a$5Mr-lL1m]anPYc/q&FVuq'W7XHNG(;4rj_(5^UqCm(B#Ebf/[3d..?0'4sA7.G<u@`Dh%a,a7Fe'
LJO9/np>K+TDnk?r7C6TQ:2id;DKM/SeqBLC]RPSI/Y/,o7,pD^;%F$Is$N"-bF9eTeKm')%2muq"rti
fOY"D7Ugp:Cn19\n)sM[GHC8$(0fEGSK@1r3URrV6d`gMJDm!-g?89[i2]Af^V;J>UoA"9eHiUF5fY3;
D[cO%HJ\-Sir/f34W]P9e'A^Qb\9:2i-5LHIc:e]S@K7cN9D;6^jQ,%HOnf%GPpYZC'u\0Oe77>n(59m
rnN!TS)WoW,M)Agb,5<*4&KDl1:4c(-*gWh<Cg^Z`46_dXouD@<q7:XRj!O;+r9d(]>"-12k\#e*U06:
]4liBGfFCp'-%_0D\NpU(c?825K@B^#1+,`=L.c@Z`uK!XtSP]B@P]P'!:HLRQh[o=rNNT\+M<J'_1HC
K2.DAI_Rl&'siFk"[?pm4Aja,\FY1[%N8a4qAWMd]l[N#"blPQa<?7"OF?O(a,,nj(Ete8G8k:Wn8*EX
-FK<@P>QgT0k(kSfigBF*4,08pqd,5hED6nk'6hR5!6.N'VdX[0X!$p(F,[f1bLIU,5ua;*u!7qg$1KT
ic>,W*JmhaC9=+#^L)dfNM:'dqI'1:T0_&"IEX?i!nDb')&EbieF]PmE6PhrB"W.r1um-,mk_[8Mk"db
*TqNoVT4Y!8*a&'B7+HK2</7sehc?bko2E2'Jh@h3-]-04`S]MSq"d8j7!YN)/%9C`g>QD1VP](SFNHO
kF?E],L(I-RU`_UoJt3Uf.:ZM`iH9Ybd_7]1OdZRAt35.g/0nKM?;(g')*CkC`eJLp)PsUnon"RT`Qn.
O\s!tYpmdVd<(%Sk@_M4V_j(o9llkgXghm8_Flr,XPI-JhN[QRrQ0sQ\8)`p=)PbPLo55M3SlSi"kULF
8Y,(a'^b%FbijOID2n4Oii)@)Y6\!,F?Ni>It/n^FZi_7C<k)@-jpU$`9gsFQRb\>Z8Tg]SN%Q3cdCW8
)I'mYQckI^2"9AqTOC."@crgST@/@A`)02-6'XK=Y/&T_DSE/HdZcd9(%2l_G7:pCZeIYgaK!CQiCu>7
rUJ)!=s2Xc.C:%/',=UI#`c&Bg8k8'K.Y8m6_3$L`\*_Tg[QRInGGA.kPsf_`W+P<cK=6E`Ou%3J&lsf
r#L"gqt':/TDq"I3IcP9rbA'8OlLU>Dr>N$7BZ5c'Cg9S?<=lq/pJ:\W>7k5!8/Zm.7p`o.dS(^fX_E0
^K:bZ=/;i-qEfkI,oZC+C-jt1fl/\0]GFeBpaV6ek`J^)NAnmj4M9nMNaL,AEFT-')sZdT75Ru>BjVNt
@/00@JO`"um96au-KR%t`U!adqNaomid:@AoR(T1HnW77),cM8F^!kPq*X4m76p0JZB:c3F^s!3\S&K.
*'qFWbABh\VW,<%:^frP$GZ.7k`i4/.hc?*JJrHa#-Np`f.ZC4nFpP025%-b7`VQ"(d[dQW=P'[EPr7:
dt3>odt,IXS"L)#=X2XLYM0)D1i6je(UNo/Un0=7h?q?JifX"$0g]<Q9`,Ma'CW&r,L.e.eV6)mplSUe
1(_Clr?_;tnIU2bNR)NeoQ8?_@sC^b`4RQ<\0L$qn7HrB6eUIl1KRmo/r.]GSq_)_3uim?`HmV`SmOIZ
O8($,h8FXr4jie""Q2b11rnmbcgbLm*&6G$Y;t_u@ZEO5^A/aq^Q9%NZf%=H_+8KN;tZ^rn!D;/UDT[Y
QmR:8(_s4JH;p2[[55Oe4gK_Vjc$DoL=A`GFm$lOcah'mN\;GM&i8ZXr&GTsMAUi83Ng6r%[2:2'R/Cd
H4Qed>0OfrW,mY8c<jI3H!E%<'LjVDg#5XC2\3,n1[d'a-3F%^<kD6n&aUU<n@nOK/I&:4A`Z=?=[QJ"
R.W"P8S-Cj@LmIuDeQBb2Hu-;MNX`R-!&mi5b/1^N!+g!W-mld\`X;q(*P7m"Q)J;iV+E\9BD!h8Ja!K
nlr+T#,sB'hlc)sGmNhGm*H1d;&6b*(oSD=XW>fSL.p`3?8+s..Cr'6?j//\D/gjT1D_3cGS`endee`r
W>BMT@Zg,!%j'W-Y)iCLc"AW*IO*2oFgq,&2>^dM6uU=T#E5594V2:rIaa"uYtQLY(p84ZQLud_'&qr<
>\t8*4/@<%H+%\SnfYWVDXgAq)-Km\26Hn<*5Mp<%YlB3Gs?Dec42\+FP'/AQ-S\!G>gCs8d?f+9]5:+
FfiZo>1A'chs2\1p%#CK;dW]-dr9EmI9IHKjO-1=Bu5&B48F:<8.=RkI``JFO\W$ScMYTs`lWm&qY0s,
K.FlNVr]6N+!f]+OM09D50>!S]Jh&%\Tp[FX)QJl@^cK1h_sm2bRscl,aW`d3,2(3+;qt,Ai0#3`dNh=
bGR*J>^.t^#G0NCRCl["@a.YYBK.M\8Fgb+NpZ-Q/"2guc\f_.F0s4pZPc`Br+4loSU?Fl)FI6-i&I[a
h\3Nip'MIB:>P7TPk`BWBc*(b,-X7)>%caq$rMgjlSD_0&pP23TdTQ;cp+k)MB&q3I0><^khudHOJ?9e
XeGVK)F<>A+O!?PXOt7UB_aE[.We!$`APAe&^9;aDC%a;,\QG0PHmJ9=\shtJ:8H5"kWW:L)fj;D_Ra]
6Ru**b3Jq6]RP+LA[_[Uj[GWNCVXRspJ7n5apVE30k5q\++4&9kb>J?X_k"j#3<*A^,V7?GVb*jjB4Q8
fbOY$h(_9<4`SlO8q6g\=(]Q14@ja%f<\?'5s/AT2(!'7)\]>FZ`YVb5(`)g)s>='3;_)a8LouVF&9T;
6W`Y<9T/e,T\qln.kK>V\,#V5D)(#lgr^K,)Y&D0;O7;@DT&J,C1kB*K4?8WMD>q9_>!]NK,p7'jmKsL
ZYOMJ2]a,d(XqG;m]PN^V;10<-C;?GLZ+D=/ai:;mOfgep/^QK.<cSlRY9I%\@^dAGDCl[En;d2e4]3j
G.G3uAJO=p@_i5ih6kAJ/pGq0r))lc&'lkTX\0"BXJMOi-:!^K?(DncjmLQSU>0&6>KU5A/pA=7m]K;H
Q?%1@$N%DGFUjbM9ADuh?Agk`H$h3Q(soj".cW^NCgNI?48D(bjK<P0jmIbNGcOU5S'0WqI0:m@=nfhq
mQr>LOh5CMG=q,1<lguW&X5rVY">1`<gFg#?SXK`aPA^Yg7-l'K=%<,FcLXUqrg8raPa5>jXu/e?1)U4
!Qs4]BW8W!h8<g2a^)O'E?sl<>VA9j]=V$/X/$]rmV]b&eh*Fb>FfeY=5(X)$RFeRf<Iu"?(@+NfVOBg
s,LCQnd;YFh;(V+b$F?+]&`:FkCR_uGO?p$FdpM1K.$EOlpkh4+a9@ImDrQ@V5YV^I7>W/pRkgSEq_;<
J$6=_Yrg"$R`Bf#</"Cu=PKJ-s+IlUn*f"<8$u$\WON`J;'qVge?Kn*`"OMS^Tb6=KlQ\Q$_QVP]m"k:
(l/7P%V.8"=r8kW`S"pkA<E[GhcH'$_PtW#3qZ(A^A'RDb$$cM17nct:toGa,i`s?[HT2tWa/>I:e]Z]
BeX*V&37._K2bnu0TthC0<o/LpJ.:6pfAj'XOSQZe**uYd4lPXSY)TmK+?&++&q7!RSsbH2M8_r]>_RD
\Zt/Y`@/7XoA)iRq5EjYD^8,:*\PGIWrLM\(gX[URh0%#(J`lNA<s\@[Knkbd3:%Vc9#WN/?.!i2f[f)
/H&I*:@qlgG2oVOAE>u652#i!]4`9\ZqasUq9U;(H[V/%^/>Y5dd8E31L>GFrF_>S[u[HrO/lFpN0?//
+LZl#/L/5)phkZIgVR^Yj='e@"3IlpiNtf.Q^L^sH>iL*@XU-*<el!c(ltZqb*VlC+4p;5:T;6=*3$ib
'1]eMUOs/t(A%\M=&4SIKm74'1I>PQ0(I-)RiHaXI(/WGphjTSSSMITPj^M".,Sn"OFeJ@42.Ls:/`-2
GW%$&h^pP\W:p1[I*?bl`_a50D,G)E5>I)7fA'I:EQ3FMnD,:?m;B&c<9%^\lpK?NEJ@6G/J*8^o4oNF
DD,gM,Ri)SfS,,DRBo+NY!a62A*iEk"d0%sHu/__,"E:*bNQPA$K!FK`cX%]Y+MS'/_TB2igNkPQ]QU(
JJEHl&:m1*/J.#I&RAYI%8hA)>dOFe;U9<m>00:eWrQEeR27K=]SLAq7@LVo<qDHrGW&3lHfSXuAnd+:
eW)JWn&T\\XO4/2kTTVVpYWB=NNQTi?12(pnDX"APiNa`/@JIJ+fZ`J@Sqg;]8^17V#`uVqg"L>>=<Cc
hG(cOT9o!^rMM4.SJC=%hQ:k1^e>7GbBF=f:.2Hgb*!d$h@M.Xj!(#R]fN?57$M%Ahs2\)ojZ0;Y?6;K
V0,2pBZ4*<]6-0Eml%,1(fM]:OVjd5b88,LG#:-Gc3ufQFe7&8*55XMnD,9tP4jWBaRZ9YaG5;u93Ea%
BjH`5R9HcEHJ(E<n8ob(a0!dF!5pf>C5S.LmR%72+$r(!+%!VS7@\)'<utj2I9eUoeCSLhB74Qsf6V(W
.")@dlpK4FDiLnd;Rki=a$JNkon4:En+=fG's.-FNtBcABohDH?&t<_NtS#r)jDe"]sUEe6hW:lepRgs
+g$UP5(Z/[6[80_36KhkHjCP.qTkBV][^N*#Yf\D]IM&`oL6G`YcC]M?`j.obC"Ueb#!RQ^PR`Rh?3Re
YPp%m2bO5A-D)JoWO[PrG72>,9)LI)BjG''L8eCRMIF3\Dh%K0MHeR#+Wst^nf\!+8e&5o`46m6+%"m#
OgV@&Sle1/GlA`,LCi>q>1`*YHjJrD32o$\KQoW7mLHB5D1X,Q`I#"I%j@lV0"T3_cp(u/$>$4!lbp#6
o+s2$^=o2D4F[-c\26d[Yrm&L4PJu]I,P".oFr[#iTkjW6$5guE]:5`/E;#*@EDeEs16usSFDZQ21:5J
(/_*j]7$Anj=HlS/nm6h,1D[deb<F6]<t8Zc5ptm/pF@##a2gM/KW$i)EYS,&T<ifeTX@JWY^i1lCfJ=
/Op-u5515_2/+*SGKZ:GQJ?$k[*.:253'j<:qpJ8j30''.Ci[VkN0=p=3H`<27QF:_UnSdqS02a._]EZ
D>Utn[m<C'h<I)7P!>Q9UI`e4*_Yo*L>8nK>q\X\Q<We.iY!i*`Z]&IXtqkpLlU'Ypgs<7cHLkqP.Q[b
G.B,`%_6r\?]h'841.[bq@)75:d/p/--]8D.,-^;!t+KJ:k-^HE_W9Qh0.j3,A`FrAW3b7h)_rfK\R:L
\Qdl#+t6@/TAkqVHN"q?ggrm]bMDG]\gq[1hf:Zq`&QuZ)<V;k_1NQ+e1+'_mP`ZoU2+gVLYQaLcQIF9
aReN>`r,d*,'D565=WDZ>CVNboilkdrVD9K,<$88rkA>BUi'ap-bg[WSC6$f\\Qs>PW?9o`)g18lH[06
ot8$5NoTi&Aa`X@QGOWj4?62o<IYH:hL;g&Si0\!INp&:iN.5YY8(OroCUJp(-OZ;Q:dC.,K<V&N9nmD
bIJ`O1iJ$]`a'ciZ(h+),t>XR`_f5pd4gTGa&p)mN9ADkG_&_'r$lTi0hsVsJHYXILlnoOT2l\8;%,&&
]Y/?BW.\B)ockl@6c>X9B<IN88?aOho=N)mXjSV]3L^o.S0iH5k_8G9YfA!VbG"stgsSH0mQ.K'Mcn\8
>3"9[Mh6F%p9sHsL7L]#!9-//H6K2qA),"Y?_Z8VhQ,D,S=@Ve4NdF^=b$k,[-4]TkhSua$K)e:bX^L"
C@kh5GhJ#F^4W99O^.J!h`*R&]Z^6@ZC>0O,75//l6nj2O>U.T+3Yn`[@WbT37b!J'2Pcdg4AO.AC!.L
BMrVU@B>G#oub]a>PXgOX&Eh6Xtit%?cqB2%pDQN<mEteBPk>sZ@GF"1ukp3pLYt>m+$m6)cF\RSWWI)
#@W!fB-EWdfu![/MfmF+8k&L1#11`!>isLIX:@$7+L?1p#N^4USc_&DRRN9Nj:,7=DrY]5jn)m6D_2i!
EV%7@Zhq`3]s1+([/*rTUe<fDSIB/5XG3,#_658q3,`/dN$chTc\)-n&B/+&RKhW=Gsa(qFoI[c)1$AG
?n9EHQp[`qs'=XVcLn8>Sp)Or'a,WLQ\qU[=%OLp)5j!?j.N9umt23L3`lTC;$MID4R-&a?XfVshC0fd
<Sgm"/Fmm-)t.6BY&D[/MC7tg$Z7"/#$X#$4ds!GRh13Y3*(^$"]CYL$YRJ52r,R:3,h6!hWouA1cgY(
>c./=;I;%r8FL4$RA-549pl+3Tf4[9[.9P!h=?X\bW&m!);Nj?!D_+K7<]6E8`atG]'mo]^Voq6HJcu1
-(o6>W-O>/i5HPoc23C#T(dE1\rjZc<b5lenRZ@omH>X36P5I,jg8(ZLjn#*2<)</1YK%Y##Yi&8*NLb
UTHUrp_?81M[.8phOoM8.OqDffe_"tqE?=1)r?AWO#s66;K7^P[)?gKgX00B$Fr0Y'q6t!pOcoXh:I*b
A>dRW+\Gr,%4^YDPOZi]&Z(]c2tCbEJoA$eeqO,Z+U01JX#P!F[;SYHp;iBjf.feU^XPgIhOoM8.OqDf
U>SaQ;B0HpZ7#iN*jNI.8`$_X-j7P=bY[IN-F;W+Ea8/ph+L7?,]R?l0XQQZ7<eH=AQXMh-s/$9i<n80
KtU"5JB\0fAPi`//.-rV4;3>62g3C"T7=o(U9IeP=BQ?"iEeR#EQX3SV097VM9)EA.->VrA\2Of-`nfh
2*o?Q6dL``CLDDb1F'T3X^VKW;rXBUO8DZG)LGj]>qXDkAu0ib^Q(tmLr&-mf>5\`NN8.bG3kTtKFW7#
bVrrG-hU=03EFft[19Zc-;iqhgWX;cgFZ_$WQa+Ydu>&c5.i;:k,ad8hJpcMSq"el,JSM'WB9[,IO.l3
nYUtC*lG!@cWHc=gfNC_/8Fip?:_\3)t1im4s!qA`=,LhG3J@BAj17/!jmWLSiu\#)-tL`!,lg:`bjaS
i5Rc7/\bJJ59XD4PV(&AbY!oYX=`hRZI]4]Tcq0Q0jpO&m$+!`G$ZTGG[G/2hk;091PJh,%WZi>n7(^H
`pDm%?$;!QQ$Uc'7%NQ?Fd]nf?F#(!8-cK-M[mm%'iZ*1-5?[hq^_LQh/%1l@NM7fG0+,_P/(VlWkRNR
NdFd/0U2K&&!)HJ`MF6EL1QH2QM#RfT9[K75$"^=/cAV8iLb/G/3-3qDo^)/m_<;VU;,=QcUtGd39Sm#
=K&*:<kt_Q'6[DGcjDJ0Fugm<-3]YIFGj'/,50.9be)/)St75PYd-S<*kB!ng&Qs'SPBVuMbEWLHQ9<A
0sX1a3]r'4l#fjKn`GL.GB,;jX*=17L1c@II-T_*Wa+kP,skbt,>1F4AVn4O.W!3Xm[:$CX00@-m@H&h
g/BH?gNeO_r=2>+lQ31Wm@??aVV&4CGZ6W.7!KD#ZU=poQAE>t&qOC:QN`*bHGX8=Z:'6>(XHp,=1ZF]
QXfLN`0iuY#bt"UobfT&/d6kpO<ro.5@06ke*:d=[2:[N`)>)Doone7G,!3;aPFdd]U<n7p*D">Jp,=2
a`b'ElFlO!:=f+[M_Z9'fgSQFSpRl!@$&GBna-;5lR?/04/mfP-M!:'A?>9Ei%FBB^LMcmn+7=orI+Q9
:N9u(@6/L[%UR/@<\9CJ^i!u#:1C8aB\oSCLepII$a*F;IgbR?<7]qUX:J@j('ruAk@!lnq(K!QNq[^B
\GWBJdG`q,emJ%J2L/.@j7<VA<X#Sa]'dl^+YYE1g4#SPqF.#SJ]'K8gS.ZUo$2/fhCl3&*5r2;.E24O
U7C)PjR;]D0'FtW&M?4:J%$oWp^"@'qh87:haldUa$*h18D-jclVM,ZP.E%c]PV@)g^qBg'f?qA<e3Co
>Lm)6_YWF)<LKs-B4:+8IL<+\7.c32'NWm]X`eMbig5q<K&XHYWme^t#JN!?,fK\2r=R]&;cuuc(Z)`%
,i!?ah,%RabQhKX;5>Y](A^>^gAReh>25FYFI_k9h%hWEHL.btn)Z4GGeUY/^6`3W%&l`\?X%jO0n5eH
_lA7?q0LHa%rc.s0uI%2^>aCRE^n]jR'OY,Dq>>r3-$cj^6OT*+,5koL"26@^2RsHa(`LU7/V'J,DpcV
X4P=sHmr;^Q*"(]Yd38_US47B_58OGQO*q=cal,ZC=ce(b8JK1;$VHh<EE<@Ytile`b:1=2C`Sm)+K[L
=27jF-CU0A3n;(6@@MthWgDk%YIN<KW$OYo&^ELG8^:21\LUpBPDMe#Dl`eGla:.ZEXJD;W!IN*Wj2i.
\&.b=+q:YD?7YU>>MWD%T?1'%,(@LAF%$9G6]hZ[33t(lSgJY^k-hNN[DkA9IAcm%AY68/F[_r!3,;^r
pD%$_q-'o1&J)G]nf`KS45K/<]It"Dq69kqNN2/iacr0^1c/A*6FKgnMt?-[KU!:!m3gtDP@jaF-JKBs
\Nj_[YI\h*Za/_@KtheP3C,SZf+@S;hf?)7n=,q-8TF=*fq&F?\d<Sp\%sS")l\L%C5<oTH\gu$X*9Y<
Gr(J=5Y1NRV\NbO-5.eYhO/%=]c3dT\[58XReD3O(S5dr<$C>fp]59c.:m1sFXa4^,/ng/P^Oi8k)I3D
2Ck?8D1e8L*%S'rg8sJ8G3c)=B$[q5S2n3>\RUpNk6)^i#H?QfRq"mn9!@Pg,!50:qEg4-*;DU7oV2k^
r=9l/V_+MIiI,-nK(QIa<6)/W:umXN`1+;Y#3G'i#7Wof&>%*%K.VuP+LCKd6Tpp"GeYru%V-`Ka)c^E
P[t(6E6IcJ,aE_0JYA*XA%^G:+QEoX_H\QRH-[u9V9tIC.:jW]:mejSs(/)JepBQtQ[]a:(o'U$<NZ83
hKt1l>.#UN^%h<a`e0\C)YIe%:2-&^dABj>-I*N>)HrF@l7dT/qfq,Q.1%rZfkPQGP;PpAjo<D174C##
OCoHa`07`7$K'V"_l*.Wl(g!?bqbb[Ta!f;')2o@06PiG&+.eBBZ,$hT=!1JQhkgkZ@&bLBJ#c^\&)(R
Ad#p%pajQUTT>)qf;)j$J[h.o$FdNHBftR3%[bJ.dO;Y]<sHYFJ0ZbG:HmGoF&(O'l"um0>B-Qjd^VRi
U./?4BZTjIGqQD5U@S5,ndhQD4JC1V5K)8mnVhd5(#\G*TpfNbg\>'CMNo&@68$XrSeS'\8E[&6[!#en
M=CZmH]LT5g-Ck"YGm:P'1^[9UV6QN9N/i1BB=u;0]();0:DN9@AVUJSZ$V3-KHObR:Fr5j'+%R0H^eM
#an%/>&dulhNUsgPHJ!LTMA,I;d[*cVol[hi3V,F'Xi1"(Z`:[@?GC!TlMIElu7qdV<W!#@?&92]3@J?
?:6iYE'<G>AJ_U@DUsi*3FEc?)&Z!aoa^ZP(qq6ZXL>`S57XD7$aDe,[;'tB]$>2)+R&L"iP?P8[hb;M
SLElPXP%/_/Y5<9;K2n%oZll%W3#FCaXoZrHg?cI1(#?$n=%sGa"$2=modOj-&pJND$Hj'FJqR@5#KE1
g%OA=XNVRXi,>2_e]XEW\M:M-T\]I[41'V$U<0)D#""];<g_!QaN@%qGR14)l_<s'!?S?t#E/8OgUa`n
_hhYL9]XZEX^D1SrR)%5>q8U5&TOp,$b;d2@2A#=C'B?J\lP3(jiB56qgN)O=S"Wp"YXA)eDbAb[DH%Q
pTnKX=uisA:hU$Qe-`390!-gnF-(GBCg%A!$9W$ET!9f2p#7B+4-.rZ.8$^MiN;4i$57DEXqdHoW"gBO
6]PUbn'DEJp]s\*]_EPuZiYl,O&hX%&t3!4k"A<U"RVA?PI@d?9\/?k<Z%>&`3fYf-Fm.oS$\hh(aq"Y
[)/U<4JD.[Q"ItO0aEd/GMo]kp&$oFkH?fg#5`]kn9Ld3Ds`"k44a$'m1kkh5,,j:l/R@S*cdUW]$sQ'
K11!'EG<QM0_`mNVYb_to%;:C^-%dGA"dS["0Z3AnoVbqadV)@lF"U_YqHngEa;^J?C]ca>W[=1Cu*.G
jN53",WiQd0hJ[W9:a@3_sOlc*P3TM3BU]'B+W+tgpH*/P-"%kB(6;nBHq&U\a@Qt.:Lbaj$]4ZCN.&P
P/M+6f8q8Yqeib'5Tr]=;j+4WCQ=Qg%JNF_2OegT9;DKk0C8o1[P+%9a'1L^@pG2``*KlG&u4Ej0:bHY
8KRg$V//ug[P&(3qf4(\'duY^r)o0L8UGhbrnd'o1Kh!6ASprGW#Y,=a[\lnZK>^!MDE5d>k0IFP),l#
P!&Te2)8mT:T`&.l#U9dn`Cagdkf:j@cX,/C]2WCZA2Z.S,$`$\`RhRZ_Q+>[^WR!,`[];qTT:fZR6,9
k&bUb5%lHfWGkCZ?(I0$32DB1rZ+e(/7OSF\[,hK1Rm"a5,R?8Lo0MYAi;,I+b;Q=ilO@=A$"6-gM0.t
oBPj>S"At-GV5s5Tl%d.ofu6tT)&8I;n_AY<9Gu7)j,cmDI<c@AL.Jed/1`Dhm#EGmkWamM@P5h=Vk7d
nb*PElZ**K]#.[Q=k/,7F]8hagr%*#)"j$YFr3,OM^UTkB9V013NZ5^aPMM"#1s8<VZlL6,KZi5BJVo[
mXqf_mlR\rg;.,+AHUn<bq-$S#Au\d%>6k_Sb;L)hpK,d+%P@n*%<fq:5@5FCo#MCW\^-pRQs0cG;RX^
+ZpTa&l0$u<Gt2`;g`4Gm9@2dKS5;T4ml"1M:!l5B'R*.6M?2`3qQb>Xhh,c"C+E-EGjZ12\d2YYgL)0
Tf6t#^(L@-kumtuI4G,E^=lShPWe;aSAkI?M;J3<Ehlu@lbn"boAPKU#IFAbA"ejQr2mQcm?:u!]Q)(T
cGJIi#IMg$4TCH?H+\-F7f$`0EITmehgqsi/BuoOqt&DY:ER&ZjaAC<[BfW+421Pneu(F.WgZ1X^"O=g
-p99eGVcARf$E\57hq&D?V8g)k*?W;*(ps[Dq-]4NpuD^\mk!pejQln<T.*OCf^dM?_=8N=I+3?W.OQf
/2]m*P_no=<i+?9"tS9i44<3*Cdl$6oh[/-rJu)38(q$[eQ7O+>*CRC[=@f2nid+0'UtA7M5ob$&r-E2
I[NUmJi9\b4Bk%<oGd@TeF/=KJP)9qgtTkMYLY-tX+a(s\p`$]79sCMK18#TRt"D,H@@W^]bYW&E=Rk=
THotH4QA`t`10PFnGAnYkrs\2fqV1:**92aS'hfp7eS')L+/aILZ()t%egqhO8c1<2>$uF-%?DbF_XER
ItA&<"9PV^-u/K!e9_da[f!]WS/hsd(ulg_Nppj"[FeYNXdVFQo,?3o<*?_emTDEN'k#l/.UM.?pl$SK
No0WQ0g>Vt8)iAposV\43I978FUWWQ[o:%Njn)9\JU?n&V:Du0psP1BYL]j8O`EhX=j0$/S^Xf$1kQ(A
Hd49ifY![&'oA3:M.;4@4sbLGRE-@&TDsTaH0W8J\=4b[==lGCoq;_mDDfYVep]0/<a<L2Fl)9Ul;u3"
Sj,1VN.19\d>?L&BoY:7OCI/-XaZAc>;E8qJ%"7pY=j9@^_b%-n\c):`]-3Gj54"^i6VBWXK!/=9:@a&
pcEG=XQSKG5QC1'Ab.R)$jM`j9(mH+*OmEGWE$q&?at+mKBL:I@-EC3q00K'<iA@b$ZEdcc2PFen#p_\
7t#VO)q]u5i7,%1U8-"\_]B=*'nEL]M&tL!n,&%!V"'GJn$**"ot_S*QTmDMr1_<R2f*D/Q!olRkb$C=
9-^LHZ=6N<P<NZf'k,1h5bq7G4'&gHg`NYfaB$)ANT@O4T?ZjL<R?M]*\liETa@NrH,@b;%GuZRdu,!R
hR%!rg8Y3(annJ@6I)($T4i1#gQBK:YLXt`&s:2VokjWqI=*2Ym@]IRlCQU((^H5ob=[)MS:@6l'qdE'
b)e%:B@`(aW8UFGZ-B>WkulXZG#3baqQlukC3PS'.dLR$r'Q;nRc94rcbVIec8F2d;#s9TnV&W=EC"/#
jr"12Sh9`C[q<iZ+\fq4`AIT`):Yu$*j4!!6FbtLp""VqO\+iR$1`.VXK)*)b(L_I]R_T>OA=:-Dj=R=
DJSs1?_=8XmM)$W1BD78X>]U'?(JQ1PGj2+l6("EZbnJsNSdd=gT<3.Gp@$Bi>BmC:Uqel;Ff'Jj.`&4
L'M[slmI:Bgo5H:5gJ]iR,I\XM!O"tbF3bKn16L/cg&2G7&3PS<Aqs5Kkj%3#\X!TI`]Li-@\VPm?TX"
)<"1`\[u;t3+>&NNX'hZ0^DXUU-'8ZSaoH;VcL[Nq[9s[JNm^`8PMcGojbPP'0_qsh`,g#N82L=]#[i4
Vqs">DRg`9`PBFpccUS[1jHJZn/;8QH@4_FT:UPMV#U2DHmgMO=d#+'YkGoUBK@9&<b-/$O*7j5J)Nb_
rME/?PM8gQW!^csS4!nH`ApY238V^T^Z3KqYUkbS,EQc-o"ctI%F\It1iX7QXWkZIb#i/>d0-dS*_?2T
4Lm!!>N6Q[mj4TP#0Q$)2V>THYG$I5dW3PlH!6-u0QWd_##<p@I!HK$_k)F"U5(<@KNgoQIp?)6k"R4#
Y%.)0L&<s`C$fh`a)8pP+0=FEolW"@&TVMNZWA4pANqZhVV;G3PWt1f@.^JHC6VaP)H"=PVD86,oa)7?
]1a(%+F*GK/9+SR$I5mM2UGp:Rkp9V.OF>jWf3\,d7%d,9-%9cNDYEZk"sa27(!YQ:L2;?hRf_`C9bnR
>#h0Im.SI^S6kQ4.VMp$9-)el-p':ik+sh$:!9VRNjEa)2mKG*EO01R4Y+Zu+&4cZ*mO'!90V[EQ[Gmk
>2?(5bbrUgAW=*3FAr!NBj3:snAWFFgO4Pk9>kghE\:/5(3W?h0eF""Z#iVaB:kBignnsrTosB/C_'OA
5F.+:Y,nNY`LIX:p2MXGS@6FnqS$[#"-GYsd#+pF\,Kjff\b?SL-P]uh%Hu2PPV,ISn7"$hYO?bB_K+P
,]$5BX^U7Nq%p1(e`ua%b#QhH.g'J8N"<>E:2:Y,Q3d(]?<SJTK9Zs'Zp:3dq*'BZXaos'.3#'Fct`Pr
#qb)MPSV>=]0'eY]\E]g`m9bDdD+$0fe!oF,0sq9JW_;/U]lYE+$Fg[3EeZrZp8#5JX]EWXB?hEAn9K*
GN8[WG8seZ\9;Xh?_mo_NmIj\AN@+_hFI!d,5p'oH/:S+:Hb15koj8LMUFk?id6ILYK&r3"V+:B]fBS9
>tGE;ISZ9\HF,q\N?Kkr\qCh)/1htq/YG+g(+;b?^U-6t=ppFsU71.jhIf&#/fRmXnsrVU#TC8iaLIf)
>/3V>6OOarQPOQ,>/5TVNYW1;U]p=R>q.EK9')Gn>:/\]"0So/[:H[3$^@`t8IH6!^!Hi?<,$oQO-O(9
gkX8ET7]"N`HQbu86T0&kn9)[%iUc`\nMAk]X;i!0"20bID"1F!c785>p4n#If0k'OQVPnm:tgtdoVIA
3gkP!4^-!Pm/%OZm4Z1Pb0%8`Y?u1h)Lq5,NeQ$L8O<%5VQVD>jY\NE'[O)G](4Rh7aMf!"B8J54\)^F
V4KQRi:?=3JK_:T7.GT2JrdP,*<tEplr=dXAQoC760"O_T=XkQVGk.4LWmcAVg6SKDc4g`I7G0SJGCbe
flI@D<hH$&Lbo5$C>GW-dX880c<XYcI:"h/YK[[M;gfYrLQCp$r"];u0t0ekCV.8dDQ_VO0/OA@htp2k
ELXF\nX%^h4HMst-I1];BX5GtDhS\A]8(r`YK7+rT6BsLD4`?$S8kYlQQfA:A.=R6A^"%f.u+UCcP3j3
QZCs21+F*RMa&^3G#Z]s&'n`K7#"s,R""+Kkr6%%_=LGVrQ)NmBf=>OG.0Z"=kVs=e>o_sR8%q;oqC0S
/eckU[RhVLn&X*&b!kWFA7jtH5;KRg*^?r?pETRaoHP;!=/C>Zj*kOVp#>A<euHBrYp]HY-JlphW86hL
r3AJ0fX2c-2\8.D,9PuqAB.CWc?F0:TD,o=fk5qdAF!)R4BCWDbLL`>^30<+)j.L#^1g,&Z?$<Ma:J94
bh]Q\kXTf$KAtTR-j)4&].#-\4$u,Nfbk\oBXp/\HFA"]^ci8V\f]TS6U*=^?b7E53P*0J])&Tfic`FO
9q=0pDmpnC1mLU\@#h/phdLZ9?IhsE5"f(JgppU:LC&7SRd4g%;>0qnA7pf@r]$jgNc/ibP?+9e1#\3,
c-I5u3-Jf6al5Z/EV3#rWO:o,9Oj6%C]0QVU.t,fo2@D_m11HK>5VO:8Q<HJq9?U9P0E[urO]D`s7nC:
?)-?(s70\>s48?N&Yc%259Z`69)K]S#lF5^fUr1dCAL/uO1f+<7c'\5E/j;Ki(^n"3:T,>lRoP>B,C%(
nl]"LYGb<73:fmoAh.ca,-Fj8\)[3s7H#6!M)WF]S\mC3'-2XLlBCWhO.,uiZd?@&2iihh*JSkDlFbrc
'%$87Ut8:E@YN%\r#5pP)iui$&7`^;RTdeCRu'WD:Yt3ip*O<p2k%,Te3.I6[WaUk7n.M)6Sfb4Q*\Lc
U?XY?$]g/RaNBfc8Gum8*MgICYYUppS-R4La#`s/mG=jIpQMN%Fah92DnO]9=tMkV&FQArn,r.&g9\X`
q6MT[.#'=CYQ`:Us+teSf`0$i:b$EqT-g1!L'71!.Vfk'X.pF(onV^2^g3DFGj:2>__W*&p9:?F@!1sA
iQSps`bA+=J2pq$XfDu%(tgC93G<5p2fg$6Ekq7I'"\2UAg59lH,GW3Qobkn>"P(?R0tM/Jf#h2Qs>Qs
#URg0:joI=(Y+0Uee*E-qPUI_=Z*D&iJ#+h'p[C+m?fs+,:l?ZpGf:=lQ2W/XEu,*Ts*$@/ku8DAQ=-!
(Z*b5#dKA;1'>-BY*JP^Hej1'/+'DG@l<5\b\1RXg]lB(kB(ToP\#T-L8t`E#0a>8PG9dA(Jj_22LD,@
P;s(4lSKhnaQAN?(?TP=-5<OO`f6"IW\QYrnJ'::)eO\jms+gP=(jm/8[6:,AWA2o$[NoU%Z\;dA"!;"
+jlo*j`FRn1;4:A)R\R&!T`MK!aYa/%C]d2;p6?s6U0b#[u4\a(e#"bJo&re0le*ITY^QGSS]?47j?,B
;/M^%2Gq*5i*Q#lNEMN@47U;t1+lL7,gWJA$%hO)Q-W4O,2NFlmWY(s'`ke*4hR+d-s`d$HKTV[1NmuF
HKN+d,eqTXWDaj[qH!$-ab`0G],fOLa5?BHV^a1f::9&#op,9247:W/hK\!-X)rr2\<"uQ^%.Mm9feH3
7k5Qlk_8S>bY(;WCBUTVN"SLadVkqohSPi59B<\H)'sTMl$ID>AKLLGH_OQtaN'iuTo7@7h`%_8`eCre
]!(P%]E$IR]K+CZ\W""u47ZH>C,#\`/<6)(!#d+<H`p19?@]D;.%NG_hI0-@VLo.qQ!MOt,Yg"?;o3CA
Nms_(p\dGs_m+C\SLtf,e>L5t27)KM=+.9B8XB`Aefthhmd8rGX;"Las2Z1b37c=^#+7V\l/Tn+bb'i.
`+W#mI5I;)->tI!B*F8b:j)R3p>/.]ne-USF]<iDp%p/%H"r%K9OC4b0P?\LD'8idCcVc^mN*;$;<*Z\
E3-]H:%OC/mSirQn(=K55hT+-FU#m0NZta#P15_Q"*H_2Wq5r!c]QVP?8FPjq']G0""c[*orPobf3qPf
54SI>I$'KcqpoWC6jgaTH=?4e`ZWT6fPHf[JVe.qPQWkH`'C>C`[>o%ni/;3.[B%>Db%G`gt$2)m>\_)
Rg"iB*mZCNfU@(=\Pc>%aFHF@9'kl)^m":*Ga`s7FuqLBQ7s3H*H?kl>$f8+*h2akl,e;J8%+FLl(chG
)+."G>Y.PSAf@4g5T^Tdgd"_JTAS'D>;HIgRE&VqJ3nR+Y&hohbUXUi\;Ao&a)2a;AW8ZcF8*K$RJ^4:
\l1-%F([KB+eXES.dr2Nr8S[(-`&XMQcqkn;k;oR4#5ZTf7#7U`@lLg>Pp+/D$7>4*fCY.Xj\T9,_B!U
]_mCD"D5gr87h+1r"tr`Wce1%5M*uR*/':N#opD16Cuq=32DH3rZ/Vb[g^jKDe)/,>qUOiQiEBJT#GhX
hp4mKQs;Sj`IhO-V*9sSB>NikGlnsBaAh;_=h1DakB,qC?^W0`e0K#RA(qX2A6)`\YOeQ>Ps":o'%a+t
cBCF1[8,m]jT(Wj>b:Umg*AEEqP)f,$jlZUOXHWH?6f5*R'hQK\/FuKqq.oIJg4BOdYGBc_3;5UkN(iS
A^<pu>-5tlre5X@qn1gu\9dgJoiXEj$ArcR`0Q&$Ip&/*616Csog<(t#!MF;1=XWcc$]I%UJ\']WaCVn
nQo'a?PNapURSu)n>P^_j4\mBNHmgPKi.XB?s)+)%6K%!0#-^)3,n_Jj*C[]44a0*)js3/\U1]fd\B;I
\`3lg@IF+n]%_5tq%o&41!ef3gq'LU<:4ULE=B25i3N.WrkrHfm/K/]7T`Mti/H$7#d,NV4,k*@o*`ST
ls`]gNT5<ahE*na10^!LbZpm&LW8!2lYF_*dqMT9Ang]>/Ma+Q@AoZJYAjg$'KNI/2q,EK=[tX]Df$nd
Kf=.IX>@SiI<"F1<T_$*`0j`#/X\*4;cWjoD.-)M[hl5)a0/\2/+&cP&9=bT^I6eFQ.*m<G-3IFTiW<T
WPigR:;P6"gnJ^g6s&;=M[rUILGF(mM=[iq;iN<1qegcPfu\Z(=\$1$:<0/ONu`_%pJJ%%3F*%n;DbWh
oEq&]LM,e0rgh0;BBt],TssN%Q9G5Qi>/o;Qg9?HZcRXJ6*Wq'#1B9]6l]DOV\OpF&Egj_AW?/8rKL@1
kX"[WI$tTFp8U;\EbMD3dcTddK68m3:H]P]fS"5r??]J84\Cjl8TmejaeA'gW@3XG:fs.&b]E*b**<aN
hSFGjpaW>6H&n<rdJM\Sm^gHZXDJ6)4&X+KiDR_R%ZGic)NpG>b<#8_^85su!b5fGggHclTs3m)rK:Jd
J+(rP5[S)S8+4h5obo&#3[5pL51l^)`qtcLWQ.oFMHSo@WXQqPV^Z%0o3,.gd:IfF`OAQDaHL<)5`EXC
X\/CF<B#_sTWrBrmF8_m4Rps\<=%mt:/T(O>4K3cU4kPsBuOM%r)+.QhV0G)oMrE^UIRYu?fHg':B&Fk
!tp+PT``'p<+:oYJiA+mDMhrm8II0da."B+'pi=Ak^(jF'pL]\W(_i%muTdZcl%CRnTm0_FA!k8gT*Gj
qJ(dag3Y(OknJ@;3AWZnXn8=_0=``qpPqs0J7$j@<'>9_r>!msoLg8Thg=S&Y<%s$,H'Ho?Vl,]C1l`"
`P&WjB]2j3bT8,/RTfo""cq]tB^e"/Tt%<dMSF7Nisn4sEM45MF/no3";e,R_;bom_e:Wd&,Kk6`esLg
,Nu0]1fj7iOnVpZ4K`%Ka7\;^K>&fbWCK_"S%]Dj4-)%2U4%bu3QGcN2;43W\#]=k:e-2N13&#p)1aJ%
9"u(Ll+OokqPlMT4VAPm`LY&W06SDo[%5.qMc(hO%ZX%hAap2T=.N0,#dLR8rmH(^ml`eI_q+,qhBSrV
9j^eGEkWH1[3P:rE;U_91kD6Ja8Z!d1G]a.L&O!N9@goH/d2W(7:es,fm3I4["Xf$S8S3&):*01TCrW[
fgngRgg_(*G!E*_>fh'(?!NG&YFVuoe]R/<igc,Ag>7V#Kc.jqpo<8cM7*gU^fsu]_r(DZP?`l::G?Yk
'5i:CPLX,X@CEZ-qR^mrW.dc4;JB#q0M5aac2qHKOJuu?HtArp?14/g.Q9M5*J\dYJ45l24B'k[I]R!:
o&8_:WTfr)\)`BL$dPJ-X!$b&\^\!ifpQ@%H]q[iFsb(RDM(As^]"LtFn#pRZo?p4.lF`f9,lE[Y`$Rt
J*@QjNn1W6c$+:Nch4be5SL9P0&TGl4o&/I(Tq&MX`j<D](_2=dnmRD9qZ0.`\j?`p$=MOe7FFV!e2TC
l8nQ<XH8E21:_?E+ho@KK"2"7/\4M+9u0Jul!S9B`@4NG8Pgf=rRY0kZHaet..M\_H*4_1Ns=npqO41\
fp00NpQ]YBcAu!/>f1_4VbQG_mK-WC0VeI3glEk=h(<!Jrj(sS@Z+Q6RJ6J^>Nkn_s7'HG0ECCus*i/R
:pSoU6?PWMi69AR8qk%[pt+grZ^h'RM2kr9@&Co(RG'X8;#B$-b[u6'p2Bu#_>M&#Pk;H6@J-QEZ:m>A
T#koM=Xh<S_4+RXrY>H9M]"Wu>'?BCc5*mKq$Z<&a$&^0gA`%4_QBgS2rs)U5[9DcL(_b2MSA,Bk[XJ@
q%&Y4rckljeKFS30A9HKdmAFN[/[Jre8[>:o0t[A.m:/t$+nNkRV0E7HeX!R^hTg>p4!8eP\04)[W%X0
#QKj+PauMmIDhQh`T=j:Wd\UrA]JNbB@%gRAb"N5Vie19DV=Fs#_+ooVm*I2=sW_nI&(SYmrI.QHJNV1
=h'K^AfduL%KA[RrT'Tg:N]UM+9On+NVd>,i;^R]RmmEqFO=f__J#I4_drT>TYha/AGSL)W60B[3^ij0
K+=iIfK:DICK2(L)seF-L)/-],&bt5X@8Oa$Z<]!0>+tUB(u'rdm<GOD(qSHrR:fH2'$L4=nup6?%bT"
N/>fHN>d'M!umeU-\hXU8g;I=R#,t&?5PV#XuE#%_*Roni>;)TFG:qdSi$VRNVtZaNLjf^:KUn1L&j*U
c/4&uXDb9W&FDG_eQWh^55`>TWKO1=X'8T<oaPj>B@%8oK^th5n3p!"_J/h_aA_H4MDp?Wb!G`oV8(Pp
l-D>kiiAfMJ)gPC/O'JS<o/<%VuQd@q_Xg_EW(ok]8Tas<0k;j`6?B5Z=uO`WP!PgX3YsmBD9`F_c0T4
WA^51Nr,uN2ql,eJ)O6;oD/A9hGB-e0H"esEG"rn^/2nIV?"k*jmD+h]bP_tJ!bHJ[H#Sh!]sc6n;o0\
?TRTS$sI-To-M3bq!\rAXPc,;VgIPt)lMqVU)UuKMs]2RR30`/rulKgH^]ede4kG1ZR=<mQECW9\l/P7
?"l2!XjVgU`V1=uDB[F--[YrEO%ln!TVm&h&'eX\$Xoe4m`<ZM$-!E5'S612@7C"U(Z*GFl@0-]EbWTO
PAps!'0,5g+?[.#2h"n7,KV=Ms4CVoUrjHr'-Jn$U?ED]RG4fDL7P5gq`f^.*j*/6l?<N[7(W:Hr\JXD
dpqh5i0b26qF0P<<[jQ6rBEW(`u)fc1f&IX!L5;SCJtUj477O'LO/q?a7FeOCNjB\_)/K(C_nJS;l8q;
1Qm^k`1j7L(GH;$=ceSX7"u9!A@V25.q=obp1hYUV^7ih$b`-[1"P!T'q7b,l1L;u9o0`SAd?5UUJFp>
-/mRR+Y2o(&1Rkf"pZ!F)lW;?YDsM>rp3Cq<k.5@LK8qA':Nk_gWbJRGS\g[%9&&:fFtJ(bfQ9fXXHo-
EB_rP@h!RUBj&5P/h;RLH:"RJKhs3C0Q&lhb4\Jo29ABi@q\WbHW_Fnp)F_S,`73Mld-UC="=mm"#V4/
eW+9tZ&ZRXTbs=D.o_iH")<8:Ul2IW!T<2PCrLu=UpHbsg#-O?90;?uZ\[fg-IC*L7/HWuXVjV`L,)9U
i[^`r@irduM*7s3T[%:O(UeA2:/U*12?_n*b`<;6Ss[Pergk(MAh#D3Z8#\>E$`N\eAg2uH(l=jY_Zd-
m!iTZU#EA/diuaIp_'W"..c6f_MRa+>`mrgkWW5*H\gN3]@3u!i(CLt@E;(),T*.?V"]eh]JUobb)eW"
1/>4cU!e^l!q68F#?u$s5NI`n2pI<2)AkO^cEmh1B-(=T335#;\WR;4TT^mFp1\2Z?!5!@8#i[(G]93r
Y"@7Hp>4"OXZB_2#!Wj0mWj$dC!-u6k/BN`'etF#4^4Vi,EC<ipYqbO<qgC$$R-(co(,.",ohOIhf!Tl
3Bii:!&=hEF1bTi+l;^K#PVl^S&LLTAo1/j8\--Y>cXBi"A^4l_.[e`M4m$bfocSOY$*pOL&A31P%5?[
>N7bPdGBh=`N8]ai8<M"KsP^',XZs21H=-c?>Cm3Y.qhSXfoTK0$^o*]`h\l^MaMkDID7F_]3]N=eW*Z
p^jO9Xr5O7<E>nG01p0<:bXrTm-"hI?LhfaL]h1J9DA1"4HnK?OjbhQ`N!SkCM)@QYcTY@^pU'Zf-B>u
edPSBf@NYP@')?3h'TQCA/Y5!<]sRFa93GtNReo2&nSp&2c#I(X/+h?nV`.IWJH1UnhFZA6/6b?\<c`-
nE-:p=ceL!*J7P";Y^0.gfZ9T0EeF"BFf,h_>TssFP#-K2"5+XFHi%S/!FgKZg[j[oE>AX'r^9t%r)8_
eq.uIL([@\L-sd[C!!+qKoEb*I[4/\e/@ok36#;Sc^sp++.iLl-Vobpo)[O^bQm&raIX=J3E^0;S5iCJ
N9JoJG4AX]cdn>5ms/bRaI]Mu6Y`am]qiT`eV/(<];^OSCqkt\19cX5^f77Y+,#%h.MgSs4quuImhe2*
_O\3:B@=.<'ABrk:kK!_.]tEP0>[E51>8O>NNnlKeZ+mNAMKTJ"K:P;d6fE?(Po8*c54jSZY07kUTH^3
J)Xu61BF.Qr_+HsoA)0EPL"3+ea<UBAVI&sEH5SQg!n2]Xo#R)l_7,8Q9VRX\`I07kCAlu6+$P1e+0^.
[f"ARH!C8jh3NQ@hLq1Z;gBJ.^SRjih_KC.;5+uDV<-SA/Pk!_Xko@.F$@+qIh6H.a0bc4=OSjO-AspN
5?$7H*,55Wr%5P)Xe"c)rRJL7[VsEKN?69Xas(2!UqKO4#uH)7R[[?dA%2B(nAR!Z3b`BVnn/T(A,02/
bI%2\LU=#FNa9I8rVMIQ/$HkAb`TC0MWdr&h"#B;P(o:;,IdR]mOY0JBK[-*62a)0cU:WJq6rb[7E`I6
Vr_:[e+0]J$(;Yq?*``tBXNiCMIA[g\,:#D0teUgQN+cm`C%0[)[eJe/:T!dE6I.igp-c/=^/QbOH&[C
bq96:ociqHnu#H+bMOR7%Enbp?_HL"aGH/b/`/JR7c<`fPpmrLJ&cm7ou<#&O7o3+2`bi&,I9*eYh#?/
e/+S/m?u5O.qRA[]'5JA#2C/i]huZj0o`_@I89kBkem.iBH<<q\A(+n-/mJbI8ZYEP<Jf;f&tG*?aQP,
RkaE.=$N9GKGWJjRpFpF2,TS6e7GsfoEis:;W-+*2D,lp)u372`V0GRKliTOp"6USF"8Qk'D2]*fK-%l
43%n>pgp/sk#W1,I4&<>*"IG>R#QOd@.1hI,`hY=gSqB==6`j;]%r_-(YjX'%5Xt/%Oif%qqrRcBTLt_
:>i'B^B8Kqi_*Nt<c#AYN\7Z\)(7-+=@8]>D&9PP]Cmbh([-jKWRg?\D_b^pB()uQ_s`u*0coa&_$"Mr
qtp';J#j6_66EY-SS**=ZJ)nf7f0c@OTWRP0AK*L`[](Gi/o)JM>Q8IGVLbQV0;Y:\;Ec_01=7c#KNUg
0GK;Tg@,#N7'j/=mQm5*('Qi,ZVZh6K.r>h!11sSh&JQ`;g&R4#`d\mi0YR=R5s$o+ACI@mQm5ZrDRsL
;GpA)c0FkM.qi\ce_2TsfKBMHF+<EqKW0)e&&K<n-g3j8jOT&qpE2T^7*946l5g)K5'R!\GO-B(.A!CA
@<T+P5kj\o5nAS'f-4Rh[5KNu+d=[eEjtB*B,9YRZlZtO"-U[%#@S[#JDmhtg:!>hj;nHEoH6hRcK.?"
?BCq/B;X1R#@SDAQQCh5gpX!`_<Kr#KS/MTE(geZ*1UG`&>jW!mQn(P"kpl&GFGe#1V#2,/l6:"h=bP7
mO.JiZ#"B*&>m+eoH;BE<]F,Ki`P#h5^Y["'?<B+6't2Ej;rtW]T"se(148B!@H$"Qcuf4/qe7lhRaf'
9CCb)R)=-"=sYtNCbuj(dY%&IA&rMf"&GNp.K^VrP.h?&/#6oh!4@3Dh!qT8hG(>5f60;fIGDs=>TCRL
MYLj:pBYpch^6G`hO2.f]OmHg=L;EA<1#`rYcg1$T@!Q?=7+?Tj+(P.-AT:hAn^A#78oh,n('1efn2$q
$QX=5/aC(7-4[A1&oTRWCI!e8fa"%4)4EZP0tY4uPr7RRa0XVJbmN.CnZ>nj2SOllPWlh3>p"*79/;^k
ZeK3[N1eSHM=KQ]9!lSH@=6%>9]IY;MUe//NLT9U']U]]6"rH#]tEC#ebK&(55p79HlcUP)Em+!g[)=q
p%0mg,R&/e_J@*1PAb-:MgngC]akLEhY:XeC0]rWA\PB"R$XLY1T30Aqk_>Z_[>1_dM5"C$(S>4i^Trn
GC1gSkMNOl5K'&D/?p3W)PbA[me\+q2D08:780e:e6+5+/,8FkQ1@A+Q&nj><iC^--ZMf"Z^#le=Kj;5
[UN/IW3Xus?t?s/?XbZ,n"#]XqC4%6CX'65TFKH/UFS>Cf9I>TCY:<>M<ePIaYgG(OP9Rd?T*Op)bl=M
k?s[LkTe]'9'G:B8s"piZSU9IkoPg,`E=iaTQ=S$@/5\M7-/a`gtFFAX3kSP.$,hu(b)`LQUiP>n&8]^
.5P\_RC;i2J]4D!ZI`PBC=PlFbFjLE)cX[UK7`=7PJ3hPap>B+;S[0[-$e8NX`r`c,'s5/a7HJnqWX5f
`.>;u?5\#@*dGYaGhI?uOc:q/SptKF]Q0j0!BXaX4*JQcc6081n_tES[!(Up]6KMEA[6KtLA_P*qB3ns
T_Z7agX;*AY]dtT1j6g\:[V82kLpMP037Fq%Fs)C>Q<mJ27(]\H!6,uE4o5/dB$V$Jh&56II)b!Q1gml
E?OY)"(Oi[?cJ,YhHR0(0kMYR`7C]i#IaWSUJE>)]B]I(Npssq:fN`(p(Tg19IbldYTnVf%sTo])F-CD
Q&#63CZ^sm7r5-N,/B3U'TX9>Tb9FrN8WD%g\>ADXjq"&XQ7<87h!PYNYjlP).DheBA/L%&O60=#Tc'C
0oG%c4gFMBO%oZuQnQ*[E=gl-J^LYD-con^nm7d^>EK])(ZZ#ZA3@n7cTnM*i],sFRf>,rqXaMh*fkgL
iEt]oVY>lknGX&K]oA(c`s?>F\MIP'k???>NGud"6&a9S%:jpJ8m@KQ`UaN1AGR1'c"#W&d1`&_$=cHi
^FWc!IrFT>rCj$4JC\>dGXIC%G1L#pY8C&4h"Gf:4L'M0djU7sq!^:Va-.\,0eYZL01Be<De_bFZTPE1
#j58U0XKhq-(aasgWmRl$8-_cRj/o!mj)9!=e89QcLWQXpCkr8gj?C`K=iAnYKeE9je"HQ9HJjg>TDB+
.QSq;(q1TZFQ1CR:GGJ=*>$k;m;I6ih)\6"3*1t9Ab/fc5p(*`\OIIjc0I3B\'PPOm9V,8*-c3`7-_1*
=pO:LkXclSh_NK&QeCp5@37I!e#]X')Df;9Y'tCL[:0g+CVH68jBlDsl-;92s/34[aH@N3K'9pD;+H]N
#jE2>RKMWJ(RamOgj_-Ep>$]WA8V29dK57*"GYkpNdB(eC?)cTs.DkT$\(>pA'S2(YA\XlD7R\@1oa;T
jhk9,b7?Z5.;YGhaD4T70Zm\-A#3qX7d+I)JUDMLOh#\f):1W/Q;:[DlFJJe"4%<]SEC8"X;lW,NmA3h
VpGa:eW.rcBADk0Ksb_ue?2AZq*\B;<iKBJZ,l1&2rE]>.*8$b*SZ6olLogQl`Jj(-O8M#Ks,0h:YDeD
GQ,DOh"UDYpq,_Q$tWiNp'[+k9!h.0(EfZE0</uaSl5pCV=>#;hTONsEe%6%ed0L_$Y-S>mYih#0^1TO
)JN;&L`!=X'(%4..V?4,;Z1i`^9iY(CHo)Ycd0AdJNrSgpL$MSPSLH*Hf44QX<%0PCGm3c<K"70i8%\?
S\#`\g.#]AKnG5;2LE^&`49*R9t1_?p:qH2]M&<d`S,;_UTIKXUQW%*M,G1+-V0a2CCc+N1M[qt[24S*
?eU@&K*@.?NSPtP_`Kblk<;nOG*KBLCCe(AMRCJYpYP6`C%d+E7u'g`g7pB`Aa5^W:8,*\=F4Gr9%G'Y
]-_;4RC/#_m=DZ$=aT?Dl=+Ts+e#f/Vl$\AmY)Bk]3WY7$cC]<E6O^A#$?4+dYbHcV_W$$C#-Ea"tIcs
4Z_j];g$K@=aQCQK!bpR@b)S*>RP>._jkjp?S+9TGRsGZMm[WM,\n(/A[+.%+O/WF#<Id0LKrgTCCeOq
9%=XbB'h=@A^&lgEf&>$fi,?rZrfC""ID#X^Ln>]>WC&SBZFf`"27,ho)-maia_-SQ_4;/4`[hjn+`S>
-[rRPk;JR/=/uJ[qZZRKa*dk,qt[I7pIB/tP0GZG&A@2B="!kIEPg3QZ$GbrAM$g_%;I7PAtlqfXDl)"
\"d">84r]pREPjOX0S83Y3%UjL#"K889"1N9ANp[dC-UB`/"9SBa_[N`g!3MVH$M?)@KPj'CVA3r2)Au
.c2iR2ZInq4$P$3$hfq7ph93>3K/4[(tHASoOa,X[$9-D^=\k2nHZ[:SC!^</lX]!gj#,DO280#=%J:>
X&O!#M_%^\X'=AlHQB2L13GNc:XD"kq/4p8c[UpfI!:jsaQ5;)g@1tT9"q>HpfFp;-#sAWgQr[WJ@8Db
?eW!A[YK1=n^faBS2)c[djtC(;G<VG]Um/"b3<?HC]!`!Rl-m0iLThIYi:("m<&=ip2%`i?$,6LAGf>t
Zoeo`I)O9/Wak%E1V9Pl@E];kCZm(1#N0fdh!6$X43GnJU:E1=e3g!rd-W(=F">aN&^%L\@*)0Ya^&,r
2jRG:1Q6gCA^^P3-)(Sr0BhGuITRZP]Jpo"?_3J+Ql<tG"`(gEh1j_+Tlp1.gsE(+DP3MHXqp%l%@KO_
6SS88qFtkEFM>G8<Hlkl!uhG.WPjpBGsNNP+7SGtrE):"Rqcnh&F8Kr1#UBpPs[b8NrAl#7n&;ajOh,N
9Iq@\aPe0&o7+s8.dSUodsYp6&_7P5TP^mH_o;#<kC!klG>c>qeZjUG4DGYK9?k!*K9n`#;<'!Sd,HQl
[3@Ybodn[VNie*F=VH4^r1gLI6f`@IKbQSE2leX6,E.gOTi8N(4;Z`=/Q?uX><LXHSW/6g>M9RHrUl\u
4-l*4DiFJ5!T)c3\9jDA^G0eRqfR+a,<`.m=@Zg4e^Yj\)UWm0Oc":W1M/`?7dlC\*+eE:nm]g_"^.ND
ICP5[m?D]LCu>3!4)KOU7)q@:2c=3>NtJ?MZ&X?89/ioUL#I`>;uO`QDAPKC=@ZQj\mZUng5q>EMNp%o
OAUn^PFrHZoR<l@X<Z\iGmK7TVL:k;NeEgUShB0:(_p.?>2Fg3G]H](E^?lh[H!h0:>0qR%:9RI.LmG6
<A(d&(h4nd"VYHQ>GVh2,;%MGMXh?j@$HVf-/Ph/HaOfps6Reim,Xj':LPMd3WKhlXgcW9J%F&hI;1Q6
/VRP;=E</,/^#$"Ae1KLY,#!oe7gG*7^8rajZIFGq:'*"&ga9b4F8j``Ri6kK8F4%)tsX4>ie[L`.m/\
",3@qQ5B2`bI,IAXD,D:m83Uj/*4OW74HVWqpOIa'6I^G'\8D;5cTl?@DpnmF"<hDAQJhp(Z$;LZfp=(
(Pijqk=V<+,j,Y_3-HIbnAm>c(rU1<+Z/[44o!@cor[`J0q;&"f0/V8k2pchRsX#U'_nCdZHlchR>I!b
-)Cn,$_1:p7U7et^.5-)Ic`]``64Aag8iK'Wu:T#)2qcL`)GO<k/DE5GJ@hMjRlc]F1/9qSen>l.T#re
LLpgqbbZ`98moq/R@Up;>Og&+S1b_(LN.*PU$D1+QCb:mfbE,lq)4i_aF?oJaqC+2..?.4C?&9.g,-It
%a@33fh1&.jVDAoV&8:9)?,"`2X^&&R.7saRe+)Re1Z*I8RH3?;]^@2nbu2lau]slr<MdrUa?HQe"<]F
5Tbm*ZAk-L/`J=*XRGVJHg"o%Tr,o$JQfA?<iBq>#%O]b\<C_*M,t'qDX[AC;`Q3h%ai"War+5FDMecX
Noua;4lbuX[sW\S\68-HHu\RNH_c[L(9blEHo[t@XTF-uAbAKpR_/L2cK.F*^7T6eUD2:GHii%NjdBGM
WB=gKM6/mQK3qHH;s16`4;U>0)P]EmY"Gti`%Q*Zch-["jZ0qqBP>RLQoV61kNUYcWMenkq5SJPZ]sXk
`h1Cpi\-Y@e&][Q@?fqJ44/(-]P$>?m(VO#hQ`@/7!5<rS0D[Rcob0Y<h<6J5:b(P=\W`NnVm0so&Sqb
8o7lVad;a``P7WbpI^f]B?k3>:EqAcj4tceicUJ5?DgGA:'D6:nr/[>7k)d+fsakNfe0*`(Ii[&*/ldK
T:C;:j*iE&W<bB#5kmR>[[^Hc96h-TW`$HP1QufjK4CuF-RUlPH%nQZ/EJn^b:2OeCum[ba>MF:bFGCk
B6RO*$0-I&dAtBk=C=6.,O7BllL]IE&TkPWL[tB2Ros4.+[%\_ge0l?j[XBWB_MnX#s)+YQT*f3iq^(Z
T/M+KHQuNJ$6.2k27PZS=>_3NGbqi]%IWXA"C-$69$>%FOCA%>i;E(S847Cnpf5rkmr")kGJ"OXe$rhi
^"I==QT]u-/^a_rKJp@/09B6gq#+OL:*3T^cBV",N9EWRqJPqoo(:Ja]qm?b>+LAfGG=N33QA5J26D:5
Z0R`9lZWre-0ocO9ulq=i688EnXAadT6r<Dmi%EG;sOg.=E*#BeuuUm)a:SA%ZpW&[-F!fl@\9g40D`>
\\2M!dUCm('f>GH*i4rNfX#2S99eaFqAN+oJ+@<e1i-7MQYK0cC2OGZ+BJITD0W=$_6M3L3JQNM^rq!"
6PT=1*9?<@<m`"=r:p,;r."_nrm<t,YLaj'O!/$'VgNUdJFg%58,r2H^Y^@:ldu.;VtX=O~>
endstream
endobj
7 0 obj
68517
endobj
3 0 obj
<<
/Parent null
/Type /Pages
/MediaBox [0.0000 0.0000 803.00 551.00]
/Resources 8 0 R
/Kids [5 0 R]
/Count 1
>>
endobj
9 0 obj
[/PDF /Text /ImageC]
endobj
10 0 obj
<<
/S /Transparency
/CS /DeviceRGB
/I true
/K false
>>
endobj
11 0 obj
<<
/Alpha1
<<
/ca 1.0000
/CA 1.0000
/BM /Normal
/AIS false
>>
>>
endobj
8 0 obj
<<
/ProcSet 9 0 R
/ExtGState 11 0 R
>>
endobj
xref
0 12
0000000000 65535 f
0000000015 00000 n
0000000315 00000 n
0000069260 00000 n
0000000445 00000 n
0000000521 00000 n
0000000609 00000 n
0000069236 00000 n
0000069714 00000 n
0000069430 00000 n
0000069469 00000 n
0000069571 00000 n
trailer
<<
/Size 12
/Root 2 0 R
/Info 1 0 R
>>
startxref
69787
%%EOF

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.23.2-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="372.44000000000005" width="681.2" x="808.8000000000001" y="142.0"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="296.09375" x="28.614190924657578" xml:space="preserve" y="13.404178370786525">satrs-example Component Structure<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.4579944349315068" nodeRatioY="-0.46400983146067415" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="311.8450000000001" width="155.39999999999998" x="823.9300000000002" y="187.59499999999997"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="41.25" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="155.39999999999998" x="12.272399999999493" xml:space="preserve" y="9.639200000000272">Application
Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4210270270270303" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46908977216245173" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="207" bottomF="206.71046874999996" left="0" leftF="0.0" right="5" rightF="5.399999999999977" top="45" topF="45.13453125000012"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n1:">
<node id="n1::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="838.9300000000002" y="247.7295312500001"/>
<y:Fill color="#FFCC99" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="69.2216796875" x="25.38916015625" xml:space="preserve" y="4.8515625">ACS Task<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n2" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="355.9183000000014" width="539.4029243565859" x="971.9147999999997" y="158.5217000000002"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="content" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="4.0" x="267.7014621782929" y="0.0"/>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="1.4779288903810084E-12" left="0" leftF="1.1368683772161603E-13" right="0" rightF="0.0" top="14" topF="14.073299999999762"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 4</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n2:">
<node id="n2::n0" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="311.84500000000014" width="490.5852000000002" x="986.9147999999998" y="187.59499999999997"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="490.5852000000002" x="18.817724356585472" xml:space="preserve" y="8.551700000000238">Generic Components<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.46164229096885634" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.4725770815629552" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="57" bottomF="56.71046875000002" left="7" leftF="7.372800000000552" right="193" rightF="193.19119999999975" top="28" topF="27.68500000000006"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n2::n0:">
<node id="n2::n0::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="125.0" x="1144.3088000000002" y="284.00476562500006"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.255859375" x="24.3720703125" xml:space="preserve" y="4.8515625">TM Funnel<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="125.0" x="1009.3300000000002" y="397.7295312500001"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.830078125" x="20.5849609375" xml:space="preserve" y="4.8515625">UDP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="120.0" x="1144.3300000000002" y="335.7295312500001"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="81.1298828125" x="19.43505859375" xml:space="preserve" y="4.8515625">TCP Server<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="125.0" x="1009.3300000000002" y="337.7295312500001"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.1689453125" x="24.91552734375" xml:space="preserve" y="4.8515625">TC Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="125.0" x="1009.2876000000003" y="230.28000000000003"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.9228515625" x="7.53857421875" xml:space="preserve" y="4.8515625">Event Manager<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="125.0" x="1009.2876000000003" y="284.00476562500006"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="97.2216796875" x="13.88916015625" xml:space="preserve" y="4.8515625">PUS Receiver<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n0::n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="40.0" width="125.0" x="1144.3088000000002" y="230.28000000000003"/>
<y:Fill color="#CCFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.59375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.1650390625" x="20.41748046875" xml:space="preserve" y="1.703125">Shared
TMTC Pools<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n2::n1" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="284.0" width="180.0" x="1279.3300000000002" y="208.7295312500001"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="left" autoSizePolicy="node_width" borderDistance="5.0" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="22.625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="180.0" x="12.572399999999789" xml:space="preserve" y="9.817168750000121">PUS Stack<y:LabelModel><y:SmartNodeLabelModel distance="5.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.4301533333333345" labelRatioY="-0.5" nodeRatioX="0.5" nodeRatioY="-0.46543250440140804" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="24" topF="24.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n2::n1:">
<node id="n2::n1::n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="247.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.255859375" x="19.3720703125" xml:space="preserve" y="6.015625">PUS 1 Verification<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1::n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="287.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.39453125" x="10.802734375" xml:space="preserve" y="6.015625">PUS 3 Housekeeping<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1::n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="327.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.529296875" x="33.2353515625" xml:space="preserve" y="6.015625">PUS 5 Events<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1::n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="367.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.9453125" x="31.52734375" xml:space="preserve" y="6.015625">PUS 8 Actions<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1::n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="447.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.205078125" x="36.8974609375" xml:space="preserve" y="6.015625">PUS 17 Test<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1::n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="150.0" x="1294.3300000000002" y="407.7295312500001"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="116.8515625" x="16.57421875" xml:space="preserve" y="6.015625">PUS 11 Scheduling<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
</graph>
</node>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>

View File

@ -0,0 +1,835 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD>
1 0 obj
<<
/Title ()
/Author ()
/Subject ()
/Keywords ()
/Creator (yExport 1.5)
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
/CreationDate (D:20240208115813+01'00')
/ModDate (D:20240208115813+01'00')
/Trapped /False
>>
endobj
2 0 obj
<<
/Type /Catalog
/Pages 3 0 R
/ViewerPreferences 4 0 R
/OpenAction [5 0 R /Fit]
>>
endobj
4 0 obj
<<
/FitWindow true
/CenterWindow false
>>
endobj
5 0 obj
<<
/Parent 3 0 R
/Type /Page
/Contents 6 0 R
>>
endobj
6 0 obj
<<
/Length 7 0 R
/Filter [/ASCII85Decode /FlateDecode]
>>
stream
GauF[afZr%OsN$$?ZCN;:U+hm2(CkA#-&YV>R3Uj"qa0Zrt!2qelkQ>N_hnSoNp?Fgi_i8SMXM?UQLkW
e+;HuHN!Fgq#t!&ci8Ckq"<6gqs44=YJ4\WB8(gBo-Zd&HGAJ&?[qt=s8;3_IbsZ<ro;?)qR?Kf^]*VM
I(YXBr:b^<oABT>J,1\ql0j-"j4!&gj+$$s5&'UKp.sCidLNY[7oB&2WpB]A_uDL$^\V;XRH0!YUA=Iu
%Z:8Umghk_1;<)ME\HfAn5&"L'8_D*5B/gc\%hpGs()lA%t"(7M#VbsVm$-fq+9l_qVV<VBmm2JkuL@&
[aTHYT>Eb#n(&Q(k<ErKs0ocBorhgHn8:?1G^liTn".;bW8DYY?OPZ(F0W2;3'V=#m<e$"eb]#BNb3K7
R\S9FIQYq7s71ehq_D-_UUD7D^Xj+(BBsa_VmUo5MDlhOANF#6T?Z9*Gb:/42h009W6Q>FT>n3tT5N]B
2'"Qe2]"sdc^+%2*)H38D+AqtIu!]8IcnkYjmKW<Q(P>El*q2#K`?4E:[?L)Y4eFZSX9A"(i^"/Zu5d+
[PqQ,<Bb2C-DRQ6kFXm.GM/!SG4^1DL\IK+n!t!"]$n8C8#3Uk*sc@^=6h[5%7D\>X`i-s!Z)e[qHeEq
6Td_IHQP0B9:X(Op2k::Mnce\8%[bB(epHns2=+T45Pm3POJG\"T#$9=s"MICEALZf2kM0N'8MZRt_tF
^ApFhhMohoGKcgZoK`Z%H%1=Ai:#h"\A6u*ZT9$$3ut^UH3;daFNKp5AS`X'4T)KMi=4XYP;M[BBmHc&
d=>1.FaH9C,u/+oON<ad/#Bgn59,Z.?'+`N+`kn*!Pci0KX$iD[6^gmNQ1'$cTY\:%:(J`@mJW_BQ[Il
ouiCd)m*GoX(ffIBUiCFR)H$iI0P#=[<aU50WGj0_1O8:fSd4NbeTeTmI*pIXPmgK:!W*li6)Tecj$Ir
n-2tkZ_edrf;sJpiku'`LU4CW!c[#NN]-[GiP!M8a/.O^IFR#)jHkLp-%qg!5<nB(dj[f0:8>h4^l[$;
nV5r*b6>+C@s4DSIDr^dmaW/@;N=Ul8::XNN]&WgN-=HOLO-:tec^:6A"_?n_+N'WOGa;@k,`91E`RLZ
LH[Ncfa0h,]#;S73Fb+=l&lpRhZ<+-]n[>'^jc+OILMp.dZ/h6pe`LWm]Ns6%O#6iZ^,9_>UMXUl`b/7
qj02lQV#h^neSNDZTT#)Y4`idH[W!p*fR#4HEQV0V.DN`G`\!Z&OY2@ib(TAl^A8e>@%7HAN$?u6_Z!0
NI@hB&&A[V&Q3PR?8%kr?)47'5tb4A\(,1'S"uiMO,\nTghqlEc,Zt#7Y2E$7bh>U];BmDADQX&^s0Ul
o0!&0[rLlQ&"f7ZI8Jh;FUsq&'%]s72l4MtZYWSB,55R?fam#iYE2"4+#l9/BiuOB@"n"(I7#AnF+oR)
gH!%p-$sF%Af6.R5!5=g2?#dFTDaL55<]%'Sc1i<k/2>&I5lj$?#.dEc\H7idL7QA>J;0V6;u"eHaL3r
Hp>'rdsWJqT[>f/Z'sok6dDa'UGcP9A3PBZBNBA%j.NA8!2#\CDJ)0'i@t91'0"D$L8QG+dBc;8gDk'6
^nibODG5ZkBL#1Pd:Q]?B$0Sm\hJ%[J]`45Fh*9AP?#%/)'R+jY=`!3jps7/>r.9!0.bHL_$RG[T)Teo
-TqD6Q$U^Em'l0XTLplu)8g9/ci:hJk<E7#aRao.bO^4\BBSc%*oqABci5,4rke1&+1.^4)rBm!8"TaV
[g(1Y6'pd7o^Co;5GQ?ie_p6fA'\gI8USnHq*-\lcSKD6K4k4Ik<C>q"$HU9i.DTio:PXJr221_O6?Oj
Q1iims/1WhLAd[Mo!6QIhO_KAr-84I]KQ9tDU73FKCB0UQ$%BO_XdUP]M!SM'=NOb^O<"cT"jr-K]fN[
_P=F9hk'ji;]Djp81Dmb5s*X6Ue;'@s$:LUk>)e`s1o`Ik^G?Nrs'k&NthL$5BMIk0@/PP$NuB&UB-,4
5Nriso-[i-q&;'Cji'?l".PjMiPi<+&]sW/32-$9r+uMYfCAcur^khfAM3>\om_]ra&pO8g8,$[`sh_o
o),=!i;#TBRaLE+I-VTXnoL;TmZQX3IT<I!*</-G;<R6?:X&!o2t!]\FFU(TdMiG`a,M6`AlI<7[b:()
5%oLk+?+(eK0;E-eohF6>klOt:9c-bqEC;4;/a_KbHna/L.8k(L0Zp)?p+>-aY$bR4?ftA5>TDNO1ta/
le9\"iosoqg##3n`-46f]=O$W)0/["1jKU1BS!Q72s$"+*tS@E"P3^pKb)GJ^&D/?ErNR0L>8qJ[lQj>
Hm_%(eb,TGSRYAA5O\!D"5W54I$NQc;8T8]m,4TC+WfJ(p/?/NG<a;b<2"em2&L%@G&;^4LCE4!2ubju
mT>J2j;_1XfaLgGr[#Y@A(_;K=Jq>8X3Kj1F,JH+R`m@d=0r`f`jaE2?-k"EKZto"Q[2?J^bVCnGu0*`
/!q?s%WWb;Sc5+;lr7E.>kW#&)nhagQTr0s?E;/<*e(e(Q-]%.qJ<\NmGTl`mI$KMGX;'tUQON<GQ=Q7
q>GWu%52>/=1.MS^AbbE(t")'AHZG>-&_]ZSL-2(7hBi))Se>\E+:H%G\_k:Il'D57M(73B)+*D+p`p8
4V7ho.eSEY47+36N;"tI#r90BC#+k6O!&6He!:Y50\dgYO4H6H@AP$'*/NH@?q(R]FE5N8Y"0Er&<=<a
)h,\`+;eub&HqQ<<Wp7Z;,.810]N-J1h]l-`oo5%d6C+F<DMk:CU==hkuNif(fhp/.,C],HOlnX6=>is
fIo$sYKqBIPgT4$r5.06lh$;`7UWp-Ds7jeTJRl!(\S,$@#lH**5YWkR'd&Kkk;gU1a)[*3u(\\?Eima
LaR_rjufaZCDL8-3=V2/KCMpD=L+!R?FWWsa&j3F#g<R+i=5b^.'8T!$5uk'0S85I!j9fFZm0JPZ[P=X
IB9-mN=tkNL=%;_,mYsA3I5Ku,j0IViQ]E?[s,sXEAq$F34fmCi1!C*K-n7+LNRd>MJ]q;@[PQfB10A=
/*aB4G92eWP`EE_[Z23@6fTr,^X/op*mD%:g>9DKLRr2ejP5K&f+kp(2n/]H2UAV_Gh@6M'o_!]W:Z`O
B:lSTCYi5c)2r_A!\MtcXg&q:gSma07p6V[^U_#-ID]L[&88p"&O,7FE6E1pN/s@d95]qM^*J'HnNd\*
I!HqhKb<D-;9g$9K1e8-0gdk5#N^)f0+UXZ[gr2q"1iEl;oeZhO65edmC[C?2?!Lf1VR7A5qCIE4Y)UK
Q\DD@rE(%<VAEQd`:Is36]KeQrdF8G:Wb*;nSA;5-2$3</.X)\ZWT'5a4FPP>QS\\m:s)(R03k@lGT,&
NW-^Z!C$N&G4u'J!C'&u&A8@E80smc+qH"0N$>YY[UWfrd#h^_[U8Ql>mGW0^!L&qL7s[>l]Nps\,qCG
38V$Icnr.aB00qW_ZBT[etRJofeD$mr9AB:;a$j4A:MKtE\qO5ZD@J+clm3ZWC.+g$IX78jpNt)&_m!k
r&T)T%!WjKg6<R3#2PH@GU2N>e@lngO,+<YLR*fBTLAW0$S5Pq[qH6;?S1g`]gkNbS.m>W2PTuC#LrD&
ko31W6\4.!6+:u!1A3[)23(/.(o_rj3h5r)n3GQ+:lmiOl0B%mh,9HVK[n([N".+Xn%tbeq"uMjo?SAd
4/E[3@'5$()q_d7ENFs*oiMgpL@r2[`.k[_IHI9+LsnKAe@,(:@/CidQYc'J2a1j.d(WQ!>Pi9O+u[A!
Qq:[=MW)A*&E\j0T(Wo(^F<4%rge.3`1)*A=:oK>WDJTr<3?t@NHOpC3jcft/?X0^l9G'=H,u4#iba%=
Yp'[r'ldVg&T@%j6_C_,[b'bQ]DkA;[<_XE[Z>U6Li$rgilFTE*%PJc'9QP$.7ch-07uW>7Ju9ik>CUX
ECK4R<lFt`fZ9t3er+gUL^f\Q&K?_?a(>+qp@DaACnk:o9X[]8U9B@^d:^a/PehQ5o@^+J,'Jtf?a1f(
pYTOmE(-75DLW[3D(#ojg#CB8kJ<IDWAA(L21lDBhAsi-E.0Y)CYe@,)"agaXjT!BT,"e8XOP'bK=NDL
=Os(gZApj;[L8A<P8"q*:LU>?oZM*HM>(oY+29jbdn;hZ\NKfo_UoRC>$i2I8aQdEb_/O#W+>3JHS6d\
>7sC?dT<J)@LHQJ]p[JM5]9c3PjodjNL>25[!nZl3igEN?]f7GCHn,*E35UHq-m<t4n`RS*g&TnHCK8$
(;iiOM;CBrD!$`BQPK+JpC?Z*]pC5akVi-J\@#._LMVa*VOc:hLf(JNWRE"P)FO/:Z]F.N&^E)UZFI].
^KlRA@Pj1K?]NS@=du[c\ptE9P)nNE?cUmO1p#]gm]ZhW"Vu9Z4+=`"E=D5j`nRg7hM=7ahP2.cK)H-o
@SYsg-FO#/rEGc5#q\.Np+WrKXgU7Yqs;B@rIpu\;j+NWCr/A4WpsDjK?lTn.Oq,^U>Sc'(WBZ%s70Np
UV6%fi0aU&moB?lda.(QcNTBZ_\7T1"N4Njb$+L`DqHLfPu`i,U3+V8i^%aZg$/+8(.j\h110o9p2%g5
Il5%uB9.kC^Vh*dQ9Xc_`eqM9kH.)toLK\/l0B$j$dtdUJ45P[6dKSO"hTKp^tS9R'`iRKid0SJeP92q
@TsiN[^<Y[(Y2e`XMl5Ef\t#*Dg,42fC\ch\6j*]6:_[gipc>Y6-d=PNYSiR<lhmd2op*(ooZV#R<Z_*
He@9^e[#J:.3d6#[+K$i]XG+*br`D52=8$)ikZ(IfsKmNZjIBF+g$i_@lu9m4>.]7'?9f`(LkG[*lL_P
N,K0m<SR:L=#OatP+R<q[>:8SCWqm$Y:>RLnJ-!\+oi,ln<@bf,S]8@53'c+5Pr7$^=D[:Yp'/YhTRZ%
hD7Wt9X@p+_3=@>CeTf0fs=DT`hBF`2h>HWf[4*m:em(tfi28`+oo%b"M.b>^imf_^Q#G+]X\15'l,:[
!!mAZ^Ca9;`'FDfru4uA@)GcJ"Q2SiNA-6+X?V:\HYZEu(*4nf,-<Ra.">ScSe$:/o[WdL?Coc$Si=gC
qm-g4^Z_paJnNI%6.C%.4T_9`_Z&f5M?)k@L]FADd(\eKFL+G(h7R5<@hA^h+X-o^&gIB<4>#UOf6l6[
HETY>s#SfagHc]aM`YZ\Yu5ccP2q%O*"_T/*<FgiapBeu<Plbg?LKbq8A)&c`=n%-#g'X`A%f!O_PcF@
A#7JSEZNII_5=3HZ>^^V[/*>`9@6&o%4WN`7hkS:^$,o^k<qAABD+Hm^V_jKPiQmJkaB0FR5rR`bT8T$
40'BDGS(eO;#Sg*1VHk]RD'$S9Ke!rV>eUnHZ8u0=6j@0jI&00LuTX?%j(;c*%cGJJYKotm[;$IiU6<N
o$$`+;$fAu"`O7O-r/+4T9$\HplbLo%slanq=TC=F:8!]8)]j>3[4X]nSFALV"Yr0_H5]'WCN-NHq?D4
kLL*#aF@on0e^6]ht)6G[Y.D:L^o1?F#<Zg7Z-p]3fnh^-1iT6GgR<Aa-e&p3kka>1-Sg8IpmU(J`2+u
U8KeuG[:RO=Rp[+:iR2@^k8G:g_4pY]"duR[8?\H?[7',HNB.r4<6ppSk^DS!lAS4)7/g`A`r(IT9u4#
A,'H>ibPoJm.Fra*3WlHb004<&%boc_2]@"2skKe5Q4C2-efL7@fBcZB_M\MCOS"u<O1O[aM<`q5/3JL
rAXEujFa8!>e`I9Sg`ZPpNKIKda:8TVl#+5fp]\QeI'!\_+,#ngg,E<9'[AV*n1pYo_ta%Kac0RA3=.j
M>=%>qG%WH4FPq@698jOR\0S_>Lq!3>9?Je,O5lbqCV$p-0ljf]cG$>A71HblBh'U9#s'WnpIhbIgFO?
cd[&Y-c\-Kf&3J5WmDuh(C^u*$P)\TjI:Y?;2+_fUQC$i$5m3f5nl]Ug.PJM+q!Q_FF&lg1gAYjla2sP
8XpOf+6rAb)37*Fh9_q6fnlZfO,s;?oF\JAXk5hE@f+N0m6`D4QW@oUXk5hE@XDh.p<nic6S$`S<idsV
=?j0%I;-[sdYd]b/(P65=?j0%rQpGp6S$^uX]Sq6Z+dRhHQ?:p^9jUTRQ=^-5."dGBuTeoMX<b]g9IWP
(#)A3[bnH$F8=?8LVPX]T)XV7`?3EU@\.TO`[M-_n4S*5@\+`'_Ilc<=pplnZu4-EId!"`i5:u[HbJo0
1]*iEPt_L8ef)m:[`S0\;[s<Vh`OUm!pPq:i%t@-Y(#E(+.rSO3@*gW&'1RB4'rjq5^j_YoELK]a0)6i
I.#(`rkM,\bF]^BoeU^1-u?<<Z(W`0cfX\:[3k)'N)c2JR/JP"jc1'BnL\9=c&pc'/oRC94j1L)NE2Xl
>sQmGlpIMm^h=?<.Rol+q7+H'BXj6"YsrVhdfl2>39o!#'=2BC!PREd)rFc&Gn5c03/Tp^BQs'`S%&Pd
lGM0`a<G5+(4NZ_27*%lhfa/'=)3'5-0R+BoKehYm="2Vr4AMSoD?[)$_20j,+eKFI_6!-jUB,m4OP<L
Q4s,%O0:>"a3aC969cQoPqmIOiCq'lNF-g$3OC9`9EF7XB:4<6dE6OS(%:5k/W%5fC3>*&R??#0IsW4u
km\]dn<ZV=DKL=4?&W,K-k+t$V#R/i7I-1d^/2eo;W%U@TJ&pjRQus`qFd'JKP*e!WXJIU?a2C3H:P%-
ek#;g8;AJf8ZdO(2JJ*Q$[M%SfRs8S%%AsE>DLJo7iXR.lp'L6$,EhQ@t2P@\*%$gr!6&cM=tGc2,:kK
kct4RU*%E&&;L^^+#_"0!fp%mnn\3[A(-pE9c-M8#No4B0t.HSa!M@in^"\(,S'\@q+htgiLUS4KXm/'
rrkN\2]_Gc)i1D[80u5nS6P.hmnTN!jNl+Ql]ekg>VoqJc=[AF>YB#iP`IX5c03&iqd$PArCZ"+_oCI"
+qDB2%aH'/b76cX^[(-,\c"/8Ji\A4>drTZg4\?@?VuKUF[f(j4G?]*/9=SP=3O4S>)2]LK3$V%@7F=1
9Q"8j5o_+Ujh(Nn0@SYlhO(9BW5m`^/?L+1Q'NUWY+&Lhe@8La9e/1egD(IoekT@j9="h6P87sDb.4@"
,R`Bp.e5mK,fZIIhs!"i%iG@2*.d:&_oXfdIcB+D:QH2Q#em(S*ET-[i4=\R.\88#,EDH!UFW[qWDftN
_HpdqJ#5BiWcVfe"^eBA#'iK)28@MDX.=CtY7D&5f](CNhL6L3],NtcTiX?']&eeFWlU2g5jH2YHV!H#
fmZG5*u$^hrbT2JUKb>>DOl"MNpIm<[P7^HF1r%6n?sbj'[]&&"^Sj]Z08B9Ph']pA&-`c\8Xic:IJd;
#:lSPoB&rt!?M[6E,dQ[a:2P*G:%U8`*#4BV[L-\^8\o[fRo\klUC8F^lRh*\S1^N/`g(f%buag(#O/e
iirPX^,+g$c$PrpUY5qnH7-_5II2r"R_Grp?CTuL\RB@dB/q^LJj2RPP'b\>.*6`&TUookUA)Hs-?1Rh
Z?buLb=#*n.h[(J_U8cO[6J,'<^$J>j`qEdo.W#=o.[O)cQdMG$_E-ILs/"1]\AHZRoTRgl;qXL4b%E2
g<@<:_Xkht;'3^Ia=29k10R=q*^nt4#`#=_'e=Uj6bi8!rda*U]]S6%AnY"q@Aee))s/Y[+tPf%$p0`Z
"%559e)K"fVfL4s`p?Z"o#M0c]^/VCl.E*C8b\[<)sWc\b.5kSREit1\Ug:aP+<5Gn`9Q/5i=H)ggBor
Z;]>@*HoS%$ThoJ?$8hMJt4Z=eqZ0_HO%Q(eZRKF?KD?T00<`d2Sr4/#&0uAo1PM,hm%8&lN;be8q)&\
L>s<Z7l@qV6i$:BF%OeIhO=3dkDk_>YBq*-$LGA^n&lZp=Xu=$6<5)U]MV%/D'pTIPk^]Rn_J]8f:9%2
mij<$e8Pk8eBeVBe9ZOiUDafY;&f%^A;`@o]D:;6#ti&R'p[m?)K)762'_T[2.,oD?h9$jXblQZm$.il
D^:\CSc=5F=(2I$Tfmu#/UaHgWkI<Mf^`kQceXoCn@VqkU(J@.NX4dQ[8M*=C5lph"P/'KWbt7Z[9'*#
&N/%,8pgm;(bt'0#WqcQXG=j%)nIW5NHJR0Z3kJt9).54"aN@FX`Mn(1hKF$p.`+HcJC'T[sTr\c]td&
?cCe^,s_FeTlKE]a*)Nkin%[l.a46X(CdoV+qDFqabtYA7CYj_]b4c?Po,*S3>T?PN2Na`EQ=gX`T`rI
#U8HZT++#OUW'f`f&4L!.hQ?s=(6tGkNaWX>'hR!ro6bpJ+!S/-Wt_rra(&+@CD"BB!_E4QKEBM>kid)
D\92bs*F\(a4Yq?`S9L1EM$.O?R:%5eaPD(^9A9Zj=KXHdMI(LJ"EimZbZ82qtmnfbMAAFrQr]jK-u"V
pQq`6EUneBTCMhtfQAt=A*+3S>q+T*8(uE*pOYP,Ar*os<E%_.mB2T.b^D1`43U_A_Q?Upl&A^Sq>-TI
.t1!KIC%qIZXqTilk$5R&@KlrE"<\GD?;$e7J4jjO^>`qnI9^f(VZ@:/?=)]4c<)6"i<uA-(S(q1cN&`
FNC&>M&Xj$9%g>'.%'%lX>=3JGZWX;)SrV0QetQfrUdUZIGo@NZ:,)VS]0:!but:,,A:9eZSUo]XVRpr
?P[\bY4qe+RdjDK"FENN'^(;MS:H'VrFu>G2_ugg?d4&ALjJ\i-E$&\&B8=W;(G,9?\/dN]qCP&$4<e'
2go\$R/cNoN3Hi'pgW8B*&>CoJ&@EeC][1?L>NarR2LJ.k)gNcB'#,Bd3*:fMW+B07qRM=@n,dgm/Y7e
.J8,2N&^o%AB)LY4(LfO1It3-i@cW9HuI(7^Ijo&O!kb0Do`>WUK3><1.+*Ub`GG]),cPOo9q>Vi.OR]
bQolXkd=H10@H'pDiXq!K(G*K-`MUuHt6hgbh^VOb!*QkOM,nBD[OHbaV4"Q;,Ec-!f-?5jBfPZGai%d
kHd[C$MHDhL8.Tr7]KgI%d1NuP.h81W^f!rJ+-LJD)CoISUUAt5u+o5)^C3W;XsQYXQTWG[g+q,>`+"+
%70<G1?.m04TC92r:S(n,Ms>d,d(8B`q,htoPs[9?M9B;2kLCP,OA4@[:[9:g?i8`^c^=q29=t-4Y@f*
EF.AUR`Tbs;bMiDF_&f6fL*)nNkW9[mX3-$O8/.ZcHi)&]bYb@o"2_k`-9/QpR(TrSC(]V2u&#426#>;
lB1=^*W6/YfQ'0X(oa2IFElYpS$tqblj&P>_-Cqh%Hs8hDiGC\%-_KPjRtMiE5]\=^k$d8:Mru+Q3]jW
L+[fKhqP!,T$`h(eW6t,a#u7p$)>=R>2,pjl"%8;q.W[#U#15%'_E/IIKL+1C$YbLpQ>V>k>C)8FaBCL
lW;%YAI300la/S&Ru[5$fnH/iQ"X)GDIK9;jnC^ir2j1_#5kIZ5-j!L["RhD,56o&ceX5DC.V[gM9L_Q
97FL)Tp6,Y"2&lEfP],AIbrOe>'X`r3+V6uX9oEHdCP?F@/UHT!k0)5E`rimL`]X)4.IcX+Pp>icdi?5
")^'M1>M#;:r82d^nATk%m&[(Z&Vu>7ZCc+6XT);T=Eg&-0l#R`e)sCC'f6srB?3N/U)aS_3X*)Nk!\9
+l0g0cmuTCgDfdZbkV-'p0QORM7^-bf8$hIcH80rA+r#<mRCIi<d&8eBg6RSf\4bGf")Mq&^,[7_n3kS
?D=geT/VK`-LA3>q&IfiG=RlHO1!`Sh<Oc(<)0ir<H/E#dCD6M's1^giE"%eBc;@0`^;u,[FU)n%VW5'
YtfU=_L=Z):]G978'W86an'(KU*-#:6#@u@CS'DCEkK;\iQk.ETNFId<d;=6mp=`iS9'`*;UX**V]e(P
YFp:70m(9e(K>XSX"Z-o:>jFTYjZQ/4O8N8m!D#OD"i_EehM4$:4?C&:rg77#qKat#TK3tb>;4Yc?QQ8
^o(P&^j&&Ihht*CrSW!4XY8OJAuJ">/9tQFVrD%A>O@&jI!E*5kV6A8rJ'G0kkS"ZGi/725&ehNe%4=_
.DH[7h(o+[7Z-@nnaWI80D)+#NV&23dk\u8T/_3I]8<L?ak&3$g(YUT#-fRR]G8Vd)L(`H4[/;oFPMe^
i8`%6FCq%&];q]6Af#W?Dq3PG2b2sU+3/_mjMe(4no8iMojB<2MgT3\'c)]$X#4j"[TE6`54lF4T[R9-
j_+PfbN--]lFi%`=6Zc[&pbf1hXpbd;qs7I0E:\PNT"5rf3n@-jfUIV"i^F0482`9mP)NSdI&k(M@g9!
+8K^rNE<bop/tb1J`JKW..cjN;Oj&rk+e6kRLfkE09]bVGM\heE0.eK`a_DtLn_Z$96]gk&gQLb%it.>
;$fAu"`O7sDQt@7chHCO6dA:+LOEQW^;$qDTXV]nGen8O-k5&T[QjK'T1tCaj0;Klr[.'jRR4M_/#Ujb
'cF=3e-c,-1jKS8,g>\p9kD\\n6c?8git@ij2jqXp^;iuddKE*?XU[]]GNZFbu#@nT'1<0N%f<H#s,-F
(9oY37Lf4B*)jJRToBnJh<nagBWU0UmQdPn0R-qCW&)lRH[^Hh]\.$1)!-\5(@g]ucN?ZGf\T^-@VJ3<
`j?C9W7k_/:^Yg1\9#\EX,>Pc90rLm58m;h_kY]#`G.!=U+$8R5<L+A^);&sb^V&N<D]n,/3XGmj>.,>
Oi[=Z7Z@nj[jKYs^fN%%1n[@hpW1nBWa"aK)RX%MG3UCZ*GX)C!L9\R0oLXKQ#8kER^gWVf$1\K[pr$Q
?*"=E?"eqL3bDHn<Ye_eUcMh\;IjR6l3/bAUPK3J2L\S?G5GVSl37"G\FfkdGMOT.ejiph503N/\0Dh]
oG7/YR=8l0>iWM@N_OY!m&S)YW_ngL;O&-c0@16[X,5D9lHRZ[omMiVIE)O&Up,0,?Op'XE,Y_F`7iPV
)`MV5(P1uTp<"/ma&@LlGI1<_`O(q:8r2aOk5F$5i]_&',8]2X1#XdFVMJ?D:K2V?N7V694p,u/[Ikq.
Z5\DK?Q_b&;`h*%1Wa##r;ciR`5lu>G7*?T::KW_(Q/2)q`,GjdRLh.]qof$q:3nj)[8G<"`O7O..5sB
0E60u_Ytk.hsLsBId)"Qg+,3@s,omirLt4rQ1&u0`<_8YT#E?u_R"K*ojn@WP?e6;cNZP,FGSq>SS(,a
3NY`5e58fU/F7X8AVjcJ:Tb`N7=U/LSonc+8FbP(LE[XL<3`\LM1R[7'k)!IY>H0G[WSI\NA:"gaL%;1
d/Yo,]$\Y;q=YX.^GHV6JmYh<fNg\nl:2*G9t(%Md(hRX9ra:`6uqWp*6_s(d,\K``76L]_4CJ8rJlg!
jBi`Y`p*5J))@a>F3l,9c\3!l=qgL(`Y-5mJJR+GT\JHZYn(9Sg#gIl0rFeD:as;0Fj@,9<:(FY(_;W0
r.\^/_AF@e5A5Ff(o:Nu)]a!QnVn%#jTq<%$YOk>_bt9o:r1H'bkLl>\88+oiY]Y<SIQ@o"$Yb=<.*W`
p\+WGkC:Bu.N>m,'GL?o',nm_f^N*_>r&G"Pl+ffMe`$H+l^l-2*)6I%X3l,eX`YsquONGBBNjd4'kWo
6;%.id9GIVYj0;[2pi8=N]?a5Y7Sr;0)WXR!C$N&m`:L\.N4[GYK,TO7220N;c.(CdD..c2nV&-H)4/c
&]a9?.q29G]U">kC$U8ANWJ4Wr"$fG$`]*j8_48egj5#g<khX>n#a5e:1#jfbK,,BmE>K`DWYWbb)o^m
9M#J9%q=FhP+A&d?_Us$U5SL9nek\]Wu":qlemWskR7$hhiVDs>[*Em($RXkoNBla8.qm68CMeOo;qK*
8W0J?:;8qp?PQA07m`r[n4$t+r3CXq>OS]b[eSD<iFqli2GO,Z$cgYn'dMfn8V_ODWPkutK':U?^7[GG
;kD[r[pQD%)aVedEEsd7m(r6o:ufDJ<TohW"YUM+1KtO$+.h/;pDBLT>4UboWZ.$u-P67UWO`@?,>?T`
aN(45=an2Eq?8Z;ZX)Y&e9-lL.h'a?LZWG8hA`%(^Ilh24V@p:qS"@1cYOi$LXNqE50#9a.CK3&B)!kq
Yn5VNTbmh#f!B4JO2UbsJ*iSP`?%VIDe[8YoR9skk`9('m$Ad-`>X>8>obs=4VA)3+i\3j99,8*%6F!Z
U_8#"rHZld#HLh2,u<]*?s,sWntu+BQ7!4\0sm0RcZ!N*b4p-]>mF`hdM)+`F8.X7R1@R*LIYqAnH?;X
XY!=V0A/D=M!^\>noV;F(LlN`j9ZBQjklS^%ifaf^]27C"SS&pT'0h+Qn$J+md9LSc0jtG/o:HfSsf+`
*;-ZS8<I91BP9Y@j3Fc>c3gL[[q9AEENe_EIM'W%nBWHPp&Zo6]kqS>^[ZKm*2q31Thuco1M!X@3b*_E
r=+heeLga+Z&opXam`''(#uD6YupkSRF/!=+JHNg?t_okjloXg[O"G"l8JF?]E+U"qrPHh-H7V3*QTo%
,Ks;VhKD/i.pYcRTe_Y0gK7P8N=@_e]%:\18(Ki;?e*2Vbq#RQ]$THMT>C\N0bhr9Vua0SnTaE$>glf.
c`"U2B<^'83/Z+/[?cVW+neN'`Ej'h=Lo^:M_[K2hYa.i3*/bdZar`IlG3-&\-d9i=>/l6N1bEsI^GjM
?,i<aU"PBsc<B#`cCdTq`J]]LN%W7<-fh)W1u9HVM()C%rUJ9bhd%ZGlOmo#im<:RBEeCPTbW9T'ha:9
L/oj.lQKr\OsRe2JWd/NOB5P?eInkfe/YTVF<Bg5YHT^@<jZTF.n_B2l<@FHckPA9W\p@fOh;Kr(6%.#
@VS?L]1OSWc`##;HbGJ@Zn[ghX_;CEi6m4YNo4>F7YoXT]:%0+`=JR_X]VUSeYX'=UX4n%q8H[X<",t(
NX;s[=:n4+%<llBI\,^enOE0uiL3U#A'/Gl]Cj<E":]Q,>Om3+Ve0Uq0&1$=%@WFX!'^?BSB9prYA7aP
+-g";nbA)%m*V)l=Konq)=l0la+t#]_m>S;Nk^JkhcTO"o:J!8K'rGRRC$aC\Gl05P_D73rmRnp9^%*i
r6G>bokF,7!?3eKrN/+g't\31Ufd.hSUitX;"nD+e(Ppq?:Ds#([Qr>U:LB="'/N;oS(ISK=d0LG<FWR
TkpV>FV0QsPPUBNZ>->%!<)"Z2NYCJ-MR-_o'VKf^'dmF<dS-meLP$$[l1)u*Z?C#R3!_0CiSCmb6gUA
Z.Y]c;,+(JZ*ZjBW6(_j>5,3/g0hR;?='H#-m-Y.oJb=DY+c7kZo1?j9Q/I][ZsD$1sUH+U8E)MHHfPP
'ig(2>"PF=&t%PQ^\l?5HOcPTN2@E=+7a/*e#S7Ab.Rb@?Z11tdlQDEG[@<lm;/_9`J0/46+&fiKt%[E
EWf.)a+Tkd]A)][-cG_X'aEYgSh];k1[o2b8m(8n?`HidhW_Y2[;+M];UEB`,,"FUS_Fu31B!(`=UcZ"
edQ!bV5bjLUGX'o't/L`-H_'KK+CrGIoOqo^g8V#^@?Pp3BpID-!CPLWhL!E5THTj[WuS@Wk'=[6nZZl
IeiO.:;7KBEF1>@G0d\%=,;>52bSMfGT*1R`:<c_rqb7@fD`[;r:s_"p0[CB^\s5.bXajGHW]/))?.s3
s0k5mrBL<05NcL%]Qm?*.S&:hBAc@_k<H5C2bL^eJ+YCe&+>">^A[Tjs-17rlb>AO'@Gt1DUU8WnK0b/
Ic(?+:@k+g^7Rn2FE;orDDm"?'5!S];&e&7Jb3m!5u$+jfX-:-QK.f+IeU[?KCcaBlFl]cI:Z8or+0GO
;&cs5I=-a[@'r(:9Q<G/TnXtJq%?`f>hR"4Xd41=Hu$[W]Fd@crs8tYeUoGWf(c-1EOLjF1AZ]IPhjiA
b\14uTVQq-U?k*R<"[Y6&_6#jLM,rsIV3$,]m&bh(s8%+#SCA^NES&7gu0A:$nnT;=A2&^kAGp(-kd3b
D-660p+:L:)rG3:Yg<&/7*_hJ'\oQYH<8-+4grnqR)feT^5E&GZ't]qf'IcsVgC]GZeO,I5n(,.4:-;^
[be7;\/,dkKoolI?b07bHYldskD%bu&S&6jF^OsD^O:tl?GAR*rJGjsAO<!\!,(i8?3A%$>D(d\+c#[O
miSVhCf[igkk!EB)kN['P6EU?jKN7cU=C/OSH)$>d9OfRLfTs&J^X'.egZ:I.=8?tak%id;\qTtH:h*i
-Prn57"4i^FpXsKXY/u140NC.o8sk'h2\5HZ0MdYW$m*UE6;^$jZ"G^M:(6.4ChGF86TI\0DM1A=X7GF
'ThF`<_/oclc#7ogUcYT2.^P9G@X8AL6P=HW'A(q/MY\B+WUq&>k<]ke(\g'59.EM&QEOtAt7f'/\S`\
1MjVP\:qnrS-s?qOBZ#",a*H*ORD-+@S]e?*AQ>AS\g18-#9b]"o?k%5C8N8knNet)@'9"UD5-k7@/U5
Fu))o>rnuR=5V1hD:1];B&ZtQN+Ykj7Rd#01,VPOZgc=27G[#+X]LeJh!o4/rW"gnR`qVbi/+]L?2Q[I
kI(I=jf<TEm6Kf\V+t_0\b7ELLJ*8j$s:6rE&kd:1,Qi2[i@!d6hgh6dO@;L&/p>r<\&F<1_)u=n6XBL
khWp_EG1;b?&;e#5i[)tiir8ce+L$CJ[]U;[m.*u\4=Zmmo[oVpHES!IBNY+0[K0*V@?8XfX!2]"qZ'1
RG5n0\AOB<-tYQp*PjW!VTd?p5%WlSm9bmVYB7oViNom_WHkk;P0V:t?A/sXc%3P0/M-6XUiKBT,?#<q
`RZOWqlKtnA:uH0E%q4X(S)aKRPI\WSa;]`(2rudrF!o^5tlM*FWeC+FZVA(n;Mo(,u,[MT6^&aZlAUK
k@m?naEiCcqe/KPD5;%c'F*;O%,p&83f*dWKa[/I(Q@5A0,%e?cjo+F8A*LDYX8a$2M(DY,]lnp0IQhW
;;[a\?Al<'_uhd?`f1Cdi:B_pLReGfS\)V;b=ojQmlISlJRueP*,FM[aLuh:"e?E4>>oBXf3!`KEh$,/
*&5X!Y]m?[%t2o9,<=UW;4eIP\F3sFO%KmH>iE"QKJ:u&>&Bl6hKm)BLYJr`]-u+&Pr\I5;"Wm[`807+
Z!X9;R'6g_OY9BXgY%XHr#je4BdePpbh\nq:[X/\We]_^JhJr*)4;mfV=2WUF]T(P9upu!$oR+L2gR%:
TL+JpfOg2Tm^R2ecic9O^2Mm0D>_<Pnr\llMk^GmQkTjNWN=Up),[TR9"j;ZMOa=8hbq5I1t(;GaTbhQ
MD7sE>lkO9!7&qA)>#!>*2qm5]`!DPVMTFEF@#W-WhV$6J-<(k2_3fMEi0Q,]1&(Ac[-TnWY+DDn4h?8
an+bQgA;U/m\<a21G+1.d*c`Pm6l&A>csjT_kAjoA)=d=OXmh7d!LSX(;<*A=Rq@sAqTE<2K-P.&gN,V
MYbKLG9AS<e2S,+Lq:kH<\K)X#MN&f]tjg*cOj]h?Jof,>q.Bj6iWq,F+[N`j'thmH;lO3V`-#8%<&Q)
n\C_]7LnbR,GLE^"as@`a%iJ,rpN)hCV<nakcP'!L]66M(n=GC#r78VV3/,`F7bBj]dJ&4F0OB1HW:`W
/qo'1H=B7_hl#c(H9ZBA]QLPNMsY`\`(q&ZE4g\5E3uBiT32'hpLAMU:BU>te>dgD\/V:&p11p*l?=5W
/)+Kfhm%#R#Nc=hY?PBn-2G)VY?PBn-2FNFgu2oo*,m+Bl\UjESCT,q&&E.p,%Br%H!:/I0L&3A[iCk-
_l7b/?5_W_B50@YRTd\p`-'E0*sM)61$$)CQ'GJL]ucTuXM'57rkg7mG`Pa7n%HJmq=-Y;EjaVl:kf;h
I`]2\3<pE\q:`Q@,JRC8ms23&O:K5%U3CYEpIOjL'mCbk4%6VnZPP$\8U;c@9O@(?5U(\9EU;QIGI718
aqsBofN3;.XXThH2tXpSdj[?+6&4<\r9!a<o9UD]U[VT+OE8]egH]Db)9hY8r<57SNn3c\4(U[pb4j++
3oD9ohlCR%C4)i^"X+th%K`6kXlj%MhRKoEn'h+RDqoU&2.T<[XdA\)F.TI3@Q2,KcWE=5Vc!DifiemV
B.J6KXcT@fb#S,'1Mn(W(noQYl"k3)B]FO0`!)P0nT;Sfe9k2On*lp'W+uL7bcJZZNf;[]at:H<pNs<F
Dr*<K5p^n7Yu8a($t\"eags>p-nRh)!`.<A/)X,tf4IGh5<VKI,aX&$.O(9/Z+j:pA*[33oCnEgC]EPq
$1jm"lD,%PA:Oc"reUtO]ITt\Je>4[X_h+tX=pX)i(H-[NYC5t1OcHB/KH'lo51;u\6@AU^4)LFlu"an
;#Jc*`ZCDKi96"gm(<;!2hUY4KDd<>Fq;Ei@6!pDEMC\b4@$[483ak/\lJHn<hj)03c<Vf5ln/^&eokl
\"J8\@_OoLh.tL7.6^c?EK7toI9*@f]sSAS_=s&V(f+GE<\Kt*&cQE*ru6-ZlfVWV)IjW7<>+uh@';@G
YM<W&W#=REG_/T%U<LToXWHhN7BbK)[ncAKXFS_j=1:Md;-'=%k8-895I=o"d#)[ipCDXc`V.`;rBi+/
QWEH+Xk5kF@Xi+2?E*[iO8=W4ln>Kn?Ws^aBZ9tE(&LWSp6ShH+'PqaZsoAgj*InYBZ9u!MZ'@dgP8gG
Mf_9cVY-L?<WkHeOdK=W]sM8Gd#.du(&LWSp(+:X?Ws_LZss'^MZ'@dqb&I5hm?kmd#.du(&LWSp-qX1
61ISup<$FfHQ5=b`Lb37BROU6rh1(CVp^IiV%qa?<p[S8`M$U\g^J,1_ZI0%$t_ej5'Y7[<+1V&*`s)r
;>t%D&0b.?\Q_GEZ1\IeQMIVhggHZiBXsE=>3\KTcuOQJ[5d['CAgNCoa`W5&kAId.S=BbnY,'a9?Rch
e)WQJk2/9m>,eo%,ITu$L,afqo(2d:).<t!c;tBW?1SjiS?bWJ1HOB=*r&P;c[TCeJ%[RnlhSO'/dbjf
HQUR`icM\JG#G65cL))i`80BqBpiI<43`6/7'G5$4NUbD9q&pYCKA0k-"kf<[kTQ)_eZXKloe4.rpaV7
=-[+,C*n6/?IOF<Blu_SfjFcUXmKl92ohp+?d0I3"[/#c5_"jVQs,KD[;#TdK+9n7[R$b6H<[&8XVf'E
kc5Fci0YZO>u8FjD^q\i_!b7Q17GKo?C,-PHd+W*n![bB=)6esI>'Qm=$2s0L,NmSp6mt!eedn_..sT_
[AXa8lcO^F#%SM<eP0J4+aIB7!BlSccMFshoAtmlZPq`U,^K!UGA:6FP@GQ=eBQmT*4p0l>$,.,E#$-r
cbK`<<r@D6do>sL-d!6RKE[pc2=fJq)VMRl^90?aR]k;Y?F]B/>,O/'iRLN:`i_cMjE6uK@Z='o^r?5J
Djp"*juK]]K7)EVSY_oX0`$.IaD8Oo2qPaA#ElUVlE%sea[%QTTO:Nn_uokuKU^Wr(Yms]:DZtWj-pD@
6gd?VT]NW,BQ/>dT.Y%qX`k1eNE[Hi4L2eL0,WGome5?oXs\UuKf!mYK':Rn:p3l"fg-$XQ$@6<hYXXM
Yo<?[d3D)*d,,K=_J#[o8#dC;q(?,AjN7n39oenBqDWnfj4Eg?=Abq8h7JI/((k[BB<+]"Z3C\]&)Zd8
4R*"-%3(:6)GO?n"WTjEM6GoV4S;8!(&^;,f+'lJXl@Mq"FZ_@&L!"c2C`:B8)k`b=*UGH!IgF@C1cJ=
BkZ)fe^R_nB.4>C^Y\U2;;AS20DtZk,l//GTV6d:7TA8a*BLo:1IN`dTeh9EYFZIt7X4iYDla13)/>]?
Or[F_A7rYfLGPo:asNscDG[DjG.W&gFE'j`7`/SRUjVY$&BK.((?gnI_+e:l1nQRLHD>XL%_mF@Z1`r9
^Ch+UYg/+FQ*!fj/AKKn.^q3TrbT>^13_HVC:Es#TliBQ4/aT_5<BT7BErO:/0)Br]m9O9,6f>S;"l)V
qX%T(Jpm8#.=ZQR<\6>@'D:kB/g8Xe?`cbtr!J)hL_f""?<#H#30d#h&E7IfM&c9,fD$%gg8t1WcM5h"
Y;j7l'rm>@NhN!UUa9<%nTk*.e#F;Vn_&6Y*hWBQ`ojI]C7o;N:lI'@@oub>W;_gppXejuYuX*_D9PA9
kSZ@'&CHOgpR&2]6$8`nCY%J,McOTKlilNFHAPW:ZIW9Y:5up`Qb<8n4d`*+q6,F+H+H4;o47l]%gDJ#
Dci91F3toBF^:+tUIp4cRM71od0$d2:)!aE+"iG8QUI16Q":2hZLpFYRH-b*:oAtjK*D\=%%LI/#JYrj
dsH.J1lYk]S]0k+[4hCr\s]=Mek.s>H^tZ"YGM3fQ`--s[oF-\%F";:iX@<Z18%!k-gU=o)GlJ^q?Ldm
;3@#@ZnbT&Qd@TOC=PpQ<DCp-^!\XA7A$>Fq6fo0hGJ?-KMNsfaP5]UZfbTs(#>ad^W1B7^1=fe8&+Dr
\OA<BY-3G/lHr%-:qY?m?1Nk7jmjo1jm^K7e_Vc<ZWsn1p3KBeSUb?"'U0bqB?"<,1ZNi"1ZMR$?fcWA
8c9+E/9<11Xc7*!r)+,*H=kqmU1[M^H8H*mr>cg$rmEIaRIRuLRIR?Mbh/\`i'lN>`T<NLWg/lCJ%^7?
lbsmNaE<[*Y(>rtfWfWgB*=DK00NO"B86.%ri3GW`?%UbCON="Cd'K-@-=Di?`(`=5?-Mg^0:6dUU&eU
-(aYn\ji.6c4IOc?<"M2D(ka;ib_SO@(1pd=N8qVCVK<."I`@6()Jk3IGe/)Fj;mgHZBKia%"H_Skf1P
FBW?$Dgc>n-7qV)d5(YNQg0mE9>(iC:B:0KVs_$,;"r`8\,>q.Lr0m^?)b2+cO^==oWii2AbXp%m^763
IXZgMq.^@7f&j=Kg1&d62C)cr@`a1&>Iru61YsDcf^1[9]/uEXoRj!2/Cn_FCN6O_ocW`Xq&f-Pme12Z
Q;IT(c@<3"&&%A4>u@a31_e:*B,);(IFMi#^#!55V"_o:J[O7-Vm5!O2jN!=[+h?^@(.LnT0guMrmtqe
j(ciEm>aA@]tIsiD<=<`K4;;[(qVs/V0n^a=f9<hjd!A@e;GO/("C"m&$>kukLntU[LB0$'2r[j`?7FB
8bq*#TX3B_^W(!pEUSOnG%OgAO]U#tMHK>?398jr#JD.K*[:s8f#q3SMq?sT=cr5$b.nKe[_cL!/\+-I
M7]M^W3nQJ[,Ul-MW7&SNXr6O?<V?][,R0#7]eXV>&h3^iqUlXj/!L29.Q#5XZ0WjEP/[<ps=oY6S%j@
Xk5kFiqUlXj$P>WQW>(ZXZ/MOico1Vn^<<T6S"1`<ZEcp3'O_/G\P.edYd'DXZ2U;25(bA@t_2%f0<4*
<&*E3&WIU8@g)Ue>sAm3eF\Zi%RrRj)aI298uF;9I;;=E,=g;&q9I:4o*=G7Z]5mKomTGIYm0<K/?OJ(
puoH/-;*i%J^=,3rZaW!97Ol_&p2G)A9)BSU8(m8EOXKJ:LM3$9foFMn(aaSL#.KB=PCH13LX/uV?0a@
+5)gDlE,pj3mZ'MSM@50VXij267X68^A85U)N]LdT*7_s^)-cTD0cuFNQs^QQJDbk/T&L1PHVL$11,p>
qReCO^<=>jL+%3[o"pi->J@-_XOo)qN[Uq@\$5&p4/-l$GV=GX`[Y#`%JK)$;gZodke-:47Mi1*HOBXC
o[U)A%j!BAQ>L!SYO^EQl-cu&g>F[\'Xk>V#d*.2#)X+Vreq-TTYFNtF1p2#F.`,XNuV'X2/cREcGmN]
_TKbH;7:O0-m8Y2iQ=)bnVa573jf%bHUIO%jm0E."MN9M^GHb!:#M?ul&VndF@cipME-o0>XVNW0A?C4
.@_`]8iCYLMuFk=K6!2<&7Sf7JLCs:Q@<01A09(>-?+'C>tM0iaF2O;HmpkFO@r@ZeP![T^&&N;DV+gn
O#Caeb8L%!&BF_UI/XfZJ%AF,(FJYJKH,1Or:07Uq*/;Y<PPR%/BE9t5knu"h,0j.5Ogtu5)"K6hu:=d
'HZ,8rP,@ROd)<7F,GOXY[.,$4'$gEo4e(B?dIbZT/b+bP0<87<WD(CO\&LmXJ,,kbD<jdp\Fd(Vajd,
k]_t!nrCuDYG2?22>W<W)nV_dI(_1_HnXCUYn?E_DpSc<ch-M-E6u[M(Os=Iis)Guq(VCNQo@FPo:J!g
M#Me]X2hs#mkdNlF^^m<aI]+*#M`F>r*ZjpY'Z\5VH$mQ3'pnlf.W0/Vt0&bpW[lHh5_+XB00ge<q)]a
E#_SBW5J%=)pP.Qi]XTgk!=I1/FXP'o'_$:;,#;cim-9C^<Z9B1=7o!N5[i+e`1>KT:-4J^\b?d;TquR
,-C.\>"PbpJ09]6FKHP_nulL,7?))3Ipg:.c[qcKqXoEU',ga"gp%P')YgJkn$q;)YR<N/ABZjT0Gs)8
3-Q8#6q)7%ET8GpTI/aG(;:*WKrqeDE1b%qi-R7+`@Rdf0-'Wm<R`Q,I)_BLLkA4mr$TdgmG\s<jP9$I
PD7:?=0E;*B8A:=(k9q<31<10<P)GHO4`okEeLm?B00g-IDhROo?1J3$_AcLM9h+o#6fMLk'%e$*nY,<
BP)Y3$cX8E?8C]r7pJGDT2RfSG0bD)YL^WmbkUlqSf-G28Xf2:?e();SJ_Yr1VE^CcOB8,i\TVaeAmMd
cC<.)8c1/Qgb)2]1N$2bcCA'dPC(;Bi>cK.Eh""#Q#3&],&-QbNu@U.^ZQCh?LakL)p0i#opE&B&cDt$
#9Orqf.lKUoP`mukH1lRb!]q6$8dJ7=j7(Q2u1[lK;HAto,MPSqp]#Wbr[ie.q#oHIP2Z:!;f&EXA;(\
MPpEUT6SiSLkNL[qb!S!PI0(71OK-hCNHSD(:QYnpdtdCm=5H0pSjjIrmacej?4]eiauXa805N6CtW`!
KiG:kKD]?/5el:FigFp#a&YuO:!t1*#[^.V)WElO35glk^\NX)Q2$]1_,6%6BGL!U]A3,U/rn+m;Qm%B
4'Efjko'Ru[/9XleN3BHFuV5:I`7rg,3s]-,C5Th/e6KHYPuELqPg9j'=89nImtebKoag#o:PgXogJ-G
T63n$5*&o`M-M\2;n*QN4TC8uq1%Thk9&QiIs7H9F(m5Jk>'PeK(rtdo-O6m)pUeFDJ2Bd6:]d-6b[QJ
pjh>bc^O9:\4"0Z5'<P@3r9Vnc1H>[da(*_n+BtuphJ"O?M92R.Ou8/N2KfXo/E1BFY.RMWM=&`_nP30
=FH7UqO"$"c*?G.qFRuY-Y\/%8^lAVgXS@`=oQjP\@g_bqoJ;-b`I-=<%7^Jq;Wndo:$[OWVI8g8*7Rg
J7!P@ei(j\JFYZ;P5*cOB7rO@c8.fRb;uT[-C;NPW;:'l2DF!G'>Hd2dT7<!@ZN2Eof$a\i>u?r43TVo
2s8>!Nq]60IiGM=EY,oX]Eca.#pmW`BC5S*f'@aX1VuI]d$_6D>3/*\hB9`m75i2WB=`sFnFr_0/Fo'e
-VG,e`eP.1frTO\JT8B4rM"q>DB/%m@L'$,=)W<3q)n23JDF&`fJBNhUc.69),\7F3>_C*B)FE$j@nUI
(.jHt6!9_fRV)cgn!W3_Hp;a+XPS$B?c3rUm@*'(WNSKPbs,[CL;?a+cN^4m`duD@M*7V=^C88%Er=-?
2(HmQONQ,I6t/V8G=XQ#Ndi)iF,od>igU1#cc,\sVeq99=.j&B[aSX&R'Y(Z>kB(q>1*.n/A?E$A4R='
B-ghse]bR11Z&Z!jOl!(C1f&CjI-9$o<,k:H_*-ER<-$MK+!I+"]tQ":#D.m3O4f`Nd-&h&3lAA<a*^[
bteB8=VaAi#]_\G![Z5TG[Bup*ND*:5fC89"8bM0nOl8c$t2&^kE"<;"/g]8IG"hu5:jjB\)m+m8OrU'
7D?6>=;$t9+eHR(]$<3K04NcQ(f0b9!8l9\effsY@]hVjF[;'AgWf_QAKU5?H67DnF9*KGY&c.iMbZWW
da2WaW9J+ji65i#cctHp,tQX5(7#;?,8[!F',f`s>a>%IUam8G17Wi=_M!$iBcl6)G@#$Y.62AhMJ&Go
+\M<7J9@U2U4/3!%CBB[&GqY2/Y9tND@\'hO,\;/U%d(Ea\J2XYF(e2mtb3U8U"HilPH(?R#iSqH4O=E
j&Z29Z0F3"g%=m_ZZ5+f6aOlbiV>Ica39`j>KEEW[&%FVLYj.0.>:msVC<$uYn_]&$,36"nD)qG+(Nj%
V[(N[L6\q)4!Ak_)MrJpTtMk\m!hRuCe)>_5qZf?Vt@75GtRjA39aPX%kUO7K$23aZf#(Q]?ajTr4MJG
[-NZ]Ps<9PGHg+pRskph2Qg(O4''%/9&$3Z%=*j@3<ZD>(]TSl-:`SdY:s"jqGO1#S[-2uVKh5pdm;)K
f*JbK24Ul$7Z63uhp3FN`tr<3H[q_ioH*Ph.n62Z?N#XZ[`a:$f+h>32>5`eL8B++'jd0=Uu/p:qh7sf
huoSiqj>,uoINeH'n,a@=YnB&E1'[JcZEaP5IUtf(%jioLYoAbgCGH./5OTA@`A$LK6LS'IF#s\i!DMd
d^[>^Jl_\d*GH0L8_T`d2d/I4=*umsN$.b3iOMR.7AgNsq26-X)PPJjB2:?nXeus)Ama3)[IV`:l\!'[
V8CjYI\'JNQei0F4hL#u3$5iGX4D5'B\n&Z2sEt"NRbNkbrcRRHIe_Ue?im0eBtFCpY\Z3H?W0=H.TZT
b0F(]H0\bqd(<N]KPS+=o\ikY*flpX%)'9bZnP:IKbifQbLJ(:M\E'YA%PLBL>L`h;1X*kkZqZ_h(;d"
mCYC=QErlj]A(Tj]-"trV5na#Y?^O6q7Pr\6gEL0'j_mc?$_ho%D>D.]t:+D?172IM5K,U`Mn'*V]JI^
#X\"J7_]tGTaTW6`6c,#)UA".H'ioE..lr`J*\2RatH%QF-?5JIWe?Pp(K3\ZBptla6Mcaj`Jb7UNt+V
iI&%FNkQ.V9CLl9VY%$b'm`qPH<3)5^lB/H$VQEAq+iQTCOJdJGL\M5>sn53]/XXW]kaPOf82EF8^Zt5
Q9V(9CCRtX)+cOfrR/<]q>1CeH^X]75;Gb&4*0IN;"=H2roZ[rs5AD'4'=02(%'7E.t)!"."WZ`EL7[M
=!D;L2gdclkh7!5=Y3h1G`<6mbShSIC2#`f[+(_6J21RPi1\]RqrCm_NDVLX]S$6GQEnQiCIDt#=%ZPk
@nU$OO\<"Bq;No];s-HJ3>&d\r$$WIGU_O>,?LJn&G+W>rP5#1^Ti[YBVMmTCF_I,Y+3D+Q2E%;U0qTi
.I##;ZidYS7m&r%rMo\eI*nbr_WhrNo3a5?/Fb^uQgN&i:t;$!4!<QA.D!<b<CHpNkC+UE:iTs3.-OB!
I6=>PPsO*+fIjq-(:"h*,Hd6L+#E-/Xu3XICZ/M"k`AiX[kf4/CX>-pADH&>NI:8=`SQ%IN`^/TZ4k+G
f:D)KAuk7_/Si:2mikuLdE6UW4@kUZK.V8i?SZfJpI=M8?^6#37uZ90^Ca+dNK5j/hHG]L#d)"R,Bh7Z
0m9$pq'1&uGXUoLd#-I-d5Ad5RqQe-r\>lLs+7\Je3NA$<(?4T1SWO(Ak]I6iB)+`r/T6s2qG>@JW(1M
`GUeEVV&$.*ttp6!\55^7ZNX@[sei^6tZlYHc0h#VNIo:;<oAu.^9M?h3Ol3EG''EfI]t=D@`<PXn2\j
bOEIF/@`!2CVJfoKq=-1[iA,\D@8T7`,:]#Vq<SA^/"#1)01VjqMC@g`GUAD]+mg%f!C4J'Vk:?i3*g+
j_QVo>Nd.Ydk&8@T&9-[[3ik@1(^sM)>&:!"*=k)[&e0>p,ZC1T@KMR@^gV5Np:e"CC^qceK3S)\`,eh
q2-4KB8$5MeZmNPX&ZF'4b=1hFt:-HG)MkP`GUAD]0/.*i,?VQ295B9(;>:CLYe;aNJ4^-ZKM`=:juhN
XCE?`+Ef7B.s>]qS2`fSXod<CMVSn$G&+k:&\BnWY1Tl;BJH7n1'bF#r=*l4*Nb%CfgNBs=6rsk!O5jC
N5\K59>;Ua,T"IGR'@?m)+nWBL(S,>bR0p3>Wg1Cre>#udGI3<l))+$DT1/M=Rs'PU,AjlkOC@i?RLTd
V]3>a6U4tN@L.G;_:OMe#Y2lIT4Z)@Ni3`.\hD/mL1858:7!'_2pT)^HG/BFHQA3TWNgI*'bg+rV'OC1
HUJPc9>o%Vi\-_)PdK"CG43*A;,NrG8SK9bZ*i&L1KSLdZ';YB9gM/1mCp>e6^Y-#o+*i2Es0]hb*'G=
c^1$(PdNP``_r*5m28PWSZ"i5+>T82@a$[R1KnBAOV8sL.4GT*q\IT?Hr+?)h>M!Km\0oabhQ37)qh'6
[p.fR'n9dijA`LpU%NZ,--m5\pjnC^I^P4-<R3e("b]@gS[4D6>-[+(<^B82"Q;GK3<'#O4NsrKfG?_s
R9eJVmIX)?\3lcCmP-.(G:;H3^OeA!-g/aZUuXh#j9Am-bM?jNHSGauE=7g,R_P6;LfXOBVJ3e8>a.ge
L_XK%H[A1e8h"ZcQZpsAot@!?'OA3)%=BoupE-Up7CAV]9[S=Th4i^VAW$2V=j'!Tp'5U@:Lk90MkP?q
emBe'F</&!Q-#aod;eL..ZZl`U@K[r.CtPrXic4JOY`AbhCVgK'_=dI]j''iPtON$R4117]j%jfX\@,f
oIeO^.6<OH/WrP6MT-\Fl!9>+/aJ;>HBmHYa3,7U:?@4bhQ??odu/r.;fj%nHh1$DMU#Gp&t_kFEV+-C
g5h7fd=(Y)\MPo07PDGZ(,_O97JG3AjM[62.LW(Hd=(Y)E;7NhNW)jGUcU$%<SGoE8X9!kRBP<JUjf9&
o$\"9[+8*hkHB&h!Nl<JePX'RDJbmIf=SF>#@JPdHFG@6E\^_n+)km*56$I,n+t9fTm*P\"^LNI/l!>Z
R&G50U1`DqEHmSP]!O4AGu26JDAB@ehQ>N'eJtr#n)![1ccO\=+5*mkSIK?PjKIq%:L*pVRC`F.^AXTr
?!DC"G-W)N5JE[>8)PE?o;=fm5?W\"J+nN?oDIGm^9\CL]MVVoQFJGgMd,3.%>4D[UHaakP/EE"^b>&&
MVJ-tbrbJiM>Z%KYienB]ZJR\-l2+dpkcB'rO.G:Obhg#.@)B@fWBVE>Z\SIa0#(1C.IWV4mLf17=TYp
5[cLsl6G[ZE_;*3jX+H&&A'4l47@YHI9U5s<mDgs[sE?SZPDXX\VnW!.!BACgp#JliOSZr1YOW@n%!B%
ft#Ma(.U/OQ\>A0Y5HDhiHBT5dEsIcMh)AOJXOEk?Qs[:pU8/d`E1fO48>/7jkVH`gEH_,UPW"nBj7Y]
!5;["n*<qfMcn3\=N#)ar/H-Rd3o+@\Km3*9:e^Uc=J:T,3MkPTUa3JINdm[Lu[e;n+J."BCi/]931s@
M-j-*A`@np>L=\V>UE'qE\<'.F6UI+3Slh<.Hf-oe!5!BSc=srkpNV0agiu#X`?%6<gbt7""-C%F(o93
*&6X)De_"uaioF=Nf]%K.?hsm+_K#rB@OJ&%D9/9)M6hqGH;(3%o3`"_NT'*kN]3N5+=hS8<S!G-2Yc0
hNEol`=-0IUiY:fF>2m@:FkT`;>+Weh`3]`R1bFJb=l&/^7]SYo(jX!Wb@JI\k&aC(L<8Hg%_X\.ROH(
0#G]?`%RBsl/sYqagq$WF^7"ocpl0g/8S81AL;mTk;#n5N.'p,S4h9]E\=t.dE\9*^8^(44",,%JK&Y_
].?I0b\r\"j]j.t!p!V_M(RtV!Gcgu0$CA:L3-&!jSn4F8ulKCBuO09f3?!dj7kXZmU#ZAi%3+-B")CZ
#aC^=3P:n\-1[e$`oWjQ7mc#/:)<f6Es.D!-E151bfuYUn>5\pLQFY\,orEH$0M'Fb`p_C6&k?+('?Od
kB_rY^VmdsR_LC5LVWWg*MtYrAaP:o;=L=KMrHeVNBC>&p_(oXpmjtTCZZ*BBglWAJl@rH3F=!2B&s^*
ZK%g.^@tG9?WE,"\udjeHZU(jZW`^.JH!Mmo'<u\`uSO[9]tUOdG^0Q)>2DHQQ.I3S$/@NFK!,!Ob,N?
R0D<CeT$M!E+I1<jbM`uDG`B<1&(;1RCl:Jqbt_:*Oh$d/KD8J_9`tgBBQ<q14]kq78;X,@4I[aD)%C>
G2J)oCPj/O&,`J(*1ltja7[H`73VIi;sjn9dkfdRJG`ZEs/@a3=ln.><cF'X-[FS>C$jtIe.ubo<@(oi
WP4`q>VBhso,Dtikp9gUB%CN@J"<[an4&dtkpQXbqipR&W<\PUgF;U4PjQs0b+ihM/[K9RX3<SH4`XPd
0F>hEhM"rr*c7b*U-..je^c47_`'b^Tp:r/NDCU4V+g6!HDWgY->=D;':oB0)S$6dZ(5aSUQoYp[JpO:
>q*=c5lr[i*8i?iUV5fN=fe%;GK`'ek.O9!bG/mtZ,HFNB&?A5Zh2t\4B8#1rR0oBk9b3\f7(-A/EU,P
j(Y\C)sNW3es'7!%LRVTb^),DAOdbn-1*t4@thm4%?Sa[ZG.d1p15s=;J"N.9!`7hA9i@B;M>Q_XDe7n
0rghAB3K5"X"K.>9@om-*Nloc?AiS#<\:kh`nJJiE3.Z+EP:_bqQN2"OD:^u1MH=7P9W-\*jk^UIDmjB
/S':^TCoKSm#ef(+2kGGdn4p6BJ.^VR:a.rSR>Ym_8Jq](On6a(@uqE[Y:!Eh1!8gPK;LE7a9/JV57cV
ZW992g[9iPR#?B6eDqT[Am`$*UVsXagD8s)jq35a-`_<A\%lYH<TS'NIMZ/8Z9p-_^9:P#kBP%9C6.,t
lu?'5*:(!No,RG]W809gEb^j]'j8IEd?tM+H/$d*aI03lU"R_<:Yp8imVAnT'3`o_qpMHJOUSTl7`?9]
b)fP/1fW?*f#i4WFQ[Kk6D_>37BfrY?W,bSUo%)Ip7t\fFeI*E0nPUu/ShDgDZ6p^:-<gVNOfrfW6n($
M`7fqcY0*Rs6*!np<&<%j0<\.I,LWJLG5A#`-VD))_(bVp[.E;MZ$:?]8p@!<co&SH#O8[GBs1BS%e<;
%):gTs-q>aialB:n)Zdl*>nbR`9a\Z9(q"$S&Yb]6sar?7D,;P(JC[*EI[i8me9"SfIJ7Q?hA\M0782/
Z#6bHf3C!.3dBkA4_j#kYgI.D/9o'grRP@HkUgR^i>2.,)[VtjU1nV'Lfb<F%Q^an^VSG3/%SbO4mJ>R
*dF"RhGb>s7NX&K?#u)F92;!%pE?$iUZcIs[3EW)*I#MYnYVFEs0jJ`[HThTlejn82hi4Nm8U:V*$+GM
E>bu/nMk4O5#L:ag:$dQY;fM1\:rf&Cp<UTWF%VDo,s'1?*0T7_Mo(T6`]%_s.Wq3lj4Z[&F6k%#J-E^
Fc67bi.`']^XQA2frP!,h*rlO1\-:3^,E]@"BDk?)L,DeIdaD\_`jUC<0<B/C"t;&gnS%ok6Ups\Z<XE
]8ZN%X3Y,kUm*j7WXn[B)nYigiPrJg#3goA?hBTWBag#:Z0!a\gqS*#!g,_%?EK4:ni1T,*&76p2oiC^
eMAZ^)`R.'c)?4V?K3JQ4k;j)&>0!@;5(9sc40Jc)S+?\;,e6C&MZ`AkuM9kj`"r&e93W]eZMqD@,]tr
,W/]KZ;:21fC>BI#0FUBQa&=0d4Bd#pr%1<>>Go6lcIKEr$S)A1k?HY)FM95IVG[NI+:G>=bql_d8%t8
dZ!k;VMVL*`Q[lJKCbL6mY&)f4;Eh0;?_4#;^mMI1Njd"N+f4H4`QFqDfYLihf82a#FsaK^#t$5L&K<Z
isX(tD.OF0o]SQ1@7"!iq`)@*E):0?[gWg\o?JA'*DNkf?/Ngap0/N>kD9`2fDBphGShn#qnnoT\aW.P
hpgF`jW+#!S"1ZONYnV_8=P[s,IErIf^kc8S"p+\JhMS`i[^C-hX7\f"7F_"`bjA9k[.H&Q!^Y"F[s0]
.W6iN$G?ebI4As!ej1PW:_+RqIaI=?X)@K%hB3;3o*q<Or&@YL2%*P_?T4_uKtj`YUKig@c_*Y>g^@5@
D5u<YNmHXJ-AN<YGRPscME[!/@3@B0d?N,o&(j)FEj0R>,d"DiRc]qOiGfgHH+<a0BA/1p8j!#tj1K9R
\2rfW0`pIE?s;&Mk58<9$q@8EFt[Q&>'A=gXm('j6,OUf+Q2+6>-bYPr^"9mN-it,WOoh=gH643%bp"L
*n!eRU5JBL,,JE)G@fKs=jF+(?!?>tV\8A0:]JlrFjV$a*:_AKU?KS:587m<?^E.G`I6h&S_dP(3'7d^
H6PFjNZ+["mHa=2[OCN$$MAi/CFn^kH6R-E(=9WPT<;&Be1*!69&ltCZ,lVRSk'uM,/cuLa4l%TLY^n[
T/\qF^XF;fOgXE9(<0&6fB%j.9I7]NY;tG2J7d0nRdls4_mh)"`SL/SG*[?j>0-1;;5a/Whs3]-AHbns
=B5D:N+E1jPknqpG.Xl,7s`#Z&;:i)]GM$eXMED5V+)0\Z3'o,h=,7\AHbmh3Ei=;/RrlU/$S!-d0cOY
Y,hV#=Fn8p!]Q[!.SBNB!lRSe5.l."YQF19ME'L)"ZVOH!aFMKB@QCU!kZ4L!,"m*9cdF4@c[0e!k[;i
@+X-?&;:iAVA8#BBa%>DpfIRBDi^i(5?;0QK.]cO'aRJ`YiSNQMY=]b!3$`7!-PVZ"\:N9GuX>u<e,9j
CY)a/*_m4rYhO!uG.XjVC,d:c5TEfISkf*#5-;WgD_"(Z=1.dOYH[bG>60aaX]g3mhH]TV!dia(@.2hW
gjG'E[]+>FOZO_Fq9,b$k(<pim(tgrRascRImUbJfK%JLnK38NhLj9\?GA&hMV_Rek>V+:H6a"Q9g&ls
GV903]f+/_B@KP>037[GoS@.APGFsTNNHKgmIeZ*'c5c^q&Oi_'O<AfgkhO*I9BU>9p0_(lA',p(*si*
Wl!Pd4t44q=fK`n?ku4sD$5E=k[o[`'lWY83QXE6b<9X8%%a#F`n8a?AbCjj$R^k*rJVWXXBEV'V3kCJ
]AmLbmI0VW3g]q89t+C>E<bi3T)j9lWL=jtX<HhkGiOV2>Q("A3#s$,ETP[b90@.$Q0L@#^/a-H>d89E
5\`gTX8bF)lb$E_lhgMFUieXI32K<_`U"l(/jCkS'L`bW.nZ\MpPdAhoA444eOu&TD@'^3TLI@;knoCL
X\IYJ5'2lN,:_6B#>P`8&&##]L.Ka6n>!qjT@i6+BAmp,%RM.<=E3Akp)c$KaG-quo5S<C13:t<T&DQ2
OuFW)L:31.>HTAqcI*cO.tVQ-a4((!df3ush;U0XJ%^4C@E"B%1ucT&'C/6O=C4hrBcc-Vjmio2IsD8Z
?Y`,2Q"M)iW._F\-</D3&NmP$d;[jbP%Rnh/?Co#!a>4WTqB5lkNm+j,Hi1XlBB8/I`:5]1ui>Qnt?^.
33^/2LcD\""EEK=s$5b1VMZT+,EAiY+c^Ap2SS&_7iXPPEn`=/+KShnP`%iaK-[s-bt7HiG#JTbjI@F"
4hcNCH]7[`5@gS.M@+O=.*h8ORRMA&b*o`@`C_QA/M46THG#rA5NXW>C<Tml2<JRek$ba*4>bZc=mI`E
"r<$(E9-b./EcbB9K9CII]pbE[2R56E_MR1`V3Y&jnEN6n2S!2hlAKZk4%G\EU5(4^.6P85+L5?:XZ0$
/*<nb=bn.AoCl$@%ocSTS[fn-B5Va8T<\4-nbbW14O?<sDguDdfB(J!OM`*s`)AaZeNK!1?G[4fD"ZVk
lDlF>)tF2Z[u%O?ot1'K?+ZM38CWn\("dSGq9+LD6,'gB8!75579>NgmJpmLS'FOZj5Vbr8%:*cK=)'U
M]*t-g;21?MHgKQ2eRs0Cp4.^4c*D9^"<.S1IC#NPph,2b2L$43J./Yf:M?V_KdC"2rXoZ@I./ZXsm+?
TVBL4Nq%-sI(h#gp?J@Xn;?K?^tcu4?7@.rW)uk:WHs3[FdYb^H\%cRQ882D4i9<L^/acPj_-5n'7T2V
n`RSlb<3Mm5SNN9MaFH#n1hh0gY)%?7J-lCc0>ae>/BWr8U*1mWTjrTY?051F_&gaW%!j^8;hn"X(T*2
QZ^"!lW#XuX.1@,LhK6ML26duLRL5U4CLY5FM\<+].He:4*&%KAR*GBk'fr7p@(#mpS\=7L_U2ad+RTB
<jsOo!H;dII\k%*r]m+.MB;/*:tH#K^n,-$Pn$KNo\Knf1=QQmRDq@@G,2:O-*p.`<AD@pm`0O$A5Aff
njp)u4:sk81'cN/KXm%2<<kmt+1.;+le#gu1P0V4bjKH/3B><2];fAN]LKWSiMc_;.*pp5X\6uPdDatK
@g=*]\%%'O<b-^h[df"_ERG.<5+QqUEO'^"%EU'r"MVn/4nMQ+]g!ckZPV73:OMUX=5ppb7@/\`;k2Dp
q<pX>Drrn]c?pD]F>;YSd\CFgB<IEpWHl\Gn"hG?T-#m2:^5:B2&ohBESG_Y,`J'6.tX9N7<(]91m+o%
G*=6B3%jbPFs@GD/J<70-k2-9;s$^5Ifp+)7r(Y=]&^TFGuJC1MA&J/(mPgu0)`XMr5,^7ShPa!9On5%
8Ns'4!8=)*n%5cf,`b)t(Z"LVid%a/8`H/"7N6?m"MUtlOQ;DRK*NbLme5\a\67^!!G3_=C`MdAG3$V"
RJZS]aJlL[@i1QORCpuZcZ8g!Qd$E4f%CpR1W/P:-n7!*8[!rbP!Y-kX7U@34t'1+&.b?sX43YlBDA.W
QriN"c<T8s/1G]"?lrj,1#o&pG`K^@kD2.8KC`f<nT)C\:O]krAl!g)^'@Fj(.q=C#FuG6BD)usBD1rR
(>L-QYf$k#n/U-=A,!.rYuXI\I/E6Lbe3Yu9K.^%DV4G%XblXBd[E>E>JZrbXQ9JbWRGfdaIeaKe7lF?
FQeCeQ_JMO[,tib&bo=hkM<)eGu@5*UFI'jIM[,._CjC1SZj_&h6mQdZXfC,A><CBbKAB#RH,eeN=_+%
C0!UG,6OjmoCO(9/S3uT:[F>A0AY2<^"7Oc*9R'5RsQnsrPEi9B?QGLl+KmYKTFJ_\KLJol(Xkm0'U%m
;<-ue5<i+qB?@mc3d@rN6/oLOobWtN,`6Y+eO=KH#=jl08F_/:b!SMWU<-at':Y4(=(NHA;*J[4BWqF8
<-8^B,L>OHq-Q7V1DbDh=:GB3=5bU7/m[RWT<aT\L9]&A:pKtX&=)1K[;ubA-c`YT_4PRLD<3`gk*@J;
'Ya7i>+i,8dcEK]35'Ld,WOPE&q!fOHOD8C8##t8nS4Wh\d55";O5pcds(Llef00./=mH,B/q.t"k#g9
2s$%Dq%[hj4QEC/n9b_knX3YQ*90(XMDL8_L2fC4=rX:R-VGDnWNfK%Xg@j-enT)TVq,UW\)IjfogZ_/
1[2S:a'3Te>M%lQk)n)3&F$m2ek(?!-OV;o/b@?D=!(0/p9-=#S,E6FS2%[X6=<uJPs00$Xb(3GKN7N3
TZ\hCI%RLW.^rEn=h[77Y?l3PgMI[+HWu3SDQC#<D&%"RmoTbQYR5cH<e51`<PUW9@his[:f[V[%E1-(
^9*"M/=k@r4eC\shH"']gF=>h8lgKkX9.pN/@s"SXRrq.a=WQT5d$T$#+7o8UQA+_XmNP(I[Bb>'qC"e
(!%Q*$9/"_:e'9a.M1&Yn4U+qX1bWe;`q.uWjc!d#f`,7fWNe!/QnN%nZiMC)O"/D&(<#0r3crVs-Y)k
.B,@`]+4fhI!NnNrW1(2f,G+`_Wm,7F^]unr+eLrM2UZ9At57$qREk@&?NZFWG,M/AP:pZI?4Pa-%W)0
:u5]n^DGEJk+tWZf#bZ!)dZ$uCrP@P?>QsW3pF+b6Q1,A>NcF\O<.,*\A)3kBG0Z<+&.>-;eZloFc[rd
roqfU_U9]U3leu0$r*JbjOb9pMOeb-4fhLMQ&;>.ZJQlIRUg2F2OWaXDb[MPBC!bu)Fou&KdIt?-:*ZK
1s)&=V0p!/X21m#UFWi=*L[+r1et+&1Tc-U+!<BHpS=c[`$*JQ0E,B,7H"NF8VCaC\G+>qg^+Kc@_Gs=
8O.:[$Ig=]RNs9VVMVspN[EHVG^uS`3gt^a9MMf-Bhjd"&cJVdCjC3J%GB6(KtDrdIMr$ckbTbc/89J!
apu[`UNXmh:@_*+3mUkJi@CqT1,oZ?cnafjU-d%2gd!k1it\a_B##cX>"$.*`$6b=%.XnhHI>9V6AqS8
WmDs)7XN_Jijs8Wn42hg?`c9e`qkL5'sD9<Dpu2<Z.ls=<+ri)n"3LOjd_B"eWY&<P'FulanA7&:H?&_
&+A91Xj@]Y%STLmm[[6=QbhO5e`+CYGoT3E;S:H6h<qPk$Wgqu@RoYT4]Z>p2VVhlMTWX2#!SXE2iO7u
kHLr`$/[,%9A-1D[1:*GNPJ5.X:[3Da+prYQohJ%b]++n;F0/9^T*3:er3ZH.=@=lK.sM,<)O;31A]=p
-h:F8VTB0$RFt3V`F9&KZo*O2R&;[](G!63ENK,G``[se`3JjcajGB&NB)-Q?lmfCLW##:lB_\-H[Ka4
Q4La!;7D5.ViBjWQ)#FLSh<iTk.)JXY(FFA,#=53(^dR3a<:^%>&O=J`=lft+gN4q=>qSu?+g4]8)_t=
WW)"E&&g?qPW48rN%giC?n4aj?]l(\11/,#cWARsn1^U&6M276oKOgo/A)p)>YB24g'nGYcE-?<8A!!D
3K%OW:fD#s";g>B9oY532Dmg>*Vq5t7SohB%qYj-LPBjCqa"PfSBnpV^hVrk2bqL\=ZSkJ`h%2Go6`Ve
Y1^.?atdagPNk<."E0DM%E*DTn/uFF?`ad9*@dtFXoipAQ1(8N0ji&.Q%",)hbR86M%P)oRf"3]nY+>/
T\-t-G2E"-?MZa."7USGh9sMprY)KAb.p("H6]ZOU$?RgFj1;,i<M6Sh_bQm_/&,giEX;#;qnc+CU3QQ
q0*Qd"VZ8JQ8+\a$!Ak?2bkkiqfgPDbQLaLpWS:WpKAs8YeDS""@*S"JR@':'r((;YBP!YCM^]Lg%hAe
dYtnZ<SRtSs27[EMn;0F31b&<`::P?T4#ZC!I(%C:^?2M[U.cb.Pk6F2ZOnk5"C&iEMr9aD2-YiE/&O4
`b+F^;!NFeI/ad+0#E+dS=dUe`o%b!s,!M3N=k!32e@Y:1O]EnQ;N9F:jNXfm-o0GX'ps_T,OJ@Tmn$,
1p3AgU9qU6F*a/;\8W2[nJCKkl2EpuQ1rW$%]"FYJM15IP7)M+4JJEqcu'/T1O@b`W5$OAfo0"ui6Go9
TYk4IUSrji];"4mJ,SdIhlDS0<\h@tfdufnD)P%8B/>iW^"BD!daS0tojD8/d7Od_FMU_LZd)HqNE(>/
_YR!ZdBt#DkZt\Z<`UrlHJNbW[sIG28j-B<kKC9DqC>i)HE20ZoC&>>Tp1F,`Um)JCah[=MHJAXW<N^)
?B+!1>G9=qee_2JH,J>+g"gZOg?l&BH6^FF2\9o$-%,J=BT2dMLRcu"T&mZOMOGp,+8#PHGDQ)DhHG&s
GuiJ$M;9b`RMc(.0@mhA]m0o@e-F$Yr(O#,<=OQ@m@pUH=^UD&T6MXj)Oq[nh5la5rA%gg)InR^X6&QR
occiYMmoNQdAe.)K2l9opSulPfnZu_b56,a9u\Of%p#FY\eE$I5)bc`R5;FV'Uh<fWD\[1obm"'pcE!N
!9+2g;ftmEV\rOLgW%<Mpk;*nC":Bbl.l/NeZNP1h&u,`'DKA]KGm5O<Nhs'CW$gU-tB@%[UYB]PURBQ
/r9qg]omG;qhZ>bB&.S2a3%F\WF0;;dOg\IhA9#-+m%P\72$bA-^_sMM1A"M<FB15;+p%qX?DB2s2;MI
(\k57cOI%X96W5+*Y;,NCo)N?036LEq@W[.]V-Sj'"D9F;UjqS[XN2_\R2H4@lI[rCIbFm8P3!;*E4(X
I[tZTm!ar//!%RU#Q4j>p4Yg4muR:;m^]U'qh\K<GqUH`,J>21kiQ/Z][?IOI"UmaKXH$Po:J#0V:pkA
kYK\93[&E9]-EN<VE7>=oqlOZ-3pYgO)kF!Rd8UN]."984Rq:Io9I&44\cs$X7?2c]&1GmBO,n-e/!P5
pU[M<4W*D&4#9caFanP<M@jb7V"Y'6Q?IlWbBkKAc@!K)'JHJQgfDuJr"A`C9j3FdO`kD^YZ`JMVgM\f
M.-UnC,ORCW`j!4]g9V3)=4GKdpm6(IspEnK3X:7mFgu%AaIcjA]MYhgT*j3UVH1R)Vm;d/)D4ekctPX
91P^aU+bgAl-A%6rKGmqXM7PP_O\5[KQ0rc6!3AfJ(k3@"S[YB8i]"N=A>Biq:e/7aI[7Pf#F+BXI985
;hZ-lW-0ZSatuq%#^-@0(f(GIR]u+O0"]dUb;Sp!g<OI*%(;:rf2_,`a$cKKd<]86H@OW6*nmIhEn:fS
:-lCDn>qUsG"TdBQd9&l,j5"%@CC6`iB9,*NA"uJ?A8\:WlNKY]$Cmb[;=;Vo?]C/rL.nrd*VC-la@p?
$(jmd9m3H5_YEQ\*H^u?nB8?9M[]P!fk@6,IOk!:bY?8h@kTdCN*FAp[@Ju?\)ZZWZHdjWBstJ=^LY5U
\-;S'1tq=^YKiD4'^E%1JjP+?\q/XDj*7p')9%4%`Zdk*%%B6E(b`A@@a6HMjRZ"`/L\uVOpNf(A^Uhd
[qPO+/m?+R^YG/,mW^W>Rm1Cb0;K0]&#\2:e8DpC3;;@3SMn1t,Ok^/D;dT@>#ot=mE`T2=b4?fP%F?@
)gHA4/TP1,iZ?:p.3AZe72=i9>8O>3:Ed%gg&Bi49iF3R!Gt3:\SlW$mCfULNAmiN2r6o/3jEu1Bf2B7
;8X>k@MO7N>']l^[T)<sH*X$$0@mlrFFf/Xa#]q0<nZXdfBq7l]^=BJ+#dA]dElWR'1GoDi/LhCr,OE9
C$`O)eXp2[eKIh?%T_aEJN1*,=tA_%Z=8-hpIqLDJV:EakE+fU`fo9V(S4*W-Y/%#?@%&,2PLFB>3e]n
B0@\Ar#&k$&PV#/5$&@rA$j^u%GkSm#-p1rgXi&=NBfRbf)&5\R@M%.b3cuFc\[tCo/hDA'/!F\miV%@
Co7qG!&6='$T8Q/L,G=,k0<+&HSD'\3sDH@55_PJ1C&,s<dF0ElCNp?:`\<#Xp-r@T+G@5%j]8npj4)h
pNk8P"Ks0n13C%o;2-bCDr$XPfUs%1.I6l*]NNG[.(BVbL8Xhk:L:3=Prs?&P'9q6V\\`.XCFss,UuQ6
C@7d3J1&S[&j2"%0&F1lMI.ub7IOO[%_`:A:lg-ebD#HQ52'B98"F]tU1hNd='$P(D<bM.:u?(n5.7%d
V8)QD9:G.ZQb$MReQ;$5U@/WGf8D'I'=Q'BPbF?Up!R@QI44,5]qOOG>G5L%$5C-$Htc=BhFG9rS/Jcc
<SXT:I)TNDI6D>dC#Q2[oh22Bhn(9IPHC@UQBD$U1s^R<pAG*NS*7(_<16D9^$,?fplLc(^E0c;M\fsH
,+k`9g@jXrQGfcfhM3VE7W>_@<O\'l_@$%_D*(U,<GId6o'R3QWmsK-IXNRAQS%n@[-IDXeb89*qrhsF
NM^CnmJbs1LJI(djO0JF34!f,(T]\kHMf-E2!d-4;CBN>gt&Y$M:Sh)SI[5LMj<)Eagr?-.D3&\X%^!c
:5a.fN>8M])K%G22>5TV\m?-MVYrWWeGk8Oh9taVYq#79g3H'!&(rD:Vj3ec<NS5-d.YgA\Ur8*NRfi3
/m=)]:^;$P@]p2EC"N2@^3mYLr-!Ab^6:^%>"4[ZLSbZ!Kmi"G[]ufqaU;J<KFb`!gI%V"[DVC*cT_%L
H`2#rm%LK,hm)rLHGn/@i947d,bNDm$HjL%<8i8r]6>TK-rtFm/@fsN_YCthR`7QUkVGEdLZ^n,dJs)R
52=_VcRZjnT/:*7Gi:Wc4=JK*W*:>+Q)>+<S6$c=@g-Q5F^B@_9s$RL=!!UP/!"Nrc7?7Yn[q1mWLm7F
F^EJEW`;2AYC4%=bg.dMNq:EB_q8`f.%aS4d>cXZdWqZ4bDG[#)D]>-SDSEp&^kqn)!;Y5;S,Q62tS#$
K0)t-?W,b7lb#RMS[\<7?D4LQcRYe7cR[udS6'%p6X/i#SQCK2>FFNa64:/m=%I>&3_47md&k",/'n"=
qV@qtn]UJaYD=t+Koh'+/)6KCH0*TVn#25;eg4kNd(7_"?C,t`Y=l+cBjL#gPe_&%H2_\fQeKRI;U^K'
\^`^B95Wj7ajG7Cq+Fr"oj4!=H>@*krY,1?"M;(m-2*;`;EO>iWSP3>[@>bod`-R<Qcu_JBI0Z3jE+,^
&ncm`P#\PYlVa=hIjW1fZh#kgf<b&KpZC.T`h2aMSiQ"!^(ro`m*G]2%.XAo4/.f9^0Z:W+9+jrk=6dQ
EqeFjZTiHiIF&38_jtt+AZ.Jq/3Ghf-<nbMD#)A<D>;iP771(22W<GK4'%=VUWK\n!m>?U=eF>M%GAj^
W&$M*@+\$tmt_c(grjV?cQ^9K?n>kHZoScd,OM201&jh-SIff4kcl?"m@FQ!ATrG?U>NGQSVOZfFs)i/
^<1BA45PW0,PS\Wk[*CHeneU?8>*]QIGO0Q&aA+MldW6mk]2r4'60_lHYIt!l_[_`D9VlJI-;0odg+S)
pnkrBc,mT0fB3M8UC&H1d91QJl*!/%Zj_9Hk'M%!]:Q4U9G_:r4FUpS"kq*lk?.Fagd!mMXhT(VB5?9X
3Qc,98+="F"!W4jh&AAcNL&r2(Y+mee%\g>?2+ot?p&C@?65"--gJpC+Pcn.O:>"mpM].dP;G\Gm_`am
n_(FcmJ"WAmqRPe=3f=SHSd[S\&F36/6(>BPCE>$=@Yp-V6s/V9?Fd)K/^6OHS=0PgPVBIX!14-'7"n8
='Cc36d-C`kgq'O"'3\%WiR53dA0,cncN2$aEs=mPJ<<I,M`X#Kpt'A;p"]NClpL1nbaaUcZm4\KCM@:
Y36b0)^<W$mB0.fT=)3p7.0[D")8MndV>[\o5>k+N,3C@1PZNYQ^@\%T2F&aH%'J<d66&Lb=u!&'3\@\
:QL=@B0Bi=\,Rs_/%"EZkVbS^4>i\Uo&b7@/+,PAT2_ar?o@J%6kObQ6jlY*eXa0BHOD[XFF,$M*%]:(
GKj[%_mkallZDq$TG+)FFdH2bNIr.A?>%"2';'rGGHF8?0.g!pG:B^b8\$pU6*)Dhpl*`+e;Vtq)tW4f
KWR(3LoXM`Ecnq,Xb=-iNoNSJCj41'IVZ3tbS_)!.A]odG=3>O,t^ti4q>6f)qX>_\NBR=aLZEA1uodn
"+,Am%Cr)LZ53WA!2oS@W]Y1,#=n(!O8*iO$N3jaa]=C6=&3.X8*UuhI^LZP])gT=72Z`gh<J/mpPfd&
_kirEeZKQB<jbQO%,.HG9hF7#=9g(M=/'Bf@uo2HdNCdS;Of-mF[OGajoJr`j+E6UHC,;WqnB"Tiu\",
f_B2r4o-W?kIDt`n'gI^QJXd'QZV+a&]=LB`G#YX%<pY2eaoD8>+KZ$eEZ=%Vfi".?/t!#FkVjodd!Qd
cX9`_0srdciXIC36s&L^@moSj5,lgr3Be2L8)'cq_1f%4/F0:BgD_q<W_0<mg6SEY(jQXmVfXPmD"hE<
@8je\E;nM>C20<fji&g7O-ZcN62HVk&9cg4BQ(%#MTOYr@CKfXABr6pj_q`;>3e8ZX&%4=gZY>H:WrF$
FDL8pALi8E6IbSK<%IY?Uog.I^fmDm<j'SnPHBW<-$:3/eKHeME^,IdR@T,]/qRU!WHF@'']hr8pnApH
+,`K5XF6LdW$@UGXWs"8H58\JN:ANYKrWl&dF1tkX@Jau#sDOYY?''Cg0p?;C\TQZrE%+*@72@bNk(2]
%bGAms(U<>];bIZ[F]bA?1STZ[7XBc]f/.IbgTmrotJ,jqt@;O\(>r`?L*$?EE]0O0!4lPF$=N25XXj!
D=uNn_+Sg5V/?K@5"g[3K_:GA'Mi&`8j'(1c?QX[b`f.5&(25=;JW&YCHLb,H0\d9lEp+2(G0HcBtf$E
\p.un56E,pK)0^'+lBX/jj'P7m2QB->eYSe$0u:rG'iU:>C6<HHVV7UHVSsr;m23"a=0jmT=o]1>QB)R
gZeL`_73M0#fN]kluY3*[?fDt[=6:PV/.*11u$EQ9OGaf0')-%Fnn$PDQ@adr#L#B[YjCC\9;Fihj2mT
'?B_BhC%I[Sho_&Q4iZ66"\W7DO^Z@Y?"f\,9Cq'MVeZ!*[-<f-SC<91$ZR\RUaOT&pPY9hg4<>iq*.U
*Y7_I,6+?tcq]Lua2C`J>Mh7>N\=M:[GPpccgQ;AkYYjh)7-@A$uG`DH$TOO(/HLF:VYWF5,e;%6hF]i
65][g3mA63R7+.lp$N3.V]ajW6"fIfgoO;(Yt\"9Na!tS'^$[XcTZ(nNof4:X4Dpt<kl\8Y?#8p7us7,
a\Q83EAmDh)ej/<WZ-2DT*<"h\jqTNopr>igNmsCS->MY[Z]i[Flq%uYFp+,l*QH$lOd.1%b/J&ZO7Ac
\l.i@kH:s9WMbNln)e)$Q?<K+ltbR.Aj_F3^^%;&r:5\F\c1IFR:rb5Y;<3E6*+mRrW8q&0Q1fiZR5-3
Ja]J.V^aPqDbfca-=cq!o/H%`fA:<U[6Fat(3RVAPRkE[D)[&IpbD<@UL%$_W]M8oE=B5iB`%`Z@Hd#H
8Nerf]9krX?Y0-AHc3N*eYV^!*96(SN$$H*X)!,t*PJ!k2"h<&!"g[=oJ:km:BnR^G+]ZqnDQP6^V'&-
o=`bN2=.Bi\Z.`VT9Ids&H]oWJ\M:[$Z`nP3,?3br/bO(k,E)&F$FO5G'&1Yd.[G$Yb'lKKq.+go4I2H
j/$FJ8(p&^"?fW[>kB/1II!c[RE$1.H=C(3Qhh*.*SsPnQg<55g>V/f=q7o0j)>N+d%_W&q[<=\L@=5_
'n:.r_gPDHO@l0RU@4.:nL^57^#YG>j%H>8p@JDa(&MO_dGh=98coBr&HJR=)LknZmr&#\MP*Z+=VLWR
)H%9qB<7D&(HDo=SCGI,:J!^`MK>bC6kgUro4[VdN>1gj3frr(V5dP#cEB0TrO1>!4PkhLAs=0LD7o+6
oA$cL/RKC1Y_'oH'#fFDj7oHq*:F>C`)jpW(3/;<N;a"rgYrR!<p\r?9<D<Bmj=#In!Nddj$;#L:!I_r
78SWWn_QZh7D7&mqlV1O-MkG)mQB?,@/lC7Hul,4IV5f0*^A./i,Rte(]gjc>e?A+r4RrES!jF<eA/QS
@T)!,gs3mdg9YnR#qj!%ELnQ(`48D*UVH1RRRU#VNk[O#Tt`Db1:W3nM7u1gZdNZVY]97&3+3d*FrW"5
T)Z`ThVPcQruK`4&>*S]rW^BD)fKk+.4cr<gr"P3(\cjjg^9Tc2oe[4O5ZC.LYft=Ic(stM!ot"@dmOu
&eQCAlOi=0Z,LDHQPXX5I;nAUr)OfuI/?(:#N>+0Usta.Z!lbP*WfiTN)/alEs2ia4m]ppT<*jcJ*mG,
G&=u`e=8/.MsmL>SR7hDa,X3N-_?>6kaaPRs58$Yc<KZ]=U2;LW)QdS"ZIsR28!(+EoAr-\Wc0^kV$Eq
q(sRRktR.U)I+Y7LE,r9c5=Ud?Vqb.rU7&HFC0)M`ekfRo&7Ge:8UZ/LM3:\mLdlo2tmOm4jID8=HiXA
fnD=*n9;"=7tA?ijkNOE"PSMh)U%9fS#t$L(<K%W'],k>$IcFs$Eh(_.:-\;F2ZlfZ"0V.A*B9+/W<)m
C`oq:K5TnA5,ed#rs$5bX)%DBEkc2)n'^j!g^bE;lXSL!5CS[AHH-UTG_Fh9Xd8`G<iCe2nEdjX&[!,q
X+hTp*PU3MGcL3$c9?$cr\F+.CL5nOE3^9LhA#q:@L)hEPI=_E'fF7c,=ZIS^Je;]Nt;('H(nJDbSG]U
)3^o+@oZFAL:t:<5M9SLmdRP?=N!6_NmAVm!lYFrU"As1SFZ=/bB#VWg(Bar*d,uCAUf8?KYZJr-S*A[
]Ur<drR^WuMtV.JFQ>b_'J\C.Tii40?Zn=`F:HntMao.W65FFe;6-]AcV@".5t<6J:+L2Kn7/F4;>^3d
SOh4ViVCi%O^1@<+tBrX&BB%R2u(>)+rd]FAi?1i0?U%E%c3385XX3c@aqf2`6EpI-0m;"8^6(RLW*I`
2u^*6NgB,ck+0n$0gk4<OYks2r\1+'%W6p^EFJ[f407NQFqT^'2"X0cFkDUXJiYI&cVF?$$0$RVi_V`'
`BZm%ZN@0Boe"qV*1>.2q'M^sr6@(?)/U;_R0:Ae?[.dWpuM]#X790DN'-@')u@M(0(bime0,16*H=r?
L*@uf:W"j`G_nIj],PK;G=9dW65NU>(3e7?Q/CQjH*RhM5g%.mgeD."`81/RH?XBmXR$tYq`ai+9'Nag
UAr&,p#]<M@CjFI)sWN3Vr>XB$RcFtIEk$i0(o>3\T$a1&3\jo#MDuE[!-Hl.^M<Z\jcX7pt;pPLQlNH
h^'?##B-`//b'u@$$hGNZ"ilb+;+@%&1\o/D,3S!m^VT9ET<_X*m'&.:KKn*d+XQPpToYDM8,\Ho)ZGT
c/qN7(T0nnh<b]EBfk,1Fneqj@I)qL[mqRoUP4i&W5E-$E;Q]2rp4^OX]^ZFT=mY!U&RJ.kj>L;H@DHI
qbFm^l.rQOQ,`#i]sE0mF&MIiF_Vn*f/Oe8U;lGZB53rs&\5odh2Z9@^:44`NU9]/1_+P?GjF:oVK'2S
*]8uFeQh,W@u3%&,AJ3-C)gdYON+Q]A(!?XM^".5N%378aR'QXLSJjK@s^/d3$#Sh)Q0&MKuWADl`b)n
DYkJh*$Wuo`7-bP.kQ'\RQ*-!f=HX=C0e\Y-aa4;:*7t0BS?ql`^*(]coA`'^".tY2jq\MQ$o=FkuuuD
EOqt'1_/D2NPk4p`p(pLVK(6Ul4isL2%HY*HL%<.8phHoLNK60Z#cXT*(SL<`7-d&,*JaAqPG%%`p&sC
LM7_io=2fF;chC,I1T?;FKIm-3*i>"]!#FG1d.OP5[e@H,1efme2Y->4q"@)ZGrRX;<&d3OE?ANC6HR;
]tG/D9oWXqpnVo.'rZ,N0rT)tIY#KVS\;%8I2$#93+Mn!DoNFTq%)8'XqpGeY@2PAn<6!@S3[T<HprNs
AIS-B1Gm(AI!1YDiK"Z'r`mbS*aG[7_3Z<bebW]O7M!O,&k:=[HD.a@cQuD_/V?$B^]"fMrRr?udJs)R
5-PEc^=&IHX&#:$*ZJia?-J=D-lqtH];i:4JP>pEpN>!?I'd,&M#*#HE?YqQI[paj&/L@F[8;kZ)dgQh
fP,M0[ZSmKj6d&PNR+?6S[:h]gOm<o-8AFHLrV.r<#G]eCioIOTG&b5FoEnaS?stqJoCG&K5h80"p`l+
Z`eP=bf;G`2n#L<m\@ZkK=%DD-.W,q3h9Zu,qk_L64MPnkX556>6fq*kq7OUZk"n^KB\^;/&aKD(=1c'
X.ZkPM]R;VdJG.&PQq-4R&dE#4#^UY)t(L(W#;>b:6W(mno6^hfWZTfq!J\;Qeg.pfXf2<B]E5;[;_bo
`'c,W"NG$5`*S$PSb=U9"4UUo(7?eCBrDEs;2)HX4q)_M+b*limG8(jT66e`<jcD]=Vf!'k?rcc-'LIr
%4<_oG&#?N%'@2IHZ:WESZoC^4rCnDe"iqJqTu)A@2Hq)4mKM6Y,d[C7e7qn`Yh':;K".Tk4<WnL5=4^
@i#>@^H;\Vjeu8J<Jb^:LVN2cj2UhJc!nf/@9RKX[OL'?+HC<Q=s;dO3ZT7^LAlcsdS-<A7mo,sgUm0e
`-)50kUe:fhaE=`G>p_.]g-2QNr$!."fMD@RUR"\B9<hV3@;(fX&c,n5!3+GRuP[8(qd>g%Z&t&@mRFA
nfE&P:dWd^goQSIe=dUQUOm?%4LlhCDo`:]:A;lKQ,kl_:pAn/`Tc3G[FI6co?>_h3<!m3e0)ZEO,RK(
Z('U1);=,aO$V=G?Kcrdg.u#_G'gD%I".t776'"Fln?(;qYQ%P^*!&YeF=rB[_jg5rY?R,DoD'.@=S9c
)mJRM/oM-h$?=kHrT!pMgUZ]$+$9?X')m'eL&(Wof(4B,h4hZtDuXD),2*=bAPHmu7tNJYYgNZ/SHd^X
>&?4IP])RD.bN+>RbuGkK8AA1>Dr(6D7B6>2qfA#X4.![aB%rn07@seRYO&C#h7b2>GeUo@0]dq^Cb;9
r5E*86G8rU/Gf$Mk?(!j<I(Sq+l+qIFCa0OH@6g1&Lrj6SQkVWVjtGE2-TE:%JUi1A^jT(N-)3"S2%q=
JJ[dSEkJ#Hj#*=q;#VV7UtMlm6kSK]l-,qQ4D5&-Q$c<-lci1Mb?'FQ*&3X8@QVsg7$%"qXFc<NlV0N4
X4\]pQ^@0gHgg:$TS[t?$B;#t?SgJ#4']e+o@BN&i-?/b'2b0l,GW1\DSNk)KC+2NWF9Z;$KG&Sqe,OQ
[@7'7IlT^L4#jrC]5WTJ&TrB4o))oc]5h9hg>!qW\EVH=AepoNr3X^b<4%%+"IZ1<Z@cGi7eSU/l"AW`
k,T47;Rth?7;-!s'$rQ1oSF>-oE])UjeLcu_!qp#h32i5mk)Zc7(W`sb[B`HWIrH4<O_'=Wt8j[4OB-/
QMDimbZ!DjFrds[6k(Zb_Tf+dJVWq-j?=Ml7#B?XpBt8)+#?.:[KMUOZpQ."K$>c)$8s#F2=&,3C=$P^
2BQdGc[/5l[$hd;3LSLYr<$K/A4G;dUsJaCI+..Y7n>W:"MQLq!SriOp>7110D;-%[q;?Bo[[Q;VW]dL
ZeD4KAni0rVqWeSCNobaSX-NTl-Zhq\M-+UT:.]T<iaE9#+P0u3]4dgp^>g`8%LPPAmm&nWrMS8Pao79
i`>HbOm8AK:Ln/&a<s,(WMEeA4MO^QOA/k5LT#ic*77#;Df8)<^-d?P>h_(oS)9>3'U%<dJTLcVS(e3c
D)Sj<3,DaE0sk4pKXgJ#Hs!@Kcb#2YqlR2Ue,/SUZ*/K?h^@RXs73K#ShS@1G^j_Y]PcHp>j5Xs=nTJN
XS22]9=\F@o(UK5e;.A#GKBn.5EYmNe!@<EjlO$[e<a3IEr".pI-nk)Q"]\mW#\4dM.a/mBU+$%mTWeg
-gu7D(c$Hj8t!(oC5pdn=)0,FQqoB,dZ=$!O70UP>*s6n[Zslon%WYWAY(#_SYWT+X!R.4VbV%5IA@ka
qZuhm;o:7Qh*Jd<rUO;rXN=,9:2N$d?V.V'^Mn'k+HC<8<b46Jhni:jg%(kGAKS'UW"l9.h)<ZfZPp)&
:XK#;V*.']ppd@I`KA4.6-.PVdKllN/5,;B+n$St)_;4m3SnYN9VphTlZ7\)eN$m0:&Tj+TMFdPX9J0&
?iBB1Mg*oNAo$esSdOXll.iC'Ff+Eta8G>-hcd)OeCj]3Q(a\Zp"`/=Zeh@\la%q.5[DqD@b1[nC:/QM
,-)NPs2:23c6!JO0tH/4j):[DjJ8V,iE=R.#1ntqcBETcNr9D1"(9K@=WnlYgr`9J3LK^O]iKFX327un
CbrXe.'Poa[/Pm)k8WSrV0]ja])Q5T^Iuhqk<JM19(e_mlhug]LNN\[ZdrbOKam!'AX_UXm?&Q)W/[f_
Z6Ss-3oV%J.-E^,F#<c4>Zl8:c[XrISPCtZm*G,F.<sg=)eI*?iTXBca,me].TMG0CIc4GENdfCAfIWm
dW`m<PNRQSe>"BL6fWesj#qEHs+qC*H0#4Sq8hH8ls.Hk5B5t`h4V,+S!F@[\2#jEO)4e+IBM<t%M8:Q
\E4RON'1D?XQa)&*"2.]DJhU@;9\cpN*'3$lrfm"T'4U_/5a`B[fl8C-S[4#r$CdQl+MOPk>s%VG=O^q
gL7>$^Am,)U34NUK615LOcrDIb:*cnoU#2mb;8\:Ss)qpDk#U:qdfXt;WsO``gtGIU'86@_a)2mfRKET
:M!$X);BY2c#7X(W0X[O@mc9`25iH7fHon;`O0`H0E5V&)1h7Yg:d*Y"VJ2ai$J>M9;"]AB]U^@Je!Zn
(6o7!'.J`r^G#T+ro7_r'7O8k6X[Nm4*fD_dhD$%/nLjD=1_[pRD"5#3]gP+U(/K@Pa0'Jl/[3-H1VQ>
]'cko%Yf?qB]si?h'"T)X&UNF>ALFfUYO\^:#7ZfjHXQ6^@Fm:N^%kYL[Pa7;-+`8K/U8ehYQR+\,ISC
$%pPhOj3j=q.Hl(qm9nh`LXL*"'M%Bgl%QXi9gEI35F,31=Q&*=qRng%a\'9>I92c,pKS\),]^D0uaqg
c^(RdMDOHh,8W5=Y/kVfPUPAK'6,QD+C$p&O<Tlgc>]$Y]95/.foTss\WaUp3qmA"XB+[0CC?0(MZ5$<
i\#jQ>:EK3A^JJG[8&<TCWLM9$4*@7m]Q9L4#-2&:QkZ;PB]4tl%K#`(okH'Q0B^S2Pok]XSmT.Vi%4*
O%gu1jO]iD9B)jZ?`Af^3I#'$61k(&*qG@'>80Fk&(_9O@p@)"p0_'ofZH):4@oAW%TP`X[89s<W949+
VKV<C>'lj)nN]:L7oC8^?$.2ABZ1kddIT]IBdFbZf@p8E6,1R1I::M&qdVh(0gkQ?QPsGKp/8a^deV<[
YM\M%qHdo]9o6R3)h%1Nk<EtQV$:ZA5e_[K`sHiem>dc$cD[HKB,qkt*oBV=k^0_XBn<YR$<un's!=Ds
o\R^]eNa>@LI8/NEh.L:5lK6E5B-50>FV]Mp.JOmp"h<*B0e'!`Q-RRjS^BBAjt*#Okj+-%6Cj4Q!2lf
^?GG=;qs4H:]*8T(A\[;)[#g[HoEg\A9\t,Flo;Z:lJdq%s5P`QcQ+upBDAEHmnU0H\AdurRK%:Sks2o
<hY)'C[p7*4-k<&SD9l[hYeb_B_Y5\[&B:Pkb+Ku86dVO-Mi>RX\t?haC2,f\SL;f\390Q$U9!G#@U)o
[<d)sHpfTG-5eP?aaEq"qP^Dl=QKtU"G'UE+PI!n'aa?Jh1EZSDKEL&5taa/SqMMSFhKYK?14&0c[TEW
7NMt$o^(1!eW+n!k]Lk_;^ETH^SR@/0AFQ5m(?L63PR,.aUm/9T9UBZ)-C?;9n27"rPO&r^ELJQHpeR:
G8,Var('ZeT@fe9-R'Q0^:sQt&J!$^kRe<K;%aHNa@MK!\+:BeK?RrIgaF6&^s"6dE2^9Pc3OY!p]!=`
#`#G8D5ZX3QIjBR74'bEPc6A=p,DPU^W+;uHcS&^&It):(MT;qgDStcr-+*=5cpL@&IoOYP8W?;GoFr]
cQ"Gi6=BXn/+PZUEk#oR&ItT@,906c76&d^5@VVp_SNS&n>p-[rPUm+MtraaW[EZHMd]H#DClZcE$/aT
6!_],KDUD5@I'5=\s_MGGFgoANhM[-iuiN_>:jm7Iq`(4]se_g%k<O?\^;XrLi>U#g5+BHB"'^JSnE]Y
Z/Q'>a+mcLqJVsn/G4X8$5L+7GlHaur,c%`_\fMu_6f&*n#_f6Z`dEj=(%)Fh"$TjO"i/O1WTiWjuJ=8
,H-K>)<$*<+.!QLW"C$_e3*IfUJdJ!\H=]4HZm)+(ZcgZ^0H_<5\/)MKL8ZR;r!R7`_],a36%Mh8NijC
:?[t>B;&u)bh%p[89%(k\OSWdc/2DWR,*B,%dd0+FWtK_g'-1VQQa5>OGDce0B=6E3/EpG6ddSIl7tC*
):fY`cji(Y+rlR<c80;@#Edk+[8&kh^DK7H)8DX(_Mh=BLI#'I]S22#g*VV=_LYK=_L_Mt(u`:!G_?0G
_$GfC^4o<7N_M6&Rse8SdnOq8hXUpWdY/8CcYX]Drs[s#%ZY?)nm=/hTcNFAGd]nf<qP?Y<h&m=DP1(Y
)uMu0I^@fKKb4fj$R6W^1QQK_-6G]:1s;prk\VR8#`#:W':\MdaBVpD("@G8KL8\'$+!hmA;Ok+")>>Q
O)Sd2EY,@S'TqJUUj+\sI$IqDSVd-[UgO$@,=[U_:EN"e"1rJMfP:=?O>X%0CQ(m=Yo+D*Ye84;PW+?k
d"j(<#Us,L=kH1e2g*,h>\_p/\=t6?ID9p2kCQM\njF2,BkEOHpPPjZD^sVjj*aP&(frOC3J0%c;le%>
i[lhL5@[BKKhjEp>H;?6@apF3eGnY/5?^.Nohs]&qW2cS]hcf>ffQ,:?$/e0U:9dKiGD/hX?GF@bU3q@
0';X=-?_A)dQ-BDLf\Wp*OttEc9l:P]S1c]MH`IIZg.("Qd6k%kYeiac%s!0Y2$b&/Vc2u*kg0+XJ'9B
lnl>SXrpp3E7_$jUA]!l[ArAEB!Ot!a\U-742hlI]BuU9U\M;iRI.X/]XS%'2J_$Wq0.kVpY=OOW#$h'
!Fh?];tM#tK9Bd64l?oc$H=Wa4SoUsW3uh>]lN4P,KkDL4H>T4i$^]@:^^0m.q['a8f>Hlc0YO-bYu]s
fQ$JbLcBD(Cr&u/e+9o\GE&q-c5!&&ZqeAT@m1KQmh83Me`OH8nS8q"`Ug=n4h(gQN=N=aVfH[7Nd!H.
?acl%M^t!*66b;O'#TU)?h+]meGamkHZs.h#q;](Ebac(I:.79U=FHN(<`Em&`:4-^ULguX5jouOnY3P
0c$dNc?=6hH\2fp420!e7QjU&dJ$/.\?'r$o8`cBoGT@P/u<emmNl^'nCS>Xiq5Pk!as[)oLmico\PB_
e#Kj%k:3#GfO&")Am3^neb%&('gf7537%<K3HL7;JjX,k%=cK^o"@d'I[*m$WmXl\`P[T-`/3G`e)1<S
0'Q]squ\"On*\iZi\e5tG"/$2S^UlQWE#(CDkol*npJaVm!?'3-4h8tI,*0qe9:1M(.LF-#=@2L0%$Hk
XeP[tlrmh3SUY/"6:>pcAUWC^1O:h.da#ekd.$C5>[RqG=nBm0CGMqkiE\&#Xp0Q-8bXW!;#Tn7f1=C^
o?/"Yj:BC8`ZE*RD#CUhM@EHF):^UdJ#47[aEFCCW!=1HZ=S/EoN;]]B*=UF^Vtu!eCs[E>EsE-?K#2N
>q,^f_76PsOqpTVncISFG$"*\T+$+nH_I@ZUV"V\`:u<*]'F?ZFrCB7m7t?naK34KZgG<tTU%P4hNP^N
O*sgH9k>iNhdRtAC_*7ugEIKN#$t-5)gI<jr]S-pA3EW_B_UF6Nch2L-@Tbs-/We4qKd(FAht\ON+aWo
ct(p3V;0gs@k1'W=m<Ar4,hg'0ApVF%iO?Ard4R\ma;\`jgg_"TrrB!`)enX.3UEUB;G83Y'u.gLmTX'
&^!*Tg*?,LK<^c6^T9Nn3ts?jdI`3khnd2m(R$Jq0sPjJA"J]4S!pJ*UbMA<#GT1n=L-oOAGV2K/rT]4
B+`9knc$""/UVXPB*nI8ko%pGa9k>;:agCKcZX?5l^&]NAtr'8Z/pu`9>8_j(f\QNAZ7r0A>c)K@Ja2d
;1?5LZDbM-Q.G"paS")Yr7j>RW_S>.@8^u^j5\V,?QJ?$!r+>'bLgi!c.MT^*9O0>GMI(XWm8J?92,5D
;o8OPYpOhI5'c=s[??Bi2p]4HVqB^U>*$,\:c\.$^KShoJ'$>`^,KMbO*54BoQD:]rq]g9Z'EgBaYO]:
I[YH7:.'!.omn+"f^d6Nj=&+cA'9]H>CJ(!Hl9\UPt$mPQ"uAM!)L^Pf$XIt](p+8[D/6`(gF^MoU(l:
J#7]U%l:YaaCED7ru(?`\l0423I-'Q`93BH$o2k+A<*oSjh7u^RcAkH^:r#jkM5h-aPXa=382FM4I5lf
W3OV?O](V7;LKKAi#>V;Dj-K!1fqdsI^SK-oUC"pM"2E,oChOpVl$MCBOTEJbu0!,Fc/ie?.:HqnQ7YA
q:0qd;LW2dg5O,<+luf&h<T'11SgO(N&\8thXL6ODI;WD7"tI5aV[<XXiUju(/dog;GKn7^H,.6E_ZEa
9)4aed%pS_Se<_9,7qoH;sPW7%Rr%Q/ENZNV48Ml.F7qP&Y)!Wq-=tk8blY>f4D&Aa\mScG,cUCk&SZX
7(f%k`J(r`6H[E,0'-/\$LrU?rn@463*`4QrF[\0i5lKb&m-o/.>;\&(dY$&1?SHe3n$mEP83Jm(qJaD
T>asD9`._7=Dkf),D[WR)0t^p)0pNJ]=eIFg%r`rN[5or)D\$G5t:JeabY6D)*tX"UgTG\g=m+g78[io
<'Wojll0dWjNEpL$!^%kNbfeAc`SN<LO9+?d99A9kZqI`#&;R_$AR.oA^j234J>cQn1fK3OqH(Q1*R3Z
N4eq@&WJiJ5XpDM,GT@plRnj!A-=WBOqCQ]0j(9,9T\6s[2/^]"=Hf26lF+fe_49C&r180WW/3F9qJp"
,6CnORO;E[quK<@i-s?9KPSGlS'.0Jkc7/51\4!(<S[=P6rcsfG[nMd"7]Br]1)ppEsF5I3:Bi2gH?8L
AmDeI78-nFR:J$UO-[I"mp-tTjX7P-jZSckgfRX,r8j\NX/q5'"W_..&=$Z!&-W['rL,C#enZ2?cbRdV
c,4$tl;\fB.?URM\A3-KAfg%<LU'u/[;,_q?QOJTnD:HnMRq2Jp4t,kl8HtAS^6l>hVu5+PgEL0F-H(0
]t_u`)@]oOoXFe)l;Z%&HEACgC=UIe8C0b3;LHQ\O(c3f\M%$+g3i;]o_^(3;6tDlA&5+s3`QW2`>d(&
dc1eOitOB*)YJV)$JW`>2d1Xs3/&TO:`'duZ3d)(a>_R5*r1Q>Kun1q[^)::-4Z6`gtZ(E_a[&QPqA:W
Y4Vf-Kh$UDKHd/[KM@.4J`@8#k30JN(3)_V8f&YF;#>`1'OL%+V(j:0eOnCsXh==-X''AK.UTa6Wc;sT
LdLKX&5fAD77:RQ?cJ16ffV7S=$@Rm62<=;+^]"8^3$;.9ujUSkH\H.^\2T@XfNh;/EZ`(,#n3ka^3u-
ZFoXZPSm14FXQk*JN2m@h_Mj2qC3tLX%/-2Dlt7E``?%(i>s&Te8@_D]p*nlp%$>V'4QG5MK`2c(n'6g
!gqPKeX+2L!fn>g+8MaK[bTi,GWJ@QaI\+)?EH0n?&Rb]N(LReD%0MV656&*$?,9=FGm0-#]JjueC05:
d$p'A/?6O.k5nV@))Gm?Wg[+@Rh5Ih>9R^9<)m0@i[7-\NRaFRhbHO>JohpBS`h#[qsO+=7G3iQi2Y7*
$Z`SoIbm!D;20q^e>fkO,iX^"4q4(k0KZRhRa*)aNZ[!Teh@%LDY]9J$bMiXm%[%53ba]sB0t$.m=.,o
Bd`..l7PtkR]U;MF[se_IKEs0RD&M104Z4(#GR`\.%_JN9H925o-9Fn'=WHDb5#uBgOC7*CjDF#IKNHm
^r)q9;2lo2Zel0\3MReD@&tt1&e0nB7fAiCDlDcBH;N@I49jGXA,`!-]i%&XD2;'7phIR8S\&P<l>M_7
<,$a(EFlbima2UD7:c'0J^psL-tcMDl5o&GqE^ZLcD[tr)<D9k7L6*:*;cR2cJCWFb*gaePV];_-tgh]
7lbY14Na)EXaM&T-giFP!YJ@f,.9^-'JV^4b*eVO#`*=9e)RL+U#FBSfC33rbcneq`ksX,kLj:0!cMq'
'7OZYh0?,7XnJq.ZU^ra6ltFu*94;o6,2C;;9m+=.>GJE`Z&.5m\c,rqb[f@k/VqBV>ZJ8F6aqm`mi^Z
\#NdZMC[PA-nfu`1Sc'(Gmm$25L5OQ).\sfYt$fM4*u-?3R90-[`F_#BqkgU6L#_Kal]ocNnNt*'A3Z;
X7]:1$?Io].u\MW9MF"bCYe?>q/_+A7l`Zfbkql4;u"ND]s?,mkaB8jY)p3q?M,^ZN+R=!ZXp$m5%G/c
BrD\21GWQM2KG3S3R:/96:=Bi#:VQ%]PP;QrfYYEZZ>1Ga+i7aN,f-qSYPTX8(i4SX[g^gTSqR0a/83k
:*#7p(=b/UgDK$Glqu&URSh@X';M)<VIW/^DX_dWY3hcjqL2!5AM4D^992IWkUhj9-=*M3QNdmV;ilM*
*#r$DgMCcORqnG!+/EQ:9JmYsCY@K[<h&^Tn0hr=p[BToEOZo2L]>q/8KL>?@hL=KR;q3rMCZD:CPB-b
<YFIO?^-elY;DHJ27"8Wb1Z?2>MXr'`D#2X6#X0BA="S9*p$(t^aNi1$q<6irSm?U4g-$0^Xj$+cZ1eE
f'/-u4!4a8TT^uS*jJV+>34`nPk`BLYn"]s\KPM0f"dSe%Jmk@eqGuYHL<Jg,E3SK3dC`<Da7V`KGZTh
rK7e&);UlejF)?tb\`MVIAg;c=-E;"J,*1][t`FuOW]r=gglVp9%GUN=[NZ0UD7(kmFb3jAemEk4W3@)
.!pa,.?I3K:sN(2F)ulas!G[p\Z)u7_@NF=TK/8"X`7(=S;gqk7^SOZE>i:&1j7)Q_]$Mg2U<@L[7b)T
,\1$Td=`.G,PsOqAL=70OW7*k(J'l^R5fT&T:gY^fWon#XF3ZL&;Lr46QFdUCi0$e_kV=_G2$s%;79\E
gDbM%&&`qpL*ODB[.qF^6sEhG13-)tHfLr]Yj*=)$DJQG(^ZOQ&,(;;D"IDbM8.aP1Km574upg"Pk!g0
eok2/B)XlU^#_:pQDD=%kU]U6nL0kiNU/bh[3!I>U/_]t!nT6rE//83=.5ldXXkVcF.d1]ha>sJJ!6U,
%]*.P.Ti_cXb%RYU/go6?ZB+SUBJ>@;&;T0KD%H\la$?M/:3ZL<,3WNd1Ohta'[7bGBA/,+.HW6+.Lb]
4N\.%HJd4u\6DA.b2l^IMh[*iOhCE'-qC<XN^1uALEdX!^8,G-_j*a>.YeYiAl)a[.[t:t[AEgefeH\,
Z,qjja!RG,7s?6Ak+a/YBU.(sO7g<1'=N;Ia9].t$@?:9ncO*u6/iBCq._U=e)LhGWmf:$A*p[o1MLDi
A;I^`pN+-9rud8&^>?k4/+hn@E)f61jU9+Rl<B;]7l3tfCF*H9S;=+q_9@bFP=mKK,2thAB27NTQoX[H
l79h_SBSlpo!a`_a50e0Qnug$PBc49:q]B+b@li28b5S>ZhA[eS5\'QGu`UW!lFEM0YcYhS5ZAmg2`8F
Qlbu%%CSFJ!BZ4_N&B+ab]P^qIRHCEF&%),S=U6rU0S?I@kf1klBK9_%dd]?D6dTo&P/=2KKYg`4ljjH
%Z\Bs+1Oi'S2;>[RihW=1(rO7Mhp[?7;(Bs?<^U]7bN0td/tlT0Y;I1Hkq1^`Fn:dQ+Wi6C7ZdajKX&J
".hs*KVWh$5gB8V_If(r0+lf!b+S+(WBPi>pVBpd\C^RMD)O4/m.ut(BCiL]_V7/`B4&Jn:Qr7(435QF
SC!BK8S`?kVh'ti76BbFh1InWa3Eu'V-"7<>t+ad=B@AkBVdkj"dKeRrS[V1cF&'6Jb)=JcMgR2E9EfZ
OZqRR79%Ec(njP"NuV%rE\X?hi<<S_`5Gc'[O5@<,WjCbP!`b9(crtUGB7ZIqXRuKC8*tc%JSS/^Cf'L
rZ7T!ak_^r<$qDEES\snYo92HFQnaC)!%$LA#BOj\jbRIXX6G#FO=h\3'5c\(OcOn9g:IG"**.[3Q7gc
lRV["H=*dV0j4mo='W5A6D;V;8(>YsY$_0jN%oM)-i5FFLVMhtpa88qq?MEtIp_+4)deJn-$T,^%%m$[
fQeiQZND9E;AraYlWX5G`Y6'#gLm&A'a'2a(*8#?Sd+/nH32oK(%iLs2EFhSYZUDjV&jLL;C^G^91e18
C`:XS_K167K]f!3<kl(j.Aik`pekpP[,jpur&/pOL=-?Yj\)jc-H]U]]4Pg!QNAY[QVmq)#Z(4nJ:4VW
nUBJgI46MZM'Ah?&k*5$n>--.QXV^tj@cbCk(GD,V"i;_!ft/p%1KQOSZo2qkCm%C:Ab_`NT7^Ik-#9L
&$&2giCbmFj+1S"';+cj!6^_Wpa+H$"Z<@T@i0qR`HH<8B>;lOX9>u#HgBhX2pps)n@\"dQm)-Y."!IX
rod!(S3csK$>"n)'\HADI4;D1DYd8NV6j*Oi/QC[V:^05GtU'W6,&T^C0d&/bgGEOj@`X=C29C,&UZ$^
:A@_?2snu=1O<tl?<PA[IH4&+PugjB5kFKA*<%iGBiL;6_CCej:hT`!BL_eq$G!7d%fd[)1JmjE`66u;
FZr>["3c/BHj*$6RcVX2`VtWG`V(2+](BeJ\qD_J;YCSLN)o^O#2A33!=iJZU_L_51>*"Ab+`DfpB)s;
EHi1&VY>kaS!D*"&)V`#oAO[r$L'la$G7?kqFj3SVq8chY'bEHgmIIIrg`#_'_k1Yns(1Y>FY6Y$"(h%
%ik<u@5W]k`D4<@^6H_tq,2r,E<jJE9@6odmA?.ZZ][(SU[Te_A5'_%7=F%VGBn;"Hms6Mf\oJSoVtoS
?'=!8jQFD&=#h4]FB&`mrBakK,R:ps>*a`)=l?!CfpOd1V7KqS1`pPQC3+9cTeiq5b:8pAUqYj%;\g0u
obTiHXo%St3;`##`8Rgq/%d\7-2]LfhW<0Zn%ju)3>u5koYtR1[)=ro?Ddn?.On`lX`j_je!Cd+gbUcb
HuheTR6^m8&f#s6\P8Cg76Oc+g9jjNLcIYL2`$!.Pue&u#M>V0`9NL(c\+MKgkHCD0.t_#C:1XKr_':!
S)1ca8!C.;7ZW(o^Q)0l`r%;).<73I*Uk(HM6dk%Q7i$2R8j-Q.`lTo/*i%G>scR@<VXi\*a!uF6Q16P
m2-:Rr:;F3lQCPa4BG##roB11_I7;>lk(IkM&[]/hPo>NAS^SY>jq[;Pt+2ZiCLd*5mMjo<,rJ6[,.JE
^HrH57dlreb8[r)cYdLHdILtM>q,*i@N.`R6^78n$9lrm3U8QEmn3LVW2EG9W4PhA>5Le?'CB_(G$s,(
OS^\@!Nr4JF;*"3f;7>MGbQY9kn*:eQ1S'Q:d)tn"iYJN)o#u"0$L2mmP\fliUm&WhU(iH!B9mcDiK\W
EVdJQmtA+#Ar_1Jb]r*8T3ppEIHgY$Va3ebnd)L%*]\p8@.4@WGE9CD%'ELB$#p=C9jI7DDW_cYHhP*U
`N>H*AoG-J/@_CE=a=O2l5e[8?Z@!hB:&=THkUZlk!e3TrD^_=ZLS9J7$$3U3KX5(N[@q2m)2F"Y<5-$
;*Q&f?Vr7@X5V41hN(_ZiR=kjEr:$jo66E8ibA_Q^3&+6)@W4ecu4792?Z4[mI\A9<j0FEXZDk7!4tU^
GbUk?G&bZr!KPeYhWrj7pf1qH@?6Z#hul[E<\$Y>Hj<_gfZs+A?5X&i43#mA^HLHTcEh"0hZ9=)]@l9V
3%?6I=<eKFY='5AP!JM$&+.g0KcSl-Z'FXtq3!-&aB,*baB'RUmsO"'m-W@7neHS_R/`:i,TQYQE81E>
=$ku^'&jaXjda)*pA+17nWpB>2':=>S(9/kCc^"_I)FoF1f@\_?T:ObfRJ)"hmYQ2\A*Nlq,O,&oCW"$
q*'3;rFJ/n;$FFn)tdb9C-s<_oGL]P9]JidXRgV`,]`])ApuN7P535&G!GOd2X)7=GM]$uf\a2oh_jH,
V;K`T#J[#-:2*R+#AIO_4X01UAiJ%`]P.o$h-.HMkV,QF:4Mb)'MS/tOX]_/PH;KTCPY'nFb=#cQfbiA
iPeIlfT"/t-q:FcAm29lK<\W-+hfn6.%/!/k)hMTq\tq*>%!sorNb^9j`R_<qJ(<KW-n6H]LJ[hHJNga
d%O?u%WZ:3Z'i=TH[4>7GpfigQe_,&qJY$T"?NtQJYd?Um7nel]H=m%=EO+aLKQ!c)F!6BG66`Qp/^QO
&P&KWLuJ]CljE@sf0l[_]t*,.mX+\McWe:"B<Q7P"-,?m90IC]F+taCRksE=hlG_'ML4!:cO=R(`7i<0
p>4L[VlWi"3T-ID,!"q.j8ACA@%L`N\5lXUWO9X-QWhmmQhkGUa;_j=2u)=`%FRI`Wd>@s8@Q6aWKm&q
50+CDC:@<n)p>p($PZo^iD*0/1bHAiMctA9k7da8IGFR?jJC`*,03E*`&[Hfb:;(FgB0KaZ>L)"9.tE>
4<^/[\e]&03<HaXg@=_P1SuICh\soj06QhjaJ41ELONGol#HAsN*K]S`RXTA?1N??oG/&+U@.8adTbUY
q:"n49:*LgmAK0;kq=]tG,X="=[K1Hr%G&BYfF=hc<Yk?FHMpdatp=ZMEBa`S&7>`jbp%;b1krhUq>a\
U9rkIi=Q4A:hc?SiMBl&`[\R@90LBMc+Wl0VGc3=I,q+>P%L4FO7Q(N"o0][HgN@'nl"qC:>)=jIX&5W
[q\.Y9#uF*WNUL*XJ#dae0u9<?iCWjob_UC0X5tVa*rAaJ&C<=o2>UJIJ[rqe;r+=).%'q/%j:@2nlQ+
o3Tjt)K.3e?RWS:O*$@-0D-P'%oVfh#DE[i\1n6a[U(C-q(OZ(fkM@1l5_*GIqO#*e+i-j*VQr.PHD^Z
nTK2WX^>"5l=YnA+F0ri[F.]b2nH*Bf27Kg3-'#26ofV_$8498+YP^O/2-Z+'I-lAV$;r!IQ$%Y4Hj'k
4MHXr:\7Ya#k2jf26hPGcRr\.\9lg9`Qc&0ps9s\XY91Q%/XW2XOM<u0#$T>:==O'G8Z+3HgaSl&Q!_O
%B&_Y)X98o_Fk=h0D4K&i2,;+Aps>>?Ku*/QIc].$_Ln;B@Y]qHJK<`^UJ62Mu!6_l4K-HlLXr$90G:f
nkr9+PA^:mUO[7[%<6@k)Hh[S+SFZZkU'*t6dJM"dRuaXX0keGMVi6VDpk4\-.Qg#O'mf%[+n=AfIU<O
TBhUb]lucGph_\J)=pbPB"t.k:!h\/e/6Gf)h5aRWuHPPg8u#So>sY=aCUY1H16o98:uQUV%t_.k)]d;
_+Qq.1i_PoOJ?dTY6PS@s%hdU$*K+cEaJuUVS,L.mRl.ooTV_o(@Q^GNF$@/pJUTsHDtPbn'F_Pn$n^Q
]qC^<MQb3_rk$+pc9@tFn@a$++0N-D2^f9JnMg9FAd\+4>C=;tnMdC@ET:t6?g:cu0:-SSJ_p#scd"Fo
>oNP!ps%K/(`C%R(OE&P1\XdOPf$VoUD/'@?>:\Y;?K^@WQcf,El<3YCGJ@42<BX#C<E&/K0<&X414'B
--:Sjp`!K6._-7V%aS/'0?dJQ6<fqo@uu?(>o6bOESnk8GRp*!U/B+XQ(O*VK9IhS$`EafpjU*=h.V;V
I%E:$r!VY*)5L%$[i!qXX5qe/-1cgVCUX]BrLZ(,R(BQjre)Tf7>'WIRiYVbhf,SbhqgL!Np$5O:Rl`Q
4!dCb5_X>jl;1[@=?!02Gl[SX0(jlQJJLiSO=b3l[J%brP)ZM?>?0PE.%rM9URnuef+E>gpg"K\,.)/a
O*MaV]N:Ld428CA'd`g)Hm^\#^9fJ=(`'`Lp%3r%NCrZdpp`VD?#n_n3i(Kj>r(cSFq#-EW&F=pjBXP)
'\o?B>hc:9FDB5Y:4:geC*:t%VH-pOQSO,8mUOFuL<9YQ\ZbDdlchZ$bVoGjR6V^`+$I?69V@C2I+Yp5
H8jPD-i/rp&=GckcQ\(Ua'Xkb`4/*UWL5DXfhm\)/NW39bM8A$:gKYtR"_0bZg_=M%5r0RbOk#k1Qg;)
`j=2GAB+s7T@F4VG!WbaKFt6(4QtErrmJ'=C&BTtf5dr+GntkLh6Z&YL\]1>>OT514nrs%5!8O3[YMKS
<N^iGBYW&.+U'0'MoA9r/,&(CF^(VS4`@QL<il!>:u'V5?@4DLg#1pa)4(1B[b;VZW>:_/GfSZuoM.0c
Ojfpp._3%]cI[CjFk\7[?>KAC<BBCA07R.D97+X!j#]9e0m,tmqD6Ish9r5XdpK544YGe_kf'L>Nj!?G
"uQ#*)<ni$F(8&tf2ht7BSH0>^4uR1/H#6]*`X50(s3_N+!+f*rk#8&]/\5m=no2G^s'ZFVtOuqKeffZ
TpiVj+WF@+K?llmf?SET/GtaC>hqU0d)5I)-&&6?gSJ2bFtD%&k8e/8`Nu*Tm.uF%pVrb[DX]OWUUqh[
rW21cp\,1XDXcmYhjA0po<#VWi)\[llf(IDP)oMN%FL7#8)3$d,fhA*hS20ko0"%@(FDRV*[3\NGg>:-
OoQ#Taj^SAYI&8-^OFP-IaG9=#$uPo%DFY@s%%#_o-n3c@/7#2p(-KH-SGFlV=O*@gOF(>X<uEH/X=+'
.P@2C9R'&hCW\gHFbEI*[]$uXg9@OXZcQr\"_OF+:FqG3b_Nkmc/2a#!jd<_WWMdP&ZEIpm4<O1X>as.
TUP0UDrL/0].,L=^T&b;RoZ[hJ_>-;<<_O.DkdB?Teo)D<FE7B3p#_A/LiH,)QLR].]WXB$5=_$DQ+oT
o*NG6M/,/5=>nf"O_/t2TEoLX9-pk.*S]hlW>;d2D2V$.VJVde.,3%TWT'r2Kj)u9<R9'-X+^LnnND3e
H@6Mb;3+*g<FGfn@EWQa)fAN()-@W@bnAl4J_@,r/LiG-Nkt(A<ftq<J$'VqeB@5K\*]D`l'Let]p7VV
h<1)M"QYlk4Fa`XKBq>kfd-g/<-C?G='@*N&/Xr8=3VB-4/mFL.h6L+?%$Xe,V>:)[k1cXUK:VK]3/+5
>:P!]GK`t<$[[P!R"h&6.NNHa."e,=H_G#\lM!n61Y@"@l8iVHQ:d+5eRZX;-AR:8!KWr">*s!XmNDRs
<G#P?&+(5_.POXj7;(aOE7AH*6Hm)WCe?0Ra0]K>lKuhnh<\%rC3Hr'ZZ?A<prV61'&%\(@E9?:0OMu!
.PP5P?;#M7*^!S4H5j_'CIgGL&&(O+g.(I^)g(I!n\)8iKtOf,?(e[^;G"IH;V;:hW0pgYDd#FHfC&Rm
\B!\Np[i9QriG;c2/dR8DJlH?NBD/B%jY2nFb;<R<0G*qJbb0:2^j^:!#:m/<_0i:,sh4X_Y(,%.p.pY
d\.Z.;G!9TjjY%okj>Ll>?)D@#M1lYc/<dqB*4="bbD7II`U:*g=j2F4n,r@lB]Ri3C;HuLtttNkB$XD
q`@RA?[p!khmAQah`+aga!H4T"L%mH)lN=p7W>>*21eJiE!1PP._%B7qh<Mr7smP[kjeTrcF2:q)H#f6
]@L=f)V?83-b7C,^=BA[>(7!a.Cd3gft]2#cB1uiYP)^JUl2O@h5p;:k)!mP-(<]Q.($_Km<;uDlS$JS
COK<rV=bXJ.+J+::04.^0q-JccP\(:rmD:d':l5tf:qsmXhPWq7`[m)\1^`[fgQB+X`'IO?+X8<c_U6#
<e80BTh2Fl?GE</=3G@\D!oaJX&@,J^G45*le?h-fD8-U!4>"qC],PBYBrnoZp)7b>(=)SDdV?gl?2gX
[JoIX,?b?0$bAm4mh]9.p"/92R]2U\^>mq<^SUT>eoKC=95?kt6#UN:V)9B58+!DN_:-j/U^ORo.I:6q
m``5m?W]gtoncr>"&%s`_Hg='=%_'?PTspkb+.msoftf]Dq4j%E9&fjI;/q#I;01*rN'@N+/a*c`k*b>
IWo@=^P+.U9fk45(PTfmmRl#ea?$/j)fDQQ9!I`dPh`i&jk6Aqf4>oI>t\f\gseEU4)i/qS\I=7Zu=uo
^j?j-b4k`=A8.*X].<*-DPpH+KC3g1O4M8L><@=T)l^cf/)+,3<WpAka1Ek?IWoB#R:6E7Fa$:3M41<s
/[FAM&"U#2hi><!,rC^C*4n+BK#;N,@e:hom-`$M4ILd#FcmKQAO!DG%SQMJ?/iOpiVH/Mp6YME[o7#a
Nig'"Z+F`skhpYd81!+u+U*.8-1gjAh:@1b1UA$?pa>MPZ"J6$gf][O,<ibk"$W<*6YgCq1IU'8HW=:k
T&#Wmb'Q?d74m@B4,hdtKLF`nQ<(M`O@Ngpa*<>6&K+Zba+<D<EV#"QNE)&Pfm@^m?Sd#qRGBhLAp1LS
QSGE$A&]%/X4p76e%sd2BKLZI$+0!1*QBmBrHcenB.)d9@-H_[f+iUEqerurj3:f_.F%A5l@bp[QTF/3
cd(`Xiloaep8hj-XXg$6/5rFZkK8D`)+;3^:!L*1qh3g(^<=dq.c9@MGNBe*^8/en,2_M)H]KtZ;*:Ub
dNN76%<K,I-EMC5Z-ce1FP^QA;VGVqT^^NI24gm;gSFfH^#?*\jL;u&>qL<`9QHWn?*:8;Nq4Uh9]@SA
Bt1S=3JT$?A`=ePe;@2dcbR\I\_(QU]3O&S'_MgGMgUkTh^og6bH'\Ag+i%Lk;@O#Hh<\iJW@6h43`(N
"%BrIe:2d[okrV&!tHm_DpTjA/TO'lPh5#k."VHb>[tHAE8`'W>4^rN`j>5Z]>[1c"i6CY..Ge3Sn.0j
/TRZ"c<Gq8mg^f8JUC`J7`@FeZG,07[6tDq)E_RgoAjb,/]Z7,lOEe`3iFs/W)Vt:m!SNt[[GGX,7"WP
18mblXBTU3ee[+sHJ*PH:'R%^-<mdWBS2E5Rj>],``)f@o.,-*kBBOV5-%3-Yq(]?OCR[T4+E*%G'^@\
#d8KdZa0!*.Cn0-(6@->'#p(!Q\E%W]baYr=$].F.+.cZe,t0>o4s?b)*%%>*M%plGW`>2_C%I>D),Vf
iB"<N2d'cE6j5F0%_8nD7GZ8JXgeV4'aj].9J?:60mpCV5KLOpbt'd:ZjVd#W94[NB-,lY^ltZM<,a^c
^`-J-Fb4o[obfWa]6*WpV#JAJRNCUT>)tKTUG&9EQpCc#!:j!+"t.+3Bmr38X@r02&'Q#i3.6'P6-E7F
HBU>9`2s4]lZm)*Ffs_PZLO[c-"Z_'oC<s9I\5MbqIje<ICurO`6GC_#FtWm2eA>b*/UF'i-LHiCB)F\
NfNLmA"AiV]B*/&6BYjEjEbQIpc?m,q5MKGK^@A89l7"<rlYZP9q]XaN>e_!$7X:Y#t<snp?(]kMtn;^
V8N"m*9*oEfZ?X^I+UGhW-kOD5a;k8:-T+2*kn4.kbQbkU!?94g:>ria1AbnnWmZjUq5Anneni>Nd6(V
q%15sb(lOV'6OL@2.n50oo9aoV6<bX0'R.P]aX9k>)aPb<Hd0^Qa.du]nO/$X<2__7mpYgN`^E.$Wj30
j1S<`d(LakbBp&=L=0e9I->3mNpWWLG4(4ZhG4$1YBG4SH:"V%@FNn]Pn/^Sq65D\<;@*+B2h(5QW3bl
go&k\i74cRLm,;OYsPJ]@H"PmI#,*E.j'qOXF+,'TB5?8V57>jOADqJ+l7<gAu?eP5/_3/b(l8.U:+Ll
Z`8E*?EG(1EN6l*X3-4gkG%O@Hg9#f7--f'D,g>8CR%V4?=$VGF/@$79d_^iagIi:5:Rb@cn0uEXGU">
f]uO6Yq*'3WEAnL=)0rPN^'f^e1^hJ.j%gZDi;plFNd9R(\L^T#KsB;eCpj^;#*q6D`A2ABXXeEg`8]L
W-m7T3gBRAM>sp)L]=O8Tm)%4+92/skHDr+ci7(nci;1thZ%P2&S9:&Bm0rkqo!mK)=.=tl[Ss"n\>+s
If[Ru\1%~>
endstream
endobj
7 0 obj
58816
endobj
3 0 obj
<<
/Parent null
/Type /Pages
/MediaBox [0.0000 0.0000 731.00 403.00]
/Resources 8 0 R
/Kids [5 0 R]
/Count 1
>>
endobj
9 0 obj
[/PDF /Text /ImageC]
endobj
10 0 obj
<<
/S /Transparency
/CS /DeviceRGB
/I true
/K false
>>
endobj
11 0 obj
<<
/Alpha1
<<
/ca 1.0000
/CA 1.0000
/BM /Normal
/AIS false
>>
>>
endobj
8 0 obj
<<
/ProcSet 9 0 R
/ExtGState 11 0 R
>>
endobj
xref
0 12
0000000000 65535 f
0000000015 00000 n
0000000315 00000 n
0000059559 00000 n
0000000445 00000 n
0000000521 00000 n
0000000609 00000 n
0000059535 00000 n
0000060013 00000 n
0000059729 00000 n
0000059768 00000 n
0000059870 00000 n
trailer
<<
/Size 12
/Root 2 0 R
/Info 1 0 R
>>
startxref
60086
%%EOF

View File

@ -0,0 +1,348 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.23.2-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0"/>
<node id="n0">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="174.17919999999998" width="273.1071999999999" x="1000.3040000000003" y="433.61760000000004"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.380859375" x="12.06892812499973" xml:space="preserve" y="7.247609374999911">Static Pool<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="-0.5" nodeRatioX="-0.45580882479480683" nodeRatioY="-0.45838992615076934" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="120.0" x="1141.8400000000001" y="536.1087687499992"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.142578125" x="16.9287109375" xml:space="preserve" y="21.015625">1 x 128 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="90.0" width="164.73919999999998" x="1113.1776" y="451.01919999999996"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="164.73919999999998" x="0.0" xml:space="preserve" y="0.0">Group 1</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="15" leftF="14.739199999999983" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 1</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n2:">
<node id="n2::n0">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="60.0" x="1142.9168" y="466.01919999999996"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2::n1">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="120.0" x="1142.9168" y="466.01919999999996"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="20.746093749999773" xml:space="preserve" y="4.862813159989173">2 x 64 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="-1.1657341758564144E-15" nodeRatioY="-0.41895311400018" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n3" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="89.99999999999994" width="150.0" x="997.9168" y="451.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="150.0" x="0.0" xml:space="preserve" y="0.0">Group 2</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="997.9168" y="451.01919999999996"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n3:">
<node id="n3::n0">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="20.0" x="1092.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3::n1">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="20.0" x="1072.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3::n2">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="20.0" x="1032.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3::n3">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="20.0" x="1052.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3::n4">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="20.0" x="1012.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="8.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3::n5">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="120.0" x="1012.9168" y="466.0192"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="14.814773932043863" xml:space="preserve" y="4.392671444094276">6 x 16 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="-0.04942766514963404" nodeRatioY="-0.426788809265095" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
<node id="n4" yfiles.foldertype="group">
<data key="d4" xml:space="preserve"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
<y:GroupNode>
<y:Geometry height="90.0" width="150.0" x="997.9168" y="521.1759843749999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="false" width="150.0" x="0.0" xml:space="preserve" y="0.0">Group 3</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
<y:GroupNode>
<y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
<y:Fill color="#F5F5F5" transparent="false"/>
<y:BorderStyle color="#000000" type="dashed" width="1.0"/>
<y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
<y:Shape type="roundrectangle"/>
<y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
<y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
<y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
</y:GroupNode>
</y:Realizers>
</y:ProxyAutoBoundsNode>
</data>
<graph edgedefault="directed" id="n4:">
<node id="n4::n0">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="30.0" x="1012.9168" y="536.1759843749999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n1">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="30.0" x="1042.9168" y="536.1759843749999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n2">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="30.0" x="1072.9168" y="536.1759843749999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="13.0" y="28.0">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n4::n3">
<data key="d5"/>
<data key="d6">
<y:ShapeNode>
<y:Geometry height="60.0" width="120.0" x="1012.9168" y="536.1759843749999"/>
<y:Fill hasColor="false" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFFFFF" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="17.96875" horizontalTextPosition="center" iconTextGap="4" lineColor="#000000" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="78.5078125" x="20.74609375" xml:space="preserve" y="4.392671444094276">4 x 32 bytes<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="-0.426788809265095" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
</graph>
</node>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>

View File

@ -0,0 +1,256 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD>
1 0 obj
<<
/Title ()
/Author ()
/Subject ()
/Keywords ()
/Creator (yExport 1.5)
/Producer (org.freehep.graphicsio.pdf.YPDFGraphics2D 1.5)
/CreationDate (D:20240209121153+01'00')
/ModDate (D:20240209121153+01'00')
/Trapped /False
>>
endobj
2 0 obj
<<
/Type /Catalog
/Pages 3 0 R
/ViewerPreferences 4 0 R
/OpenAction [5 0 R /Fit]
>>
endobj
4 0 obj
<<
/FitWindow true
/CenterWindow false
>>
endobj
5 0 obj
<<
/Parent 3 0 R
/Type /Page
/Contents 6 0 R
>>
endobj
6 0 obj
<<
/Length 7 0 R
/Filter [/ASCII85Decode /FlateDecode]
>>
stream
GauHqbDmmZP2*dc,(JOJpjOd%&g0Gt.kP?b'JS!oXFWk;Im?:Y1FF&?pX/3EoVU?*%+=B^)P-:.4Rk3i
s*]RdT>/;PVD+YBq=ssLgOFU_/,o=0j/OX---Sln^>2M2IJ`Ed^O5qQVk*kY?bcTdDuVnJr7h6u29C@I
n%\htn,N!oI>bl:q_/!E8Gq*>`4k*>H&qp+S%%gYRm0Q`^J"46=8=LjUt,*E8fY3j6_j1O)dT5Rr;36d
2TE1sT$toUNk^6OlG0M$`a8\SDu][+2SjlHja.QR?iT*F^H[dDqOmlY/O#F`o1-puZR+(6c_!Z$9=T]T
3D@>hs2JCF[r;<4>?W&7(f(C?hGX+g+/RK5>#oRYmB]N&/Qlj"0&n123'X=7S:R]6PWlg21E6kE\4XeS
2\[`<&[K"H.d`i7:KfE2]p+JeNVt]"Y*L+:.OV5^Fb.U2n0a"#gC<8<]$kU=OJ*NV)=du!KCWJ.>a@;!
AiTJpf@'RJ7&^"(Yb"R/l(pRdQ<kVs%?8)!>V`PO_WeB@PWi!pB+5<W>7FTapG>"eLU:Cra(k/I;;^ah
mAcX]0+W@c9"cLWCDs?VR[8F?6<T5F$_0C:m"MjrBBs4k)B)!QUC!)4)NFtV@<<r9q$g4$m3nNi+q1ru
e]XeUoN<]O"rT6rdIc7nDrJXG_<,>V[+c%pmXpGV6L..t<8>20'_?%rO!N4JZZYrn7!-+)W,<%t>ADS8
/TJAA+-bJKcYS>'s3KC9l,0-0e5A(AITl4'3i\f46/U?l%$3[[D1-a[<d(=Q9X^_,!@p9Wm'TZV7GRh3
rF)Ja082PP(B7n)^+A+X7k=ASO*kK6,(6!VRDEO4*]U9%D@#R--_LN6iTq@`,YPD6Cs8m4P?AfT*5Gd]
[;(LpJ)b!=C%su^D3#$WIOeISK@*am.##XK[S[_P!?^>tVXHJ#qEt3?2Z8-=k2XWs.,17T[bN*>#j]U:
+n?FH0^6*EPO[^;:H%KH3f("]U9q;P]"j#B)eLQ$@1?"IBSn;YH6lYb&!tVhGK>8uB'7MF"FFfqpVGc<
e,4hFfV*0s,_P,I_QT;tW4=HSW2;Ra[gBM4`qdES$e#Amc(@.hj5@*2oF=>L%a<mBs"l[h;q(=d*OOlf
J\oOmWJbKB>#1\>#4"cMCBF9nZ_8(\3B3X.Ce5M5@GC=9ETuK##3d.F9`om9\CVZIc4fpE_>+:BO<J]+
H82#q>$E&a":@K&ZD/,"^/e9SMaTXR4:emV+:YVSV']$J([5Yf)XL^]K57i74uu_XpD*@olnX,&NO9ib
%&N3p%WmY0V3,o$NEeT^eb<HbXfh=$96H48FpusINN>n+-?`G3[[)E[c:Y2<_OIriakDd!l>kAQ%VsAW
AF8/If$8]MoQQX3ZSq>[Jc02#p-mQXojmFp?*'U_akBmhOKg^+ZZ`MGY*I2DWtB:KeU(@CG1Jk?KA87s
@g/HOM6nM9!.9`I^:<BOrbA\h]cSgEhCU?uURj'M1$c"YMq5^c](iQ#&>PQ:2.[P-3c2mhE-_@`J$@fS
Dr,`=a@ib&>8l*Uf6+&oBsDgN[$uSRWT9WU_`,ciCF7Mt7gQI=T/'sF(R6T"e#_EoZA5)r#HW*R#p`]_
;dE]PNpSIdg\6CCQ:6F]TV^1VrK%68^6q0\hmJKElGB.7UIAF+V^67qcJ?fV+8sR\g-Z3Xb'QW-m65AL
UC0S+Tsnq;>ibcqXPfO;:H"8jre($\6sJQ*T!GIQ4Lo&VH+V6@U@aE[<9K#^&i7s"S2Z@R:6kOt\?n?\
@p8!#:7CKqp!R+&Y]'#C/Qrg2RMVR=@b(t;`foH&?N-VC;Id]"Fln?G<NR>6`9P]52`L$-C<*l4plc.7
4LX"1<nT+Nr-?OgD0lC<YFkX:nR7D1lb[?95#;tmOVMWgf]B,h03"K'->^:DU1!shS^`%C-di:.>MJtE
:J,\\RN)(L`nK=]CO(1Tdmj\N7gT%#Z1B<^qo0QCdR_QNI?m?+,6&K!4fE0TH4Xp3/#bs@]Ql.@WaMU]
BWhp*L`O1NZaDS.g0J?#[[Y>lJYclJ%HQ=qZk].NXgD>/K%5%V<?,\IWJYSHNOHKbeii>SSm3KlhX7(m
EbVLh^[9V@pJ^+\Sq#\_][o)h+u?TPo1!NNWGPYC7aKRZ"@1!5)Z=%7Gk!')(2ETj@O9&6$UQ$l,W5#f
q92>l8u!Ra*@P4\#";duf5O:q?Lihd1dQS3oQCa.7'^3sQ.A<TLOPreY-r$(%o1.R9pT8^-Ke$C46_+/
`eS`mioh5qd#K&0oVC7:jQWId]7!m5,a&m0hQ=c"ZPH0.p1eq0dAGT7:YaXJ`ZN^=N>&k8^p7rB.2tdh
>7]pt>(T/ji2PU,j)3m?DJX0V4aUHr-0P!<FjKu<`jQ5I@g'TS-p/qo6bA*h`@>K!O@WDGNGfcFPDn8k
]N0g#G1lYPO'jlnkp+!`qs>Ub7G_G4*lj8smU>/I@Y3o^Fm>;3YP,@#_qiT]gE-XV3s"W]nb.b<p$Y?3
@p.AZZUN>E*hXPKbOH9YrARdUTCN!PnOqQaZPu(cT3X,pj&";:8,VKDOZqD-aSKhVqQ$O/25[h-T_6]R
C9+8bQB+N5[;fCAnV-i0_0PGC#EHtl39hG%(g^PAcZlqVGQ/U8AnYrZ(Kg>P)i3=ig+4SW7aRXtd$=q8
L%9Mtd$HV]Gf>!pdr-2@flXdJ(LPtj,MLHnp\kW*SaaSJb1esgYlB$7O7W0)U9S1#a./*sn^kcE-_o=M
I85TH:.DQ2I1D!Gi$Z1lT>/6I"-Du2cT.EHFQ=[6rO+36h85+pMnhi2=P5BW=f<9C4LP$c2Li\nn2Q>/
86aIt6etQI5`[KZ2E3b_<i]HljKMH:KY"CGZ?V7LVr;CX2Oif;5.FFdQb;jG+0?nF_p@U)<,M@*<4`aK
)7I3"r9K'"d!+\r*2V?N3SEg7/[&sW'\#jaP1H<_Znr1[lc&EWhnM0pMdc"_:8QfFUdiam=V#(qFVU2P
6`9k0jf`3QgpUO`EW+<g2@2a5%OX_YV6HW:\If`r(V&+CI%c@aW^)q#c$?X%X2Vsm_OT3YPd"2`l=-kK
Das:U\!CiR(YnG5i>#L&.%$!kq/NSCgUU3Qm"p'\LpGKB":LoFAXH@$VMV`V[,8V9DI=TQ(@-18S2PtA
#p\$$-S5\jm7qD$]fs/,rG[Ujfqmc#bj$fCNm`;<$&dI'B'Vt#8i)P7BZhobd:A7W,=T4K&,FXOip8p_
LOQ5mBJ%hI`BU#`MR>[AM[6@;S!hjY_\ScsBmPt3:"*f2Dc=`_qlG]U3EaMtbh9gYQsI$c->q1Tn^X<F
B^MnK`hQN!$r;KHG.U1*@D,\s_idP]nd8k`lcJB9?)Sci:d<<iAk*aB)c^K%1m?I&*P6=t5d9rE,m2_$
Vn<<o:eoNRDDM*'FcQa361sg4]H*<Ip6FiBg6!taI_2m/9DeN52='`Z"+09,F'QE1KsjB9h>)I?UQK\,
Up?#_#P95ma[2];_gZ=I=Nu-1=+4sjm!L#3.`Ou)]!rM#B/5^beuNBCAU0$?f$DdgQ%CA>f7M`$=0J%X
#SL]o3a$TV'C?#TA`hfd@]!MG@]tt`ak0Va-H6NQ)O3e[.sNgi/Z@KEodYrunT!k2)NU0T2=8&]Z&LD;
c<'Bue0<IHeB2N,>,\p9X>jgXQ&<0+A6*E=(@bpE05=Rp<EU4MM1k"f?*Z'J_JWF3?gT?.Y$SZE.+,Ca
JE/T+Z'D)kYS9lJ]rKo2IL8aF+jlujC-9UUnMXTFe4`&:/f&qAB\Iin/1n_3=D--;0Ul[BZ1&_8Db2^b
['Hk%40co"!"29l%*"Z3rktlWD2eW.9%^ZRNk3u9Ma+0YGen(&OLn,.GrJr91gVYDbJj.Yg8UN4YgmP3
Y!GJBV;Jg7ITqVTi[KWJoL/WD1pI$.H@n./I[Wi"]__D15CcfGaQP!u1TR[g9*6Yui6*Kp(MI7$T^Bl4
l4#-LRH0"HG"N)]RB]N\G0l5^ge]fV#>g27[#Sj(5X\1A7I["a?*hQ&War9\^@fsYo-*5J)#@>mOb#;?
;rr^6]^*0nWSp<R?kd:ah*%WZO-=Wa0k[Tf`^=_Ta#4<ck0d5oZlbe`(65[J6f8.>=Y/`2oR4nkGg!L3
%J;@oKhW'B3<Gm]mR*t:^!&E,Pp]&mJ/lP,])<P^q`ktSqPu_Ah:VB"opQE8:i>@QNd)%R?^]@8KY8]$
.r78lfYIc>Z5b_;0L8GRq9"\nSE.(2XlM$>`^k0:f5@FBorM*W69'%!m.skVQ$M@lE?!AMY-NVO\nH:c
I]NFkbrYC#-2CHlUBd/88?o[?IRlfq>0bkMj&cg6/OrroTlBHl3H3lDAfm4L&<hpKl\%T0QiHDJP2GYt
U4`DcUq^i\OV:fom2J'(`3G#k[*W\[L/r8*/XHqTASA"9I+f,)`E[XCaqGQPOc=m_YU=#U$X\O5;D-bN
)KjiEYJCg\-^#-k8sCYcRC4CWSN<^;C2_WT">0?Ci>16s["nQ],:-2g"!];L8L@r=lHRtr7#oB4OWs60
q1)H*ql\Sf6AMaE!paTSP<K+=347A-D?f:";sM31R/=_i`c*-TWc/n!ra];sAjc'/K%G]7C_t<Z&M3N\
o2O3_Bl#c^,'p/l2gC*(nr,[5&7Q7Yns@;$M!\[u0nNi882R-/,!R5mP_*HroaS(^qChALk+Wk-X)r5T
L&+!bYbJHM\<[mH\6b2=ZQcl@#jk0<SaoR\E`Q-QN&@sqofX7`lNXo;FpmT-hCRaF]Ma><OaX=s8_V`T
]d`0A9isrCnq?=ce?6$HHMJib`Y-?ooV$!Vh;1M(#kM0aQFH``Rk2hFk/$UJ[U'l"_q\sAX\5RrVE@Z^
Mk1`ZpI_NpdE*>q]jT*IDlNbQDjG._M'_?Y`&ZD]98h)+.paUXcgoPB\@KipB8heU`Q9djs)G2Sq9W]T
;!cin^gFr9$)@J=Hi8H`c,WVn[9%;6O7i2O^*efCg"R1`h]1Y;,lHbK%)+[AqJWJ39CJJ9qBI'trTSA$
SK+&//4\jh6tD.*^G[^]c/)S#$%)V_mk9Z2Z+G_DK;)r/='roX^-%4mLQ5c)]S2rg*Ui2]A4I`kOEEfP
rP96Qn+P%L+/mr]/_^N\Y.DO?ikpHkqrupaO,_jm?!tiu+:HrYnnnmO6a`q30V$j_(GT'QV)'H6A*ldc
]n+r.`R3,V1mAT'D>#PR[kEgOTr$k+/H):Z7Vdi2^R.pdMTcN6WlRK1T?9+C,m@a`P-A9`=38dnZBTZd
cP=(*]@"kFZb#iW6#VQ4_d\oA&(DIWh&*J,mg2-5Pp>"RL,A(Zd9eoZMUH5TY[Zo+774bhDLhCRQZiSA
Q=Qr+h0$%kMf@YrO7f&KrOCIsG@W80QL#&Cb?3Jf'#W#5?RR#ZMAYSa+X:q8PbO[]5KbWkHIi\YIN:MJ
bPjZfHf0IcRJ\7Ioj=lmHp7QL118I/*6;M[ZcG.!PK775p!*trXbdXWmgIVK1/'rqmlP?%X,0V!fFZaW
pAG0$FOWBnG[M7jBoj-eftY.@*`$C;leS_WVs1m,cMY1Ko"+c:%A)'?1\jOeY[1U.l-eL`?eCJ%D:aZ=
%1tOj_N@\O"pcgtpjni0>?sfuP%g;u*1LRhLU5?d="2]P4<Da`>A)#?>K]bs8g?;P!F\B1SoqS!Vtd_+
+KXRk,Qm>c]!^n6[9Gh^q^+A2WmWG)B=8a0?9-NPrM"IH^MUs9NV:R3Y49T!?k];[]8upK4&*tHAr[8M
Kda\eZ1(J+=fk<41YLXa_4(U?Q]QE![nEsZ0dr`,?\/(740cZ?,#.CFA2@`.4f3C`Wjr,m=6d82hPNFq
X4(6OR`5)FgKD3k_no,.?f(lZHhMh]P0Ni-&SjRBLJJe:Ki+JD2"tn,XDnjOEdY6U\3=gNQa_IoJ)fNp
/gPaQeORpaB,oM"hd8$^EQX?1bhWl3"R_K$o0eYZEl0WRKCRSN<WZaegA*;"'Ve\8,$e(3%Ai$21/.O.
CcC!NMX!(O>K`08#kG8R7U0(4Hu16/[/6NY1=/g5+-L:lnoT/.XD[)k8BfhY:P-c6]8?JgYLI7XbY6[[
roOisYrC=ZUfPC!\L[[mk]nkB'!Z=8F#ELjOaDr?6Z^n*W*PkEY!n4!HXsJ1PeHQ(?uEmUNTb%kS2aE`
/00(<b-Y2Ec/>-]8jqg8]Zs.iNL'iq(bpCX1H2(@*UT;Kd,9P<;4Njo>SaWsb]p65_n@Hn_>/B:iP%k"
QbUGtF[(CX&(TOH8baq85o.GU=3dT2r/gCWrFh]eS336gbE25d0bEqP?2/2Fb;^GYm!%J:bY6VTaImZJ
`@(lW',Y-"KJ/mg&R![Bb.B0$Fh>"V3TB#"-%$/O@-Ai_)am0X-r3]@m.FYVbb0d&)mYF@]7ornZ"^uH
*)62G&'m*Fj1F$!Yfg0c(Z.SELp8da&Ij(B"C3.n;/)B>>Vt0kcI?+-a8-aA?#H&WZu$DEPhBtd*\3/B
9TDO0^nR5b;=US1T1%]E(VK:h?b6/V@FP8.CHe+OalME4S^0%)k"kc$o>9/5T]71+Qam+?7[o;7;r[uM
IX9Gd"`tp-\DpHO4uhT]n?V):Xg!!?#;\0u;0Bh1.77$G#/0$?MR^:C9i[Lq<Wmd2Br^U2]5\YY6egKj
pptXT*knOP.DBjDpt@:Xqr#.8V656ensl7ts!SfiSo9H%LZ]12.=Oe!S,La8p',2;qZabrhRa-/\G"@h
F]g('HASO0Q]I\$A0I"%AU`G._[K/s#\&KV-'"Y,h:#m=9>1W),\Xh$k,]d(7=HbkN1NS'[uk?NA<,W7
#`5Muq!/]:LCJIIghG0P7si>'2'O-PEC?"k&8sHOb0<iij1r+L0L"nP&4U=nVQ+=t*b!Z<cE(J:4g?PU
7/!@so4gA1AMQ!$LaNcjXuo5'<ZRNIQ8TqW#+4jj5PQ]biLAeW0ADi+n0k#$W_lBl3=fl1Gg=?=(JV/K
j-?KT3d;r.`S),CA#&+2&KXMH"Zo(?jZRFn\",*kB$Qt;O%:;:Et)d$(ZX*96I[0h@7Z/d]9OoL/JW;"
pIoB:-S^Mu%`YMXam??RE*X-$jK8IK2D@p5_&(s7g?Dh<TR=]:9kE_Zb.+u0Hm#g)*:7NEBp=&kk;^V>
L4-7]P@lBsg(h*0d8sL]]4"5`Y`N"r?349-dU&1*.]aSICOB:PKi0r'mq\\;V*t"Y*ZX-D)kFP:G-PSU
^Ea<VoRBh400\iI^U$@:L!FM5ib6Jf3PYL0$qcn2:*[b?VZFGO(o[JSs(hmNIHP_Gp3QSuf))Z?5M]Om
oi>G5eQ;)4=88EUXhK,IYMS,GH1tWp=)[WZ>CQ8=*m8mOiudM;^3Y]eYdZi[q;Ucj/&Pe6N5s;4@4M%i
#'ed,roNh1=S;^$bh&I>fAFf"Kq.9!7,=GuKQUYDg:\jF]G5l=QkO])Q;EIK7#WMQD(NU;hm_Xfj_8mV
GHng4Kcf-fLEp&9WUV-WJZNb"8pu3L9d8P2[ji$J;-FHdSE%;#3CZs3Bt!*i*dSBu6VHOhi1XMmeKl=V
0QjE(U>DSRoi4I$B6LgdQAGmPb3hD;P1_g@`&[4C:!-?3etmbZl9$sb>KjdOZdnTk!:R9t&YsjH:ER1D
/Lu0Z[gCt\e9OCec8[<e<@%a[He^lN\MNmgjDh1eX"(5f2c(%;3@jDR=si[4C!*+r8&rh3H$&.8GIiao
<e/g9U-c/QCjTaGhGi3pR.Un>X]D8Z.G@kD4MN2/f5e2BdV8k1CsA+>aMJs)e)nHU\*g.\*`TW@,gL\.
I\U'"Bb/2M!S4PL#LM+H<pnMb$Z66DKbghd_k+X/`YmiAB]6q^'(<'he4o%a.#iNf0$HR(VS-ji)-=Ao
jL]lj>%Mt\s+[k]H)eU!T\4h.aq+=HILYd=jo$*_IOQQC`]SXB5-e0KS@IRu%^05]IqW]Ik_YAoa6ruC
Y(<$kM-6I[1L*'"/tQttcaO_:[u9G(`UiK9oBkQ5G4Bs,nU.rn^0NkY?9I*tSo5Fk[hmB"Ms&j6oBjNg
mGdqbnU,]RI(OX;>s."niq`<-q=`hDG4BslGeUk9^0NkY/tQu7So5Fk[u9G(Ms&j6oBkQ5p>YmknU.rn
^:ed*>s.!sSo69RD\pa/Ms&iQkM*uImGds8iq[cfI(OX;]m.7s4Pqc_D_I]#(XKRKkM(pXgVrdOiq`<-
q0):U]6M''GeRHGh_(L=0:m(ucaShqgPb7L`UiKVq=YKJpWg1->U=C14-o2E#/m7[#'sc)YOFFY7JP-u
HBh2=THn&jh*S3M>YciqUiuA":/o"lc7VuV>3DHA`"?1kPKR:0\%=D7/d(kRGC+T&4Lr,Bn&"It/sNg1
ai9l=m4&a"?9?gR&fphF`\V,&jHQ78);R9XZZfVL0qMQbi1"&`ncr#(3bCmYNU>F?CV<XN13;oSk@41S
%.4jRIj<=6n\P/(&pCsLOM0JhnGUiFn)^>\\a$aM<S]MRN`)ud]A8E%Qc46s'!iY/i*q;L/]?7Z5;FQJ
LZb<"dmJ[6_>/B:^-.=8'_eq=D8:Kj.o3UL);$SmG\BM-gQ9BV\/Mdn*6;(J2K-<:j`c,sj>.6pD-ZlO
f;`1a\-j7EL/sOB4Vcca2Bqa``Y(YL$+l[D7(kNW<-Ln1PTua5ed-X.82ua0l-%aS(MA$,IC3"GeF:i^
*#TY9HrGhsbZBl)`Xff/;Z.D<2EPi9=VtE"G,s:m/<fh3%`L(O9:=Hk*D\jjT&tB/L::FBel[F^Z#0(u
MQtl0FJJ/M<34KpjZRaNdU.kN7GY?VI#ME+IH*HmrV!CN:&eS(q7mE`Y.*Qq,'h&U,C.&@&NZNJG^D%#
m@BA]2r;bKr&@A]AaS'C=d5Pr?%0-N=d7(,2t+fT!nl8=0]bOOSV'l?\T-*bCkk)4EL+)[g)9YE9kAE^
f_o@:F%%N5p?/DkYjpEu[jU2(BVAD'UdB1j8U093l,e72oT7<:RX1<a\*WZk[>EYarEV;@pSg)Qao7Y:
m1%FVp5N35in2CNg=VSEkVX<'Z?GMpfdU+Gb*jVHYTN4id-:,UED)PVQ.o55"((qj:WYa+)XmGjAtcGW
%?A4eOf9A0e+UlN&]jQXLLpb9./n+[h&JS#_U<*.q=,M.OO).[;hMhl_[)o]ZhJTpB#3=X%nV_7pZR3L
Wlkd0+gPu=5ku7*%,p]o@Sj4NGqq,<h4[E>-[1$o1[Lt@AP>%GXU<C7;OkPi?,!!+.WNXJ%!p+1<0Zq_
7nV)Y1u?OdH>Al9rGq9"&j#$ANchp;D^L4?/0NeR%%188c;K-&p-@&acD.qE'RmnI]9fVn+[\;XGtBuC
Q,XA;\,0:^<=4b)e$V%1QLHnYgqutDB9oNQ!,q.nMASbLEdAr3N2=#;^h3RH><DIAn.iAW9XK@!7SS[;
O&E4EH:MB-+WfL)oO>R>@E:T?8($'+Y3@LFGC_Kh,O0.kp1&#qQjbrcNO%XTqU-P^)(\B5Y`R;B?348B
.c]<[;gs_RTlFVDdt*7d=3"D^b-Enu_H^Eu]OK->Yh'js;YD^Qjlj?VkP*Bt\o50TMQC2E'f]]F4'Ptf
nrGSTaN<L42@AQ]?]M&Epe.^bs!VAV?G6#5DuJ5_]D(m.HL#cn=O"H:iqYN[n%Yp4QR8.!g:cT4VTD&e
%E!D,l[-[THu9SrEa/k,f4Y%@<rG69)tmpfos_89q!rUaL!8>2h)\R=hj7aH)l*CPIsYJ12`BLL?A-'5
+"c-sf0A5m;ZQ-uEVA/A+jh6UIEQR,N5P3M^L$IPG=gE7(NW`\47W\IbH$'+^O*&*pW.mf7mR*hfU's?
K`.hF_:h[jjk`4oT-pf8ASdc`2fpean+(iiDin.+1TR(GfQX_=+sXSG;!s44<W)-98'`Z6A&qKeCk[l@
(5[#$>FLJD*Ye9PUXi/@RId:(/S9.pmbo_8aP]0Q"8+TtpMbdDZ1$2Afic#>a`]3'-V$7WKFfNW;V633
2=XFZqoW#b`&Gn<njm3IkQXZb&EQ\S*]`)RYS['?S/_D"(A._H,&bH'VI;o&l_X'o\S8,Q44Sc5_<-l,
[md53DP!2n](+VEn0Zk!g\8T!&"U@9Iq,j?gH.C/Fec1]hs5:ZXj)4/Q[b6Fh-HXKUkg^D_eFa!i]V58
*kkJCnPq8?g>f6HXZ*PEkM?uX;Ob:Pr%;c#-V>2RC\#=heh7OB+@r;TkpP/GHW@I8G1Nhs<4>g\NN@A9
ioX\0$YQrA;I'2JFGs4rRCreKoZ;>3f5kM`^fIdZfdH(][0=V5'k@4=9GPim%[rkq=>L0i6,)C;)s2a,
<_nrM'8=1ZG##F5M1<NOhnCaM(q/;/BCINllnJ>P'`4bM@H7bBf)]^<fV7TjL&8GUEf7J_D<HWl/@heK
J::T-QF>S',Q?sMEPkAYK>ALUEMUM3R^./2c)k,Rh=A]hkI017ctbj<LTQoc=g5MlYW%&ec7o!#ICj3,
=TWN3LL'3]o>Y,44q2:/hQ5SESjM93'PA=T;=OH?Pd"E`]-VO"/>ZZ&Da9Aj>lH(%I:$gNnU&JSiWJFn
^l:&f`kSA12b*02ijsWg`P*u`9.hUr(C&`O%\CVN]tqE92XBU3hW`SmhagF)\Q@&W4\ROiDj^:lJ/'gp
]n!tuXn^XeH6h4Dr<gLT__OhK`4m'Z)Njps@!"F:=^>.79#LI8T3Tj&r_Mqaen_bN;%`os?eQ]apr*_*
=W*u&]@^aF"'<6-mMD<5CF<Th]H=!7H;Zi(g,1I]=)*s4SbcnM$0)oooD0a0=khF0IrU0glAD!h+QaXc
bSJjrp3ZiPV+O6l@_9):L3>[j$hBkiMuCQi&CW8m0u_^p,:1R`Z/$Whj@(J,Om!MtZU5r4;^62;Mdni]
XW_V`DgPVn>M*)EWo&hh"tRFKO[UPA;,)@Zir!eCm_G[_f;:MlD`kI847&Q*>1Q;XVIT;7hiAaZA,(3t
i!/ZUEE=!#QV21OO:LV)r!c<S-4XR/mIi?oYDoU\`mNH-`_G+40P#)/YbueY-Is"\ZP_AW7;<'&$:V_C
__G\N6aYp1VrqudM%^EF!)FAr;)ud3U_W@pY"rXZ$MPbDh?3:cTtOVD<c4mQk1t:\DFH^.@T9jVFVdNn
+h]W5I\'prGX8lu.-0o/i[rcj2:gZpluJ"p6dM#PIB8Lj1%;-LV73TXmOK*2r5+Ef(k7_Cqngcg]$G]@
<PR10cGpME\+<bWEUo"EO0p@T:Dh:a*=mHkPOV\t#/(f-jBHgJ'W%"l$Sa7MO4$R2Sl&A4>14)<bhE"4
V6bGbMUIn$<H720g<Uh"C7#r09mMV4MDX'=(<JsVbDLt1f+80M=:b.5MtNsmm$6egP@iF\@rpKEEA2<K
.Ul:<O+[%G@HOG)IRh#>rR%\>#&'Y+#,7i==$b\PQcd4NRfYq9\fRaZ$SkhkOMY3O:A_jZ(!nCY\*D"j
;r'Tr,F;<uH^%aplh;I4CRi+n7c9lZe.+QRHeG$e,o2!2"a7J'AZQu&8]iF/BFjpL7OD2>k(k@tI,)m?
&Do[H)F@eSj$srLMmb^cBP)bmn>6uD`s<bGn*n72+u^")W85\_<pomAn5nli)&+7HWpms/&WSUYU,f&C
#5Ac'b6FZf]Bth(A]g3ZQ;6$>CcPBH_iW^Q&9OpeP^i8RATr]Y8RAZK;&i.\MQGHi*^P7lUqYf9B`/I\
qZ8&2`u8jM;rLjE<=4aV*i+Z4\od.U6pol:BW7($Ur,NE1[b*JD)#'+91GP5KBnu9,J!g<bt<HTar4PG
9"SP)e4Vp:.G?(tDgW`&KH3L"=1F5#f-pPdc#M5b<3Yg0R?d&rcH`u^%DL7/j%m?]0<c#"Z\j&8A=Z>>
i>j2R^V<J)bmI8.G3]Ran2u^b7T[MIlijF^3R+SfBA72s/?(G/3)h87Q"'1kBs7D/5>A6+\`OTi[<lmg
IecDbl*!]>gnF[RrpflHVtto@Tle->l0J>$j)m_ArI._+p*8=1b\E&As5i"M58+"hg]%MM_N%=~>
endstream
endobj
7 0 obj
11983
endobj
3 0 obj
<<
/Parent null
/Type /Pages
/MediaBox [0.0000 0.0000 311.00 209.00]
/Resources 8 0 R
/Kids [5 0 R]
/Count 1
>>
endobj
9 0 obj
[/PDF /Text /ImageC]
endobj
10 0 obj
<<
/S /Transparency
/CS /DeviceRGB
/I true
/K false
>>
endobj
11 0 obj
<<
/Alpha1
<<
/ca 1.0000
/CA 1.0000
/BM /Normal
/AIS false
>>
>>
endobj
8 0 obj
<<
/ProcSet 9 0 R
/ExtGState 11 0 R
>>
endobj
xref
0 12
0000000000 65535 f
0000000015 00000 n
0000000315 00000 n
0000012726 00000 n
0000000445 00000 n
0000000521 00000 n
0000000609 00000 n
0000012702 00000 n
0000013180 00000 n
0000012896 00000 n
0000012935 00000 n
0000013037 00000 n
trailer
<<
/Size 12
/Root 2 0 R
/Info 1 0 R
>>
startxref
13253
%%EOF

View File

@ -5,7 +5,14 @@ High-level documentation of the [sat-rs project](https://absatsw.irs.uni-stuttga
## Building
If you have not done so, install `mdbook` using `cargo install mdbook --locked`.
If you have not done so, install the pre-requisites first:
```sh
cargo install mdbook --locked
cargo install mdbook-linkcheck --locked
```
After that, you can build the book with:
```sh
mdbook build

View File

@ -4,3 +4,6 @@ language = "en"
multilingual = false
src = "src"
title = "The sat-rs book"
[output.html]
[output.linkcheck]

View File

@ -1,3 +1,5 @@
<div id="communication-chapter"/>
# Communication with sat-rs based software
Communication is a vital topic for remote system which are usually not (directly)
@ -15,10 +17,16 @@ it is still centered around small packets. `sat-rs` provides support for these E
standards and also attempts to fill the gap to the internet protocol by providing the following
components.
1. [UDP TMTC Server](https://docs.rs/satrs-core/0.1.0-alpha.0/satrs_core/hal/host/udp_server/index.html#).
1. [UDP TMTC Server](https://docs.rs/satrs-core/0.1.0-alpha.0/satrs_core/hal/host/udp_server/index.html).
UDP is already packet based which makes it an excellent fit for exchanging space packets.
2. TCP TMTC Server. This is a stream based protocol, so the server uses the COBS framing protocol
to always deliver complete packets.
2. [TCP TMTC Server Components](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/index.html).
TCP is a stream based protocol, so the framework provides building blocks to parse telemetry
from an arbitrary bytestream. Two concrete implementations are provided:
- [TCP spacepackets server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
to parse tightly packed CCSDS Spacepackets.
- [TCP COBS server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpTmtcInCobsServer.html)
to parse generic frames wrapped with the
[COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
# Working with telemetry and telecommands (TMTC)

View File

@ -11,23 +11,54 @@ time where the OBSW might be running on Linux based systems with hundreds of MBs
A useful pattern used commonly in space systems is to limit heap allocations to program
initialization time and avoid frequent run-time allocations. This prevents issues like
running out of memory (something even Rust can not protect from) or heap fragmentation.
running out of memory (something even Rust can not protect from) or heap fragmentation on systems
without a MMU.
# Using pre-allocated pool structures
A huge candidate for heap allocations is the TMTC and handling. TC, TMs and IPC data are all
candidates where the data size might vary greatly. The regular solution for host systems
might be to send around this data as a `Vec<u8>` until it is dropped. `sat-rs` provides
another solution to avoid run-time allocations by offering and recommendng pre-allocated static
pools.
another solution to avoid run-time allocations by offering pre-allocated static
pools. These pools are split into subpools where each subpool can have different page sizes.
For example, a very small telecommand (TC) pool might look like this:
These pools are split into subpools where each subpool can have different page sizes.
For example, a very small TC pool might look like this:
![Example Pool](images/pools/static-pools.png)
TODO: Add image
The core of the pool abstractions is the
[PoolProvider trait](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/trait.PoolProvider.html).
This trait specifies the general API a pool structure should have without making assumption
of how the data is stored.
This trait is implemented by a static memory pool implementation.
The code to generate this static pool would look like this:
```rust
use satrs_core::pool::{StaticMemoryPool, StaticPoolConfig};
let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![
(6, 16),
(4, 32),
(2, 64),
(1, 128)
]));
```
It should be noted that the buckets only show the maximum size of data being stored inside them.
The store will keep a separate structure to track the actual size of the data being stored.
A TC entry inside this pool has a store address which can then be sent around without having
to dynamically allocate memory. The same principle can also be applied to the TM and IPC data.
to dynamically allocate memory. The same principle can also be applied to the telemetry (TM) and
inter-process communication (IPC) data.
You can read
- [`StaticPoolConfig` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticPoolConfig.html)
- [`StaticMemoryPool` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticMemoryPool.html)
for more details.
In the future, optimized pool structures which use standard containers or are
[`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html) by default might be added as well.
# Using special crates to prevent smaller allocations
@ -35,7 +66,7 @@ Another common way to use the heap on host systems is using containers like `Str
to work with data where the size is not known beforehand. The most common solution for embedded
systems is to determine the maximum expected size and then use a pre-allocated `u8` buffer and a
size variable. Alternatively, you can use the following crates for more convenience or a smart
behaviour which at the very least reduce heap allocations:
behaviour which at the very least reduces heap allocations:
1. [`smallvec`](https://docs.rs/smallvec/latest/smallvec/).
2. [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec/index.html) which also contains an

View File

@ -9,22 +9,144 @@ software application.
## Structure of the example project
The example project contains components which could also be expected to be part of a production
On-Board Software.
On-Board Software. A structural diagram of the example application is given to provide
a brief high-level view of the components used inside the example application:
1. A UDP and TCP server to receive telecommands and poll telemetry from. This might be an optional
component for an OBSW which is only used during the development phase on ground. The TCP
server parses space packets by using the CCSDS space packet ID as the packet start delimiter.
2. A PUS service stack which exposes some functionality conformant with the ECSS PUS service. This
currently includes the following services:
- Service 1 for telecommand verification.
- Service 3 for housekeeping telemetry handling.
- Service 5 for management and downlink of on-board events.
- Service 8 for handling on-board actions.
- Service 11 for scheduling telecommands to be released at a specific time.
- Service 17 for test purposes (pings)
3. An event manager component which handles the event IPC mechanism.
4. A TC source component which demultiplexes and routes telecommands based on parameters like
packet APID or PUS service and subservice type.
5. A TM sink sink component which is the target of all sent telemetry and sends it to downlink
![satrs-example component structure](images/satrs-example/satrs-example-structure.png)
The dotted lines are used to denote optional components. In this case, the static pool components
are optional because the heap can be used as a simpler mechanism to store TMTC packets as well.
Some additional explanation is provided for the various components.
### TCP/IP server components
The example includes a UDP and TCP server to receive telecommands and poll telemetry from. This
might be an optional component for an OBSW which is only used during the development phase on
ground. The UDP server is strongly based on the
[UDP TC server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/udp_server/struct.UdpTcServer.html).
This server component is wrapped by a TMTC server which handles all telemetry to the last connected
client.
The TCP server is based on the [TCP Spacepacket Server](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/hal/std/tcp_server/struct.TcpSpacepacketsServer.html)
class. It parses space packets by using the CCSDS space packet ID as the packet
start delimiter. All available telemetry will be sent back to a client after having read all
telecommands from the client.
### TMTC Infrastructure
The most important components of the TMTC infrastructure include the following components:
- A TC source component which demultiplexes and routes telecommands based on parameters like
packet APID or PUS service and subservice type.
- A TM sink sink component which is the target of all sent telemetry and sends it to downlink
handlers like the UDP and TCP server.
6. An AOCS example task which can also process some PUS commands.
You can read the [Communications chapter](./communication.md) for more
background information on the chosen TMTC infrastructure approach.
### PUS Service Components
A PUS service stack is provided which exposes some functionality conformant with the ECSS PUS
services. This currently includes the following services:
- Service 1 for telecommand verification. The verification handling is handled locally: Each
component which generates verification telemetry in some shape or form receives a
[reporter](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/verification/struct.VerificationReporterWithSender.html)
object which can be used to send PUS 1 verification telemetry to the TM funnel.
- Service 3 for housekeeping telemetry handling.
- Service 5 for management and downlink of on-board events.
- Service 8 for handling on-board actions.
- Service 11 for scheduling telecommands to be released at a specific time. This component
uses the [PUS scheduler class](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/scheduler/alloc_mod/struct.PusScheduler.html)
which performs the core logic of scheduling telecommands. All telecommands released by the
scheduler are sent to the central TC source using a message.
- Service 17 for test purposes like pings.
### Event Management Component
An event manager based on the sat-rs
[event manager component](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/event_man/index.html)
is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5
telemetry by the
[PUS event dispatcher](https://docs.rs/satrs-core/0.1.0-alpha.1/satrs_core/pus/event_man/alloc_mod/struct.PusEventDispatcher.html).
You can read the [events](./events.md) chapter for more in-depth information about event management.
### Sample Application Components
These components are example mission specific. They provide an idea how mission specific modules
would look like the sat-rs context. It currently includes the following components:
- An Attitute and Orbit Control (AOCS) example task which can also process some PUS commands.
## Dataflow
The interaction of the various components is provided in the following diagram:
![satrs-example dataflow diagram](images/satrs-example/satrs-example-dataflow.png)
It should be noted that an arrow coming out of a component group refers to multiple components
in that group. An explanation for important component groups will be given.
#### TMTC component group
This groups is the primary interface for clients to communicate with the on-board software
using a standardized TMTC protocol. The example uses the
[ECSS PUS protocol](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
In the future, this might be extended with the
[CCSDS File Delivery Protocol](https://public.ccsds.org/Pubs/727x0b5.pdf).
A client can connect to the UDP or TCP server to send these PUS packets to the on-board software.
These servers then forward the telecommads to a centralized TC source component using a dedicated
message abstraction.
This TC source component then demultiplexes the message and forwards it to the relevant components.
Right now, it forwards all PUS requests to the respective PUS service handlers using the PUS
receiver component. The individual PUS services are running in a separate thread. In the future,
additional forwarding to components like a CFDP handler might be added as well. It should be noted
that PUS11 commands might contain other PUS commands which should be scheduled in the future.
These wrapped commands are forwarded to the PUS11 handler. When the schedule releases those
commands, it forwards the released commands to the TC source again. This allows the scheduler
and the TC source to run in separate threads and keeps them cleanly separated.
All telemetry generated by the on-board software is sent to a centralized TM funnel. This component
also performs a demultiplexing step to forward all telemetry to the relevant TM recipients.
In the example case, this is the last UDP client, or a connected TCP client. In the future,
forwarding to a persistent telemetry store and a simulated communication component might be
added here as well. The centralized TM funnel also takes care of some packet processing steps which
need to be applied for each ECSS PUS packet, for example CCSDS specific APID incrementation and
PUS specific message counter incrementation.
#### Application Group
The application components generally do not receive raw PUS packets directly, even though
this is certainly possible. Instead, they receive internalized messages from the PUS service
handlers. For example, instead of receiving a PUS 8 Action Telecommand directly, an application
component will receive a special `ActionRequest` message type reduced to the basic important
information required to execute a request. These special requests are denoted by the blue arrow
in the diagram.
It should be noted that the arrow pointing towards the event manager points in both directions.
This is because the application components might be interested in events generated by other
components as well. This mechanism is oftentimes used to implement the FDIR functionality on system
and component level.
#### Shared components and functional interfaces
It should be noted that sometimes, a functional interface is used instead of a message. This
is used for the generation of verification telemetry. The verification reporter is a clonable
component which generates and sends PUS1 verification telemetry directly to the TM funnel. This
introduces a loose coupling to the PUS standard but was considered the easiest solution for
a project which utilizes PUS as the main communication protocol. In the future, a generic
verification abstraction might be introduced to completely decouple the application layer from
PUS.
The same concept is applied if the backing store of TMTC packets are shared pools. Every
component which needs to read telecommands inside that shared pool or generate new telemetry
into that shared pool will received a clonable shared handle to that pool.
The same concept could be extended to power or thermal handling. For example, a shared power helper
component might be used to retrieve power state information and send power switch commands through
a functional interface. The actual implementation of the functional interface might still use
shared memory and/or messages, but the functional interface makes using and testing the interaction
with these components easier.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View File

@ -1,6 +1,6 @@
[package]
name = "satrs-core"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.3"
edition = "2021"
rust-version = "1.61"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -15,8 +15,6 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies]
delegate = ">0.7, <=0.10"
paste = "1"
# TODO: Remove this as soon as the image including the description was moved to the satrs-book.
embed-doc-image = "0.1"
[dependencies.smallvec]
version = "1"

View File

@ -1,13 +1,21 @@
//! # Pool implementation providing memory pools for packet storage.
//!
//! This module provides generic abstractions for memory pools which provide a storage
//! machanism for variable sized data like Telemetry and Telecommand (TMTC) packets. The core
//! abstraction for this is the [PoolProvider] trait.
//!
//! It also contains the [StaticMemoryPool] as a concrete implementation which can be used to avoid
//! dynamic run-time allocations for the storage of TMTC packets.
//!
//! # Example for the [StaticMemoryPool]
//!
//! ```
//! use satrs_core::pool::{PoolProviderMemInPlace, StaticMemoryPool, StaticPoolConfig};
//! use satrs_core::pool::{PoolProvider, StaticMemoryPool, StaticPoolConfig};
//!
//! // 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes
//! let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)]);
//! let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)], false);
//! let mut local_pool = StaticMemoryPool::new(pool_cfg);
//! let mut read_buf: [u8; 16] = [0; 16];
//! let mut addr;
//! {
//! // Add new data to the pool
@ -20,25 +28,25 @@
//!
//! {
//! // Read the store data back
//! let res = local_pool.read(&addr);
//! let res = local_pool.read(&addr, &mut read_buf);
//! assert!(res.is_ok());
//! let buf_read_back = res.unwrap();
//! assert_eq!(buf_read_back.len(), 4);
//! assert_eq!(buf_read_back[0], 42);
//! let read_bytes = res.unwrap();
//! assert_eq!(read_bytes, 4);
//! assert_eq!(read_buf[0], 42);
//! // Modify the stored data
//! let res = local_pool.modify(&addr);
//! let res = local_pool.modify(&addr, |buf| {
//! buf[0] = 12;
//! });
//! assert!(res.is_ok());
//! let buf_read_back = res.unwrap();
//! buf_read_back[0] = 12;
//! }
//!
//! {
//! // Read the modified data back
//! let res = local_pool.read(&addr);
//! let res = local_pool.read(&addr, &mut read_buf);
//! assert!(res.is_ok());
//! let buf_read_back = res.unwrap();
//! assert_eq!(buf_read_back.len(), 4);
//! assert_eq!(buf_read_back[0], 12);
//! let read_bytes = res.unwrap();
//! assert_eq!(read_bytes, 4);
//! assert_eq!(read_buf[0], 12);
//! }
//!
//! // Delete the stored data
@ -46,21 +54,21 @@
//!
//! // Get a free element in the pool with an appropriate size
//! {
//! let res = local_pool.free_element(12);
//! let res = local_pool.free_element(12, |buf| {
//! buf[0] = 7;
//! });
//! assert!(res.is_ok());
//! let (tmp, mut_buf) = res.unwrap();
//! addr = tmp;
//! mut_buf[0] = 7;
//! addr = res.unwrap();
//! }
//!
//! // Read back the data
//! {
//! // Read the store data back
//! let res = local_pool.read(&addr);
//! let res = local_pool.read(&addr, &mut read_buf);
//! assert!(res.is_ok());
//! let buf_read_back = res.unwrap();
//! assert_eq!(buf_read_back.len(), 12);
//! assert_eq!(buf_read_back[0], 7);
//! let read_bytes = res.unwrap();
//! assert_eq!(read_bytes, 12);
//! assert_eq!(read_buf[0], 7);
//! }
//! ```
#[cfg(feature = "alloc")]
@ -70,6 +78,7 @@ use core::fmt::{Display, Formatter};
use delegate::delegate;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use spacepackets::ByteConversionError;
#[cfg(feature = "std")]
use std::error::Error;
@ -151,6 +160,8 @@ pub enum StoreError {
InvalidStoreId(StoreIdError, Option<StoreAddr>),
/// Valid subpool and packet index, but no data is stored at the given address
DataDoesNotExist(StoreAddr),
ByteConversionError(spacepackets::ByteConversionError),
LockError,
/// Internal or configuration errors
InternalError(u32),
}
@ -173,10 +184,22 @@ impl Display for StoreError {
StoreError::InternalError(e) => {
write!(f, "internal error: {e}")
}
StoreError::ByteConversionError(e) => {
write!(f, "store error: {e}")
}
StoreError::LockError => {
write!(f, "lock error")
}
}
}
}
impl From<ByteConversionError> for StoreError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversionError(value)
}
}
#[cfg(feature = "std")]
impl Error for StoreError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
@ -187,41 +210,61 @@ impl Error for StoreError {
}
}
/// Generic trait for pool providers where the data can be modified and read in-place. This
/// generally means that a shared pool structure has to be wrapped inside a lock structure.
pub trait PoolProviderMemInPlace {
/// Generic trait for pool providers which provide memory pools for variable sized data.
///
/// It specifies a basic API to [Self::add], [Self::modify], [Self::read] and [Self::delete] data
/// in the store at its core. The API was designed so internal optimizations can be performed
/// more easily and that is is also possible to make the pool structure [Sync] without the whole
/// pool structure being wrapped inside a lock.
pub trait PoolProvider {
/// Add new data to the pool. The provider should attempt to reserve a memory block with the
/// appropriate size and then copy the given data to the block. Yields a [StoreAddr] which can
/// be used to access the data stored in the pool
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError>;
/// The provider should attempt to reserve a free memory block with the appropriate size and
/// then return a mutable reference to it. Yields a [StoreAddr] which can be used to access
/// the data stored in the pool
fn free_element(&mut self, len: usize) -> Result<(StoreAddr, &mut [u8]), StoreError>;
/// The provider should attempt to reserve a free memory block with the appropriate size first.
/// It then executes a user-provided closure and passes a mutable reference to that memory
/// block to the closure. This allows the user to write data to the memory block.
/// The function should yield a [StoreAddr] which can be used to access the data stored in the
/// pool.
fn free_element<W: FnMut(&mut [u8])>(
&mut self,
len: usize,
writer: W,
) -> Result<StoreAddr, StoreError>;
/// Modify data added previously using a given [StoreAddr] by yielding a mutable reference
/// to it
fn modify(&mut self, addr: &StoreAddr) -> Result<&mut [u8], StoreError>;
/// Modify data added previously using a given [StoreAddr]. The provider should use the store
/// address to determine if a memory block exists for that address. If it does, it should
/// call the user-provided closure and pass a mutable reference to the memory block
/// to the closure. This allows the user to modify the memory block.
fn modify<U: FnMut(&mut [u8])>(
&mut self,
addr: &StoreAddr,
updater: U,
) -> Result<(), StoreError>;
/// Read data by yielding a read-only reference given a [StoreAddr]
fn read(&self, addr: &StoreAddr) -> Result<&[u8], StoreError>;
/// The provider should copy the data from the memory block to the user-provided buffer if
/// it exists.
fn read(&self, addr: &StoreAddr, buf: &mut [u8]) -> Result<usize, StoreError>;
/// Delete data inside the pool given a [StoreAddr]
/// Delete data inside the pool given a [StoreAddr].
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError>;
fn has_element_at(&self, addr: &StoreAddr) -> Result<bool, StoreError>;
/// Retrieve the length of the data at the given store address.
fn len_of_data(&self, addr: &StoreAddr) -> Result<usize, StoreError> {
if !self.has_element_at(addr)? {
return Err(StoreError::DataDoesNotExist(*addr));
}
Ok(self.read(addr)?.len())
fn len_of_data(&self, addr: &StoreAddr) -> Result<usize, StoreError>;
#[cfg(feature = "alloc")]
fn read_as_vec(&self, addr: &StoreAddr) -> Result<alloc::vec::Vec<u8>, StoreError> {
let mut vec = alloc::vec![0; self.len_of_data(addr)?];
self.read(addr, &mut vec)?;
Ok(vec)
}
}
pub trait PoolProviderMemInPlaceWithGuards: PoolProviderMemInPlace {
/// This function behaves like [PoolProviderMemInPlace::read], but consumes the provided address
/// Extension trait which adds guarded pool access classes.
pub trait PoolProviderWithGuards: PoolProvider {
/// This function behaves like [PoolProvider::read], but consumes the provided address
/// and returns a RAII conformant guard object.
///
/// Unless the guard [PoolRwGuard::release] method is called, the data for the
@ -231,7 +274,7 @@ pub trait PoolProviderMemInPlaceWithGuards: PoolProviderMemInPlace {
/// manual deletion is necessary when returning from a processing function prematurely.
fn read_with_guard(&mut self, addr: StoreAddr) -> PoolGuard<Self>;
/// This function behaves like [PoolProviderMemInPlace::modify], but consumes the provided
/// This function behaves like [PoolProvider::modify], but consumes the provided
/// address and returns a RAII conformant guard object.
///
/// Unless the guard [PoolRwGuard::release] method is called, the data for the
@ -242,15 +285,16 @@ pub trait PoolProviderMemInPlaceWithGuards: PoolProviderMemInPlace {
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard<Self>;
}
pub struct PoolGuard<'a, MemProvider: PoolProviderMemInPlace + ?Sized> {
pub struct PoolGuard<'a, MemProvider: PoolProvider + ?Sized> {
pool: &'a mut MemProvider,
pub addr: StoreAddr,
no_deletion: bool,
deletion_failed_error: Option<StoreError>,
}
/// This helper object
impl<'a, MemProvider: PoolProviderMemInPlace> PoolGuard<'a, MemProvider> {
/// This helper object can be used to safely access pool data without worrying about memory
/// leaks.
impl<'a, MemProvider: PoolProvider> PoolGuard<'a, MemProvider> {
pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self {
Self {
pool,
@ -260,8 +304,13 @@ impl<'a, MemProvider: PoolProviderMemInPlace> PoolGuard<'a, MemProvider> {
}
}
pub fn read(&self) -> Result<&[u8], StoreError> {
self.pool.read(&self.addr)
pub fn read(&self, buf: &mut [u8]) -> Result<usize, StoreError> {
self.pool.read(&self.addr, buf)
}
#[cfg(feature = "alloc")]
pub fn read_as_vec(&self) -> Result<alloc::vec::Vec<u8>, StoreError> {
self.pool.read_as_vec(&self.addr)
}
/// Releasing the pool guard will disable the automatic deletion of the data when the guard
@ -271,7 +320,7 @@ impl<'a, MemProvider: PoolProviderMemInPlace> PoolGuard<'a, MemProvider> {
}
}
impl<MemProvider: PoolProviderMemInPlace + ?Sized> Drop for PoolGuard<'_, MemProvider> {
impl<MemProvider: PoolProvider + ?Sized> Drop for PoolGuard<'_, MemProvider> {
fn drop(&mut self) {
if !self.no_deletion {
if let Err(e) = self.pool.delete(self.addr) {
@ -281,24 +330,24 @@ impl<MemProvider: PoolProviderMemInPlace + ?Sized> Drop for PoolGuard<'_, MemPro
}
}
pub struct PoolRwGuard<'a, MemProvider: PoolProviderMemInPlace + ?Sized> {
pub struct PoolRwGuard<'a, MemProvider: PoolProvider + ?Sized> {
guard: PoolGuard<'a, MemProvider>,
}
impl<'a, MemProvider: PoolProviderMemInPlace> PoolRwGuard<'a, MemProvider> {
impl<'a, MemProvider: PoolProvider> PoolRwGuard<'a, MemProvider> {
pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self {
Self {
guard: PoolGuard::new(pool, addr),
}
}
pub fn modify(&mut self) -> Result<&mut [u8], StoreError> {
self.guard.pool.modify(&self.guard.addr)
pub fn update<U: FnMut(&mut [u8])>(&mut self, updater: &mut U) -> Result<(), StoreError> {
self.guard.pool.modify(&self.guard.addr, updater)
}
delegate!(
to self.guard {
pub fn read(&self) -> Result<&[u8], StoreError>;
pub fn read(&self, buf: &mut [u8]) -> Result<usize, StoreError>;
/// Releasing the pool guard will disable the automatic deletion of the data when the guard
/// is dropped.
pub fn release(&mut self);
@ -308,13 +357,11 @@ impl<'a, MemProvider: PoolProviderMemInPlace> PoolRwGuard<'a, MemProvider> {
#[cfg(feature = "alloc")]
mod alloc_mod {
use super::{
PoolGuard, PoolProviderMemInPlace, PoolProviderMemInPlaceWithGuards, PoolRwGuard,
StaticPoolAddr,
};
use super::{PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticPoolAddr};
use crate::pool::{NumBlocks, StoreAddr, StoreError, StoreIdError};
use alloc::vec;
use alloc::vec::Vec;
use spacepackets::ByteConversionError;
#[cfg(feature = "std")]
use std::sync::{Arc, RwLock};
@ -329,16 +376,24 @@ mod alloc_mod {
///
/// # Parameters
///
/// * `cfg`: Vector of tuples which represent a subpool. The first entry in the tuple specifies the
/// number of memory blocks in the subpool, the second entry the size of the blocks
/// * `cfg` - Vector of tuples which represent a subpool. The first entry in the tuple specifies
/// the number of memory blocks in the subpool, the second entry the size of the blocks
/// * `spill_to_higher_subpools` - Specifies whether data will be spilled to higher subpools
/// if the next fitting subpool is full. This is useful to ensure the pool remains useful
/// for all data sizes as long as possible. However, an undesirable side-effect might be
/// the chocking of larger subpools by underdimensioned smaller subpools.
#[derive(Clone)]
pub struct StaticPoolConfig {
cfg: Vec<(NumBlocks, usize)>,
spill_to_higher_subpools: bool,
}
impl StaticPoolConfig {
pub fn new(cfg: Vec<(NumBlocks, usize)>) -> Self {
StaticPoolConfig { cfg }
pub fn new(cfg: Vec<(NumBlocks, usize)>, spill_to_higher_subpools: bool) -> Self {
StaticPoolConfig {
cfg,
spill_to_higher_subpools,
}
}
pub fn cfg(&self) -> &Vec<(NumBlocks, usize)> {
@ -356,16 +411,21 @@ mod alloc_mod {
/// Pool implementation providing sub-pools with fixed size memory blocks.
///
/// This is a simple memory pool implementation which pre-allocates all sub-pools using a given pool
/// configuration. After the pre-allocation, no dynamic memory allocation will be performed
/// during run-time. This makes the implementation suitable for real-time applications and
/// embedded environments. The pool implementation will also track the size of the data stored
/// inside it.
/// This is a simple memory pool implementation which pre-allocates all subpools using a given
/// pool configuration. After the pre-allocation, no dynamic memory allocation will be
/// performed during run-time. This makes the implementation suitable for real-time
/// applications and embedded environments.
///
/// The subpool bucket sizes only denote the maximum possible data size being stored inside
/// them and the pool implementation will still track the size of the data stored inside it.
/// The implementation will generally determine the best fitting subpool for given data to
/// add. Currently, the pool does not support spilling to larger subpools if the closest
/// fitting subpool is full. This might be added in the future.
///
/// Transactions with the [pool][StaticMemoryPool] are done using a generic
/// [address][StoreAddr] type.
/// Adding any data to the pool will yield a store address. Modification and read operations are
/// done using a reference to a store address. Deletion will consume the store address.
/// [address][StoreAddr] type. Adding any data to the pool will yield a store address.
/// Modification and read operations are done using a reference to a store address. Deletion
/// will consume the store address.
pub struct StaticMemoryPool {
pool_cfg: StaticPoolConfig,
pool: Vec<Vec<u8>>,
@ -422,7 +482,17 @@ mod alloc_mod {
}
fn reserve(&mut self, data_len: usize) -> Result<StaticPoolAddr, StoreError> {
let subpool_idx = self.find_subpool(data_len, 0)?;
let mut subpool_idx = self.find_subpool(data_len, 0)?;
if self.pool_cfg.spill_to_higher_subpools {
while let Err(StoreError::StoreFull(_)) = self.find_empty(subpool_idx) {
if (subpool_idx + 1) as usize == self.sizes_lists.len() {
return Err(StoreError::StoreFull(subpool_idx));
}
subpool_idx += 1;
}
}
let (slot, size_slot_ref) = self.find_empty(subpool_idx)?;
*size_slot_ref = data_len;
Ok(StaticPoolAddr {
@ -476,7 +546,7 @@ mod alloc_mod {
}
}
impl PoolProviderMemInPlace for StaticMemoryPool {
impl PoolProvider for StaticMemoryPool {
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError> {
let data_len = data.len();
if data_len > POOL_MAX_SIZE {
@ -487,7 +557,11 @@ mod alloc_mod {
Ok(addr.into())
}
fn free_element(&mut self, len: usize) -> Result<(StoreAddr, &mut [u8]), StoreError> {
fn free_element<W: FnMut(&mut [u8])>(
&mut self,
len: usize,
mut writer: W,
) -> Result<StoreAddr, StoreError> {
if len > POOL_MAX_SIZE {
return Err(StoreError::DataTooLarge(len));
}
@ -495,25 +569,40 @@ mod alloc_mod {
let raw_pos = self.raw_pos(&addr).unwrap();
let block =
&mut self.pool.get_mut(addr.pool_idx as usize).unwrap()[raw_pos..raw_pos + len];
Ok((addr.into(), block))
writer(block);
Ok(addr.into())
}
fn modify(&mut self, addr: &StoreAddr) -> Result<&mut [u8], StoreError> {
fn modify<U: FnMut(&mut [u8])>(
&mut self,
addr: &StoreAddr,
mut updater: U,
) -> Result<(), StoreError> {
let addr = StaticPoolAddr::from(*addr);
let curr_size = self.addr_check(&addr)?;
let raw_pos = self.raw_pos(&addr).unwrap();
let block = &mut self.pool.get_mut(addr.pool_idx as usize).unwrap()
[raw_pos..raw_pos + curr_size];
Ok(block)
updater(block);
Ok(())
}
fn read(&self, addr: &StoreAddr) -> Result<&[u8], StoreError> {
fn read(&self, addr: &StoreAddr, buf: &mut [u8]) -> Result<usize, StoreError> {
let addr = StaticPoolAddr::from(*addr);
let curr_size = self.addr_check(&addr)?;
if buf.len() < curr_size {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: curr_size,
}
.into());
}
let raw_pos = self.raw_pos(&addr).unwrap();
let block =
&self.pool.get(addr.pool_idx as usize).unwrap()[raw_pos..raw_pos + curr_size];
Ok(block)
//block.copy_from_slice(&src);
buf[..curr_size].copy_from_slice(block);
Ok(curr_size)
}
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError> {
@ -540,9 +629,21 @@ mod alloc_mod {
}
Ok(true)
}
fn len_of_data(&self, addr: &StoreAddr) -> Result<usize, StoreError> {
let addr = StaticPoolAddr::from(*addr);
self.validate_addr(&addr)?;
let pool_idx = addr.pool_idx as usize;
let size_list = self.sizes_lists.get(pool_idx).unwrap();
let size = size_list[addr.packet_idx as usize];
Ok(match size {
STORE_FREE => 0,
_ => size,
})
}
}
impl PoolProviderMemInPlaceWithGuards for StaticMemoryPool {
impl PoolProviderWithGuards for StaticMemoryPool {
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard<Self> {
PoolRwGuard::new(self, addr)
}
@ -556,30 +657,29 @@ mod alloc_mod {
#[cfg(test)]
mod tests {
use crate::pool::{
PoolGuard, PoolProviderMemInPlace, PoolProviderMemInPlaceWithGuards, PoolRwGuard,
StaticMemoryPool, StaticPoolAddr, StaticPoolConfig, StoreError, StoreIdError,
POOL_MAX_SIZE,
PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticMemoryPool,
StaticPoolAddr, StaticPoolConfig, StoreError, StoreIdError, POOL_MAX_SIZE,
};
use std::vec;
fn basic_small_pool() -> StaticMemoryPool {
// 4 buckets of 4 bytes, 2 of 8 bytes and 1 of 16 bytes
let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)]);
let pool_cfg = StaticPoolConfig::new(vec![(4, 4), (2, 8), (1, 16)], false);
StaticMemoryPool::new(pool_cfg)
}
#[test]
fn test_cfg() {
// Values where number of buckets is 0 or size is too large should be removed
let mut pool_cfg = StaticPoolConfig::new(vec![(0, 0), (1, 0), (2, POOL_MAX_SIZE)]);
let mut pool_cfg = StaticPoolConfig::new(vec![(0, 0), (1, 0), (2, POOL_MAX_SIZE)], false);
pool_cfg.sanitize();
assert_eq!(*pool_cfg.cfg(), vec![(1, 0)]);
// Entries should be ordered according to bucket size
pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)]);
pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)], false);
pool_cfg.sanitize();
assert_eq!(*pool_cfg.cfg(), vec![(32, 3), (16, 6), (8, 12)]);
// Unstable sort is used, so order of entries with same block length should not matter
pool_cfg = StaticPoolConfig::new(vec![(12, 12), (14, 16), (10, 12)]);
pool_cfg = StaticPoolConfig::new(vec![(12, 12), (14, 16), (10, 12)], false);
pool_cfg.sanitize();
assert!(
*pool_cfg.cfg() == vec![(12, 12), (10, 12), (14, 16)]
@ -594,13 +694,14 @@ mod tests {
for (i, val) in test_buf.iter_mut().enumerate() {
*val = i as u8;
}
let mut other_buf: [u8; 16] = [0; 16];
let addr = local_pool.add(&test_buf).expect("Adding data failed");
// Read back data and verify correctness
let res = local_pool.read(&addr);
let res = local_pool.read(&addr, &mut other_buf);
assert!(res.is_ok());
let buf_read_back = res.unwrap();
assert_eq!(buf_read_back.len(), 16);
for (i, &val) in buf_read_back.iter().enumerate() {
let read_len = res.unwrap();
assert_eq!(read_len, 16);
for (i, &val) in other_buf.iter().enumerate() {
assert_eq!(val, i as u8);
}
}
@ -610,8 +711,10 @@ mod tests {
let mut local_pool = basic_small_pool();
let test_buf: [u8; 12] = [0; 12];
let addr = local_pool.add(&test_buf).expect("Adding data failed");
let res = local_pool.read(&addr).expect("Read back failed");
assert_eq!(res.len(), 12);
let res = local_pool
.read(&addr, &mut [0; 12])
.expect("Read back failed");
assert_eq!(res, 12);
}
#[test]
@ -622,10 +725,13 @@ mod tests {
// Delete the data
let res = local_pool.delete(addr);
assert!(res.is_ok());
let mut writer = |buf: &mut [u8]| {
assert_eq!(buf.len(), 12);
};
// Verify that the slot is free by trying to get a reference to it
let res = local_pool.free_element(12);
let res = local_pool.free_element(12, &mut writer);
assert!(res.is_ok());
let (addr, buf_ref) = res.unwrap();
let addr = res.unwrap();
assert_eq!(
addr,
u64::from(StaticPoolAddr {
@ -633,7 +739,6 @@ mod tests {
packet_idx: 0
})
);
assert_eq!(buf_ref.len(), 12);
}
#[test]
@ -647,29 +752,34 @@ mod tests {
{
// Verify that the slot is free by trying to get a reference to it
let res = local_pool.modify(&addr).expect("Modifying data failed");
res[0] = 0;
res[1] = 0x42;
local_pool
.modify(&addr, &mut |buf: &mut [u8]| {
buf[0] = 0;
buf[1] = 0x42;
})
.expect("Modifying data failed");
}
let res = local_pool.read(&addr).expect("Reading back data failed");
assert_eq!(res[0], 0);
assert_eq!(res[1], 0x42);
assert_eq!(res[2], 2);
assert_eq!(res[3], 3);
local_pool
.read(&addr, &mut test_buf)
.expect("Reading back data failed");
assert_eq!(test_buf[0], 0);
assert_eq!(test_buf[1], 0x42);
assert_eq!(test_buf[2], 2);
assert_eq!(test_buf[3], 3);
}
#[test]
fn test_consecutive_reservation() {
let mut local_pool = basic_small_pool();
// Reserve two smaller blocks consecutively and verify that the third reservation fails
let res = local_pool.free_element(8);
let res = local_pool.free_element(8, |_| {});
assert!(res.is_ok());
let (addr0, _) = res.unwrap();
let res = local_pool.free_element(8);
let addr0 = res.unwrap();
let res = local_pool.free_element(8, |_| {});
assert!(res.is_ok());
let (addr1, _) = res.unwrap();
let res = local_pool.free_element(8);
let addr1 = res.unwrap();
let res = local_pool.free_element(8, |_| {});
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, StoreError::StoreFull(1));
@ -689,6 +799,7 @@ mod tests {
pool_idx: 0,
}
.into(),
&mut [],
);
assert!(res.is_err());
assert!(matches!(
@ -720,7 +831,7 @@ mod tests {
packet_idx: 0,
}
.into();
let res = local_pool.read(&addr);
let res = local_pool.read(&addr, &mut []);
assert!(res.is_err());
let err = res.unwrap_err();
assert!(matches!(
@ -737,7 +848,7 @@ mod tests {
packet_idx: 1,
};
assert_eq!(addr.raw(), 0x00020001);
let res = local_pool.read(&addr.into());
let res = local_pool.read(&addr.into(), &mut []);
assert!(res.is_err());
let err = res.unwrap_err();
assert!(matches!(
@ -759,7 +870,7 @@ mod tests {
#[test]
fn test_data_too_large_1() {
let mut local_pool = basic_small_pool();
let res = local_pool.free_element(POOL_MAX_SIZE + 1);
let res = local_pool.free_element(POOL_MAX_SIZE + 1, |_| {});
assert!(res.is_err());
assert_eq!(
res.unwrap_err(),
@ -771,7 +882,7 @@ mod tests {
fn test_free_element_too_large() {
let mut local_pool = basic_small_pool();
// Try to request a slot which is too large
let res = local_pool.free_element(20);
let res = local_pool.free_element(20, |_| {});
assert!(res.is_err());
assert_eq!(res.unwrap_err(), StoreError::DataTooLarge(20));
}
@ -813,7 +924,7 @@ mod tests {
let test_buf: [u8; 16] = [0; 16];
let addr = local_pool.add(&test_buf).expect("Adding data failed");
let mut rw_guard = PoolRwGuard::new(&mut local_pool, addr);
let _ = rw_guard.modify().expect("modify failed");
rw_guard.update(&mut |_| {}).expect("modify failed");
drop(rw_guard);
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
}
@ -824,7 +935,7 @@ mod tests {
let test_buf: [u8; 16] = [0; 16];
let addr = local_pool.add(&test_buf).expect("Adding data failed");
let mut rw_guard = local_pool.modify_with_guard(addr);
let _ = rw_guard.modify().expect("modify failed");
rw_guard.update(&mut |_| {}).expect("modify failed");
drop(rw_guard);
assert!(!local_pool.has_element_at(&addr).expect("Invalid address"));
}
@ -840,13 +951,93 @@ mod tests {
let addr1 = local_pool.add(&test_buf_1).expect("Adding data failed");
let addr2 = local_pool.add(&test_buf_2).expect("Adding data failed");
let addr3 = local_pool.add(&test_buf_3).expect("Adding data failed");
let tm0_raw = local_pool.modify(&addr0).expect("Modifying data failed");
assert_eq!(tm0_raw, test_buf_0);
let tm1_raw = local_pool.modify(&addr1).expect("Modifying data failed");
assert_eq!(tm1_raw, test_buf_1);
let tm2_raw = local_pool.modify(&addr2).expect("Modifying data failed");
assert_eq!(tm2_raw, test_buf_2);
let tm3_raw = local_pool.modify(&addr3).expect("Modifying data failed");
assert_eq!(tm3_raw, test_buf_3);
local_pool
.modify(&addr0, |buf| {
assert_eq!(buf, test_buf_0);
})
.expect("Modifying data failed");
local_pool
.modify(&addr1, |buf| {
assert_eq!(buf, test_buf_1);
})
.expect("Modifying data failed");
local_pool
.modify(&addr2, |buf| {
assert_eq!(buf, test_buf_2);
})
.expect("Modifying data failed");
local_pool
.modify(&addr3, |buf| {
assert_eq!(buf, test_buf_3);
})
.expect("Modifying data failed");
}
#[test]
fn test_spills_to_higher_subpools() {
let pool_cfg = StaticPoolConfig::new(vec![(2, 8), (2, 16)], true);
let mut local_pool = StaticMemoryPool::new(pool_cfg);
local_pool.free_element(8, |_| {}).unwrap();
local_pool.free_element(8, |_| {}).unwrap();
let mut in_larger_subpool_now = local_pool.free_element(8, |_| {});
assert!(in_larger_subpool_now.is_ok());
let generic_addr = in_larger_subpool_now.unwrap();
let pool_addr = StaticPoolAddr::from(generic_addr);
assert_eq!(pool_addr.pool_idx, 1);
assert_eq!(pool_addr.packet_idx, 0);
assert!(local_pool.has_element_at(&generic_addr).unwrap());
in_larger_subpool_now = local_pool.free_element(8, |_| {});
assert!(in_larger_subpool_now.is_ok());
let generic_addr = in_larger_subpool_now.unwrap();
let pool_addr = StaticPoolAddr::from(generic_addr);
assert_eq!(pool_addr.pool_idx, 1);
assert_eq!(pool_addr.packet_idx, 1);
assert!(local_pool.has_element_at(&generic_addr).unwrap());
}
#[test]
fn test_spillage_fails_as_well() {
let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 16)], true);
let mut local_pool = StaticMemoryPool::new(pool_cfg);
local_pool.free_element(8, |_| {}).unwrap();
local_pool.free_element(8, |_| {}).unwrap();
let should_fail = local_pool.free_element(8, |_| {});
assert!(should_fail.is_err());
if let Err(err) = should_fail {
assert_eq!(err, StoreError::StoreFull(1));
} else {
panic!("unexpected store address");
}
}
#[test]
fn test_spillage_works_across_multiple_subpools() {
let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 12), (1, 16)], true);
let mut local_pool = StaticMemoryPool::new(pool_cfg);
local_pool.free_element(8, |_| {}).unwrap();
local_pool.free_element(12, |_| {}).unwrap();
let in_larger_subpool_now = local_pool.free_element(8, |_| {});
assert!(in_larger_subpool_now.is_ok());
let generic_addr = in_larger_subpool_now.unwrap();
let pool_addr = StaticPoolAddr::from(generic_addr);
assert_eq!(pool_addr.pool_idx, 2);
assert_eq!(pool_addr.packet_idx, 0);
assert!(local_pool.has_element_at(&generic_addr).unwrap());
}
#[test]
fn test_spillage_fails_across_multiple_subpools() {
let pool_cfg = StaticPoolConfig::new(vec![(1, 8), (1, 12), (1, 16)], true);
let mut local_pool = StaticMemoryPool::new(pool_cfg);
local_pool.free_element(8, |_| {}).unwrap();
local_pool.free_element(12, |_| {}).unwrap();
local_pool.free_element(16, |_| {}).unwrap();
let should_fail = local_pool.free_element(8, |_| {});
assert!(should_fail.is_err());
if let Err(err) = should_fail {
assert_eq!(err, StoreError::StoreFull(2));
} else {
panic!("unexpected store address");
}
}
}

View File

@ -390,7 +390,7 @@ mod alloc_mod {
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pool::{PoolProviderMemInPlaceWithGuards, SharedStaticMemoryPool, StoreAddr};
use crate::pool::{PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr};
use crate::pus::verification::{
StdVerifReporterWithSender, TcStateAccepted, VerificationToken,
};
@ -399,7 +399,7 @@ pub mod std_mod {
EcssTmSenderCore, EcssTmtcError, GenericRecvError, GenericSendError, PusTmWrapper,
TryRecvTmtcError,
};
use crate::tmtc::tm_helper::SharedTmStore;
use crate::tmtc::tm_helper::SharedTmPool;
use crate::ChannelId;
use alloc::boxed::Box;
use alloc::vec::Vec;
@ -441,14 +441,14 @@ pub mod std_mod {
}
#[derive(Clone)]
pub struct MpscTmInStoreSender {
pub struct MpscTmInSharedPoolSender {
id: ChannelId,
name: &'static str,
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
sender: mpsc::Sender<StoreAddr>,
}
impl EcssChannel for MpscTmInStoreSender {
impl EcssChannel for MpscTmInSharedPoolSender {
fn id(&self) -> ChannelId {
self.id
}
@ -458,7 +458,7 @@ pub mod std_mod {
}
}
impl MpscTmInStoreSender {
impl MpscTmInSharedPoolSender {
pub fn send_direct_tm(&self, tm: PusTmCreator) -> Result<(), EcssTmtcError> {
let addr = self.shared_tm_store.add_pus_tm(&tm)?;
self.sender
@ -467,7 +467,7 @@ pub mod std_mod {
}
}
impl EcssTmSenderCore for MpscTmInStoreSender {
impl EcssTmSenderCore for MpscTmInSharedPoolSender {
fn send_tm(&self, tm: PusTmWrapper) -> Result<(), EcssTmtcError> {
match tm {
PusTmWrapper::InStore(addr) => {
@ -479,11 +479,11 @@ pub mod std_mod {
}
}
impl MpscTmInStoreSender {
impl MpscTmInSharedPoolSender {
pub fn new(
id: ChannelId,
name: &'static str,
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
sender: mpsc::Sender<StoreAddr>,
) -> Self {
Self {
@ -582,7 +582,7 @@ pub mod std_mod {
pub struct CrossbeamTmInStoreSender {
id: ChannelId,
name: &'static str,
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
sender: crossbeam_channel::Sender<StoreAddr>,
}
@ -590,7 +590,7 @@ pub mod std_mod {
pub fn new(
id: ChannelId,
name: &'static str,
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
sender: crossbeam_channel::Sender<StoreAddr>,
) -> Self {
Self {
@ -789,12 +789,15 @@ pub mod std_mod {
.shared_tc_store
.write()
.map_err(|_| PusPacketHandlingError::EcssTmtc(EcssTmtcError::StoreLock))?;
let tc_guard = tc_pool.read_with_guard(addr);
let tc_raw = tc_guard.read().unwrap();
if tc_raw.len() > self.pus_buf.len() {
return Err(PusPacketHandlingError::PusPacketTooLarge(tc_raw.len()));
let tc_size = tc_pool
.len_of_data(&addr)
.map_err(|e| PusPacketHandlingError::EcssTmtc(EcssTmtcError::Store(e)))?;
if tc_size > self.pus_buf.len() {
return Err(PusPacketHandlingError::PusPacketTooLarge(tc_size));
}
self.pus_buf[0..tc_raw.len()].copy_from_slice(tc_raw);
let tc_guard = tc_pool.read_with_guard(addr);
// TODO: Proper error handling.
tc_guard.read(&mut self.pus_buf[0..tc_size]).unwrap();
Ok(())
}
}
@ -947,19 +950,18 @@ pub mod tests {
use spacepackets::CcsdsPacket;
use crate::pool::{
PoolProviderMemInPlace, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig,
StoreAddr,
PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig, StoreAddr,
};
use crate::pus::verification::RequestId;
use crate::tmtc::tm_helper::SharedTmStore;
use crate::tmtc::tm_helper::SharedTmPool;
use super::verification::{
TcStateAccepted, VerificationReporterCfg, VerificationReporterWithSender, VerificationToken,
};
use super::{
EcssTcAndToken, EcssTcInSharedStoreConverter, EcssTcInVecConverter, MpscTcReceiver,
MpscTmAsVecSender, MpscTmInStoreSender, PusPacketHandlerResult, PusPacketHandlingError,
PusServiceHelper, TcInMemory,
MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusPacketHandlingError, PusServiceHelper, TcInMemory,
};
pub const TEST_APID: u16 = 0x101;
@ -1003,7 +1005,7 @@ pub mod tests {
pus_buf: [u8; 2048],
tm_buf: [u8; 2048],
tc_pool: SharedStaticMemoryPool,
tm_pool: SharedTmStore,
tm_pool: SharedTmPool,
tc_sender: mpsc::Sender<EcssTcAndToken>,
tm_receiver: mpsc::Receiver<StoreAddr>,
verification_handler: VerificationReporterWithSender,
@ -1015,21 +1017,25 @@ pub mod tests {
///
/// The PUS service handler is instantiated with a [EcssTcInStoreConverter].
pub fn new() -> (Self, PusServiceHelper<EcssTcInSharedStoreConverter>) {
let pool_cfg = StaticPoolConfig::new(vec![(16, 16), (8, 32), (4, 64)]);
let pool_cfg = StaticPoolConfig::new(vec![(16, 16), (8, 32), (4, 64)], false);
let tc_pool = StaticMemoryPool::new(pool_cfg.clone());
let tm_pool = StaticMemoryPool::new(pool_cfg);
let shared_tc_pool = SharedStaticMemoryPool::new(RwLock::new(tc_pool));
let shared_tm_pool = SharedTmStore::new(tm_pool);
let shared_tm_pool = SharedTmPool::new(tm_pool);
let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel();
let verif_sender =
MpscTmInStoreSender::new(0, "verif_sender", shared_tm_pool.clone(), tm_tx.clone());
let verif_sender = MpscTmInSharedPoolSender::new(
0,
"verif_sender",
shared_tm_pool.clone(),
tm_tx.clone(),
);
let verif_cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
let verification_handler =
VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender));
let test_srv_tm_sender =
MpscTmInStoreSender::new(0, "TEST_SENDER", shared_tm_pool.clone(), tm_tx);
MpscTmInSharedPoolSender::new(0, "TEST_SENDER", shared_tm_pool.clone(), tm_tx);
let test_srv_tc_receiver = MpscTcReceiver::new(0, "TEST_RECEIVER", test_srv_tc_rx);
let in_store_converter =
EcssTcInSharedStoreConverter::new(shared_tc_pool.clone(), 2048);
@ -1073,9 +1079,9 @@ pub mod tests {
let next_msg = self.tm_receiver.try_recv();
assert!(next_msg.is_ok());
let tm_addr = next_msg.unwrap();
let tm_pool = self.tm_pool.shared_pool.read().unwrap();
let tm_raw = tm_pool.read(&tm_addr).unwrap();
self.tm_buf[0..tm_raw.len()].copy_from_slice(tm_raw);
let tm_pool = self.tm_pool.0.read().unwrap();
let tm_raw = tm_pool.read_as_vec(&tm_addr).unwrap();
self.tm_buf[0..tm_raw.len()].copy_from_slice(&tm_raw);
PusTmReader::new(&self.tm_buf, 7).unwrap().0
}
@ -1091,9 +1097,9 @@ pub mod tests {
let next_msg = self.tm_receiver.try_recv();
assert!(next_msg.is_ok());
let tm_addr = next_msg.unwrap();
let tm_pool = self.tm_pool.shared_pool.read().unwrap();
let tm_raw = tm_pool.read(&tm_addr).unwrap();
let tm = PusTmReader::new(tm_raw, 7).unwrap().0;
let tm_pool = self.tm_pool.0.read().unwrap();
let tm_raw = tm_pool.read_as_vec(&tm_addr).unwrap();
let tm = PusTmReader::new(&tm_raw, 7).unwrap().0;
assert_eq!(PusPacket::service(&tm), 1);
assert_eq!(PusPacket::subservice(&tm), subservice);
assert_eq!(tm.apid(), TEST_APID);

View File

@ -16,7 +16,7 @@ use spacepackets::{ByteConversionError, CcsdsPacket};
#[cfg(feature = "std")]
use std::error::Error;
use crate::pool::{PoolProviderMemInPlace, StoreError};
use crate::pool::{PoolProvider, StoreError};
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
@ -238,13 +238,11 @@ impl Error for ScheduleError {
}
}
pub trait PusSchedulerInterface {
/// Generic trait for scheduler objects which are able to schedule ECSS PUS C packets.
pub trait PusSchedulerProvider {
type TimeProvider: CcsdsTimeProvider + TimeReader;
fn reset(
&mut self,
store: &mut (impl PoolProviderMemInPlace + ?Sized),
) -> Result<(), StoreError>;
fn reset(&mut self, store: &mut (impl PoolProvider + ?Sized)) -> Result<(), StoreError>;
fn is_enabled(&self) -> bool;
@ -267,7 +265,7 @@ pub trait PusSchedulerInterface {
fn insert_wrapped_tc<TimeProvider>(
&mut self,
pus_tc: &(impl IsPusTelecommand + PusPacket + GenericPusTcSecondaryHeader),
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> {
if PusPacket::service(pus_tc) != 11 {
return Err(ScheduleError::WrongService(PusPacket::service(pus_tc)));
@ -293,7 +291,7 @@ pub trait PusSchedulerInterface {
&mut self,
time_stamp: UnixTimestamp,
tc: &[u8],
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> {
let check_tc = PusTcReader::new(tc)?;
if PusPacket::service(&check_tc.0) == 11 && PusPacket::subservice(&check_tc.0) == 4 {
@ -343,7 +341,7 @@ pub fn generate_insert_telecommand_app_data(
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use super::*;
use crate::pool::{PoolProviderMemInPlace, StoreAddr, StoreError};
use crate::pool::{PoolProvider, StoreAddr, StoreError};
use alloc::collections::btree_map::{Entry, Range};
use alloc::collections::BTreeMap;
use alloc::vec;
@ -379,7 +377,7 @@ pub mod alloc_mod {
/// This is the core data structure for scheduling PUS telecommands with [alloc] support.
///
/// It is assumed that the actual telecommand data is stored in a separate TC pool offering
/// a [crate::pool::PoolProviderMemInPlace] API. This data structure just tracks the store
/// a [crate::pool::PoolProvider] API. This data structure just tracks the store
/// addresses and their release times and offers a convenient API to insert and release
/// telecommands and perform other functionality specified by the ECSS standard in section 6.11.
/// The time is tracked as a [spacepackets::time::UnixTimestamp] but the only requirement to
@ -413,6 +411,8 @@ pub mod alloc_mod {
/// * `time_margin` - This time margin is used when inserting new telecommands into the
/// schedule. If the release time of a new telecommand is earlier than the time margin
/// added to the current time, it will not be inserted into the schedule.
/// * `tc_buf_size` - Buffer for temporary storage of telecommand packets. This buffer
/// should be large enough to accomodate the largest expected TC packets.
pub fn new(init_current_time: UnixTimestamp, time_margin: Duration) -> Self {
PusScheduler {
tc_map: Default::default(),
@ -476,7 +476,7 @@ pub mod alloc_mod {
&mut self,
time_stamp: UnixTimestamp,
tc: &[u8],
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> {
let check_tc = PusTcReader::new(tc)?;
if PusPacket::service(&check_tc.0) == 11 && PusPacket::subservice(&check_tc.0) == 4 {
@ -499,7 +499,7 @@ pub mod alloc_mod {
pub fn insert_wrapped_tc_cds_short(
&mut self,
pus_tc: &PusTc,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> {
self.insert_wrapped_tc::<cds::TimeProvider>(pus_tc, pool)
}
@ -509,7 +509,7 @@ pub mod alloc_mod {
pub fn insert_wrapped_tc_cds_long(
&mut self,
pus_tc: &PusTc,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<TcInfo, ScheduleError> {
self.insert_wrapped_tc::<cds::TimeProvider<DaysLen24Bits>>(pus_tc, pool)
}
@ -525,7 +525,7 @@ pub mod alloc_mod {
pub fn delete_by_time_filter<TimeProvider: CcsdsTimeProvider + Clone>(
&mut self,
time_window: TimeWindow<TimeProvider>,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<u64, (u64, StoreError)> {
let range = self.retrieve_by_time_filter(time_window);
let mut del_packets = 0;
@ -555,7 +555,7 @@ pub mod alloc_mod {
/// the last deletion will be supplied in addition to the number of deleted commands.
pub fn delete_all(
&mut self,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<u64, (u64, StoreError)> {
self.delete_by_time_filter(TimeWindow::<cds::TimeProvider>::new_select_all(), pool)
}
@ -613,7 +613,7 @@ pub mod alloc_mod {
pub fn delete_by_request_id_and_from_pool(
&mut self,
req_id: &RequestId,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> Result<bool, StoreError> {
if let DeletionResult::WithStoreDeletion(v) =
self.delete_by_request_id_internal_with_store_deletion(req_id, pool)
@ -645,7 +645,7 @@ pub mod alloc_mod {
fn delete_by_request_id_internal_with_store_deletion(
&mut self,
req_id: &RequestId,
pool: &mut (impl PoolProviderMemInPlace + ?Sized),
pool: &mut (impl PoolProvider + ?Sized),
) -> DeletionResult {
let mut idx_found = None;
for time_bucket in &mut self.tc_map {
@ -675,7 +675,8 @@ pub mod alloc_mod {
/// Utility method which calls [Self::telecommands_to_release] and then calls a releaser
/// closure for each telecommand which should be released. This function will also delete
/// the telecommands from the holding store after calling the release closure if the user
/// returns [true] from the release closure.
/// returns [true] from the release closure. A buffer must be provided to hold the
/// telecommands for the release process.
///
/// # Arguments
///
@ -685,18 +686,55 @@ pub mod alloc_mod {
/// note that returning false might lead to memory leaks if the TC is not cleared from
/// the store in some other way.
/// * `tc_store` - The holding store of the telecommands.
/// * `tc_buf` - Buffer to hold each telecommand being released.
pub fn release_telecommands_with_buffer<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
&mut self,
releaser: R,
tc_store: &mut (impl PoolProvider + ?Sized),
tc_buf: &mut [u8],
) -> Result<u64, (u64, StoreError)> {
self.release_telecommands_internal(releaser, tc_store, Some(tc_buf))
}
/// This functions is almost identical to [Self::release_telecommands_with_buffer] but does
/// not require a user provided TC buffer because it will always use the
/// [PoolProvider::read_as_vec] API to read the TC packets.
///
/// However, this might also perform frequent allocations for all telecommands being
/// released.
pub fn release_telecommands<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
&mut self,
releaser: R,
tc_store: &mut (impl PoolProvider + ?Sized),
) -> Result<u64, (u64, StoreError)> {
self.release_telecommands_internal(releaser, tc_store, None)
}
fn release_telecommands_internal<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
&mut self,
mut releaser: R,
tc_store: &mut (impl PoolProviderMemInPlace + ?Sized),
tc_store: &mut (impl PoolProvider + ?Sized),
mut tc_buf: Option<&mut [u8]>,
) -> Result<u64, (u64, StoreError)> {
let tcs_to_release = self.telecommands_to_release();
let mut released_tcs = 0;
let mut store_error = Ok(());
for tc in tcs_to_release {
for info in tc.1 {
let tc = tc_store.read(&info.addr).map_err(|e| (released_tcs, e))?;
let should_delete = releaser(self.enabled, info, tc);
let should_delete = match tc_buf.as_mut() {
Some(buf) => {
tc_store
.read(&info.addr, buf)
.map_err(|e| (released_tcs, e))?;
releaser(self.enabled, info, buf)
}
None => {
let tc = tc_store
.read_as_vec(&info.addr)
.map_err(|e| (released_tcs, e))?;
releaser(self.enabled, info, &tc)
}
};
released_tcs += 1;
if should_delete {
let res = tc_store.delete(info.addr);
@ -721,16 +759,17 @@ pub mod alloc_mod {
pub fn release_telecommands_no_deletion<R: FnMut(bool, &TcInfo, &[u8])>(
&mut self,
mut releaser: R,
tc_store: &(impl PoolProviderMemInPlace + ?Sized),
tc_store: &(impl PoolProvider + ?Sized),
tc_buf: &mut [u8],
) -> Result<Vec<TcInfo>, (Vec<TcInfo>, StoreError)> {
let tcs_to_release = self.telecommands_to_release();
let mut released_tcs = Vec::new();
for tc in tcs_to_release {
for info in tc.1 {
let tc = tc_store
.read(&info.addr)
tc_store
.read(&info.addr, tc_buf)
.map_err(|e| (released_tcs.clone(), e))?;
releaser(self.is_enabled(), info, tc);
releaser(self.is_enabled(), info, tc_buf);
released_tcs.push(*info);
}
}
@ -744,7 +783,7 @@ pub mod alloc_mod {
}
}
impl PusSchedulerInterface for PusScheduler {
impl PusSchedulerProvider for PusScheduler {
type TimeProvider = cds::TimeProvider;
/// This will disable the scheduler and clear the schedule as specified in 6.11.4.4.
@ -753,10 +792,7 @@ pub mod alloc_mod {
/// The holding store for the telecommands needs to be passed so all the stored telecommands
/// can be deleted to avoid a memory leak. If at last one deletion operation fails, the error
/// will be returned but the method will still try to delete all the commands in the schedule.
fn reset(
&mut self,
store: &mut (impl PoolProviderMemInPlace + ?Sized),
) -> Result<(), StoreError> {
fn reset(&mut self, store: &mut (impl PoolProvider + ?Sized)) -> Result<(), StoreError> {
self.enabled = false;
let mut deletion_ok = Ok(());
for tc_lists in &mut self.tc_map {
@ -814,8 +850,7 @@ pub mod alloc_mod {
mod tests {
use super::*;
use crate::pool::{
PoolProviderMemInPlace, StaticMemoryPool, StaticPoolAddr, StaticPoolConfig, StoreAddr,
StoreError,
PoolProvider, StaticMemoryPool, StaticPoolAddr, StaticPoolConfig, StoreAddr, StoreError,
};
use alloc::collections::btree_map::Range;
use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};
@ -911,7 +946,7 @@ mod tests {
#[test]
fn test_reset() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
@ -1063,7 +1098,7 @@ mod tests {
}
#[test]
fn test_release_telecommands() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
@ -1088,8 +1123,9 @@ mod tests {
// test 1: too early, no tcs
scheduler.update_time(UnixTimestamp::new_only_seconds(99));
let mut tc_buf: [u8; 128] = [0; 128];
scheduler
.release_telecommands(&mut test_closure_1, &mut pool)
.release_telecommands_with_buffer(&mut test_closure_1, &mut pool, &mut tc_buf)
.expect("deletion failed");
// test 2: exact time stamp of tc, releases 1 tc
@ -1111,7 +1147,7 @@ mod tests {
scheduler.update_time(UnixTimestamp::new_only_seconds(206));
released = scheduler
.release_telecommands(&mut test_closure_2, &mut pool)
.release_telecommands_with_buffer(&mut test_closure_2, &mut pool, &mut tc_buf)
.expect("deletion failed");
assert_eq!(released, 1);
// TC is deleted.
@ -1128,7 +1164,7 @@ mod tests {
#[test]
fn release_multi_with_same_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
@ -1157,9 +1193,10 @@ mod tests {
// test 1: too early, no tcs
scheduler.update_time(UnixTimestamp::new_only_seconds(99));
let mut tc_buf: [u8; 128] = [0; 128];
let mut released = scheduler
.release_telecommands(&mut test_closure, &mut pool)
.release_telecommands_with_buffer(&mut test_closure, &mut pool, &mut tc_buf)
.expect("deletion failed");
assert_eq!(released, 0);
@ -1185,7 +1222,7 @@ mod tests {
#[test]
fn release_with_scheduler_disabled() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
@ -1209,11 +1246,13 @@ mod tests {
true
};
let mut tc_buf: [u8; 128] = [0; 128];
// test 1: too early, no tcs
scheduler.update_time(UnixTimestamp::new_only_seconds(99));
scheduler
.release_telecommands(&mut test_closure_1, &mut pool)
.release_telecommands_with_buffer(&mut test_closure_1, &mut pool, &mut tc_buf)
.expect("deletion failed");
// test 2: exact time stamp of tc, releases 1 tc
@ -1253,7 +1292,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 32] = [0; 32];
let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
@ -1267,8 +1306,9 @@ mod tests {
assert!(pool.has_element_at(&tc_info_0.addr()).unwrap());
let data = pool.read(&tc_info_0.addr()).unwrap();
let check_tc = PusTcReader::new(data).expect("incorrect Pus tc raw data");
let mut read_buf: [u8; 64] = [0; 64];
pool.read(&tc_info_0.addr(), &mut read_buf).unwrap();
let check_tc = PusTcReader::new(&read_buf).expect("incorrect Pus tc raw data");
assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
assert_eq!(scheduler.num_scheduled_telecommands(), 1);
@ -1289,8 +1329,9 @@ mod tests {
.release_telecommands(&mut test_closure, &mut pool)
.unwrap();
let data = pool.read(&addr_vec[0]).unwrap();
let check_tc = PusTcReader::new(data).expect("incorrect Pus tc raw data");
let read_len = pool.read(&addr_vec[0], &mut read_buf).unwrap();
let check_tc = PusTcReader::new(&read_buf).expect("incorrect Pus tc raw data");
assert_eq!(read_len, check_tc.1);
assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
}
@ -1299,7 +1340,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 32] = [0; 32];
let tc = scheduled_tc(UnixTimestamp::new_only_seconds(100), &mut buf);
@ -1313,8 +1354,9 @@ mod tests {
assert!(pool.has_element_at(&info.addr).unwrap());
let data = pool.read(&info.addr).unwrap();
let check_tc = PusTcReader::new(data).expect("incorrect Pus tc raw data");
let read_len = pool.read(&info.addr, &mut buf).unwrap();
let check_tc = PusTcReader::new(&buf).expect("incorrect Pus tc raw data");
assert_eq!(read_len, check_tc.1);
assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
assert_eq!(scheduler.num_scheduled_telecommands(), 1);
@ -1331,12 +1373,15 @@ mod tests {
false
};
let mut tc_buf: [u8; 64] = [0; 64];
scheduler
.release_telecommands(&mut test_closure, &mut pool)
.release_telecommands_with_buffer(&mut test_closure, &mut pool, &mut tc_buf)
.unwrap();
let data = pool.read(&addr_vec[0]).unwrap();
let check_tc = PusTcReader::new(data).expect("incorrect PUS tc raw data");
let read_len = pool.read(&addr_vec[0], &mut buf).unwrap();
let check_tc = PusTcReader::new(&buf).expect("incorrect PUS tc raw data");
assert_eq!(read_len, check_tc.1);
assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
}
@ -1345,7 +1390,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 32] = [0; 32];
let tc = wrong_tc_service(UnixTimestamp::new_only_seconds(100), &mut buf);
@ -1368,7 +1413,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 32] = [0; 32];
let tc = wrong_tc_subservice(UnixTimestamp::new_only_seconds(100), &mut buf);
@ -1390,7 +1435,7 @@ mod tests {
fn insert_wrapped_tc_faulty_app_data() {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let tc = invalid_time_tagged_cmd();
let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
assert!(insert_res.is_err());
@ -1405,7 +1450,7 @@ mod tests {
fn insert_doubly_wrapped_time_tagged_cmd() {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 64] = [0; 64];
let tc = double_wrapped_time_tagged_tc(UnixTimestamp::new_only_seconds(50), &mut buf);
let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
@ -1441,7 +1486,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut buf: [u8; 32] = [0; 32];
@ -1465,7 +1510,7 @@ mod tests {
#[test]
fn test_store_error_propagation_release() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut buf: [u8; 32] = [0; 32];
@ -1500,7 +1545,7 @@ mod tests {
#[test]
fn test_store_error_propagation_reset() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut buf: [u8; 32] = [0; 32];
@ -1524,7 +1569,7 @@ mod tests {
#[test]
fn test_delete_by_req_id_simple_retrieve_addr() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut buf: [u8; 32] = [0; 32];
@ -1543,7 +1588,7 @@ mod tests {
#[test]
fn test_delete_by_req_id_simple_delete_all() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut buf: [u8; 32] = [0; 32];
@ -1562,7 +1607,7 @@ mod tests {
#[test]
fn test_delete_by_req_id_complex() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut buf: [u8; 32] = [0; 32];
@ -1609,7 +1654,7 @@ mod tests {
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(1, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(1, 64)], false));
let mut buf: [u8; 32] = [0; 32];
// Store is full after this.
@ -1648,7 +1693,7 @@ mod tests {
#[test]
fn test_time_window_retrieval_select_all() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1679,7 +1724,7 @@ mod tests {
#[test]
fn test_time_window_retrieval_select_from_stamp() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1710,7 +1755,7 @@ mod tests {
#[test]
fn test_time_window_retrieval_select_to_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1741,7 +1786,7 @@ mod tests {
#[test]
fn test_time_window_retrieval_select_from_time_to_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1776,7 +1821,7 @@ mod tests {
#[test]
fn test_deletion_all() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1803,7 +1848,7 @@ mod tests {
#[test]
fn test_deletion_from_start_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1824,7 +1869,7 @@ mod tests {
#[test]
fn test_deletion_to_end_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1846,7 +1891,7 @@ mod tests {
#[test]
fn test_deletion_from_start_time_to_end_time() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
let cmd_out_of_range_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
@ -1875,7 +1920,7 @@ mod tests {
#[test]
fn test_release_without_deletion() {
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)]));
let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
let mut scheduler =
PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
@ -1903,8 +1948,9 @@ mod tests {
scheduler.update_time(UnixTimestamp::new_only_seconds(205));
let mut tc_buf: [u8; 64] = [0; 64];
let tc_info_vec = scheduler
.release_telecommands_no_deletion(&mut test_closure_1, &pool)
.release_telecommands_no_deletion(&mut test_closure_1, &pool, &mut tc_buf)
.expect("deletion failed");
assert_eq!(tc_info_vec[0], tc_info_0);
assert_eq!(tc_info_vec[1], tc_info_1);

View File

@ -1,6 +1,6 @@
use super::scheduler::PusSchedulerInterface;
use super::scheduler::PusSchedulerProvider;
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
use crate::pool::PoolProviderMemInPlace;
use crate::pool::PoolProvider;
use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError};
use alloc::string::ToString;
use spacepackets::ecss::{scheduling, PusPacket};
@ -16,13 +16,13 @@ use spacepackets::time::cds::TimeProvider;
/// telecommands when applicable.
pub struct PusService11SchedHandler<
TcInMemConverter: EcssTcInMemConverter,
Scheduler: PusSchedulerInterface,
PusScheduler: PusSchedulerProvider,
> {
pub service_helper: PusServiceHelper<TcInMemConverter>,
scheduler: Scheduler,
scheduler: PusScheduler,
}
impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerInterface>
impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerProvider>
PusService11SchedHandler<TcInMemConverter, Scheduler>
{
pub fn new(service_helper: PusServiceHelper<TcInMemConverter>, scheduler: Scheduler) -> Self {
@ -42,7 +42,7 @@ impl<TcInMemConverter: EcssTcInMemConverter, Scheduler: PusSchedulerInterface>
pub fn handle_one_tc(
&mut self,
sched_tc_pool: &mut (impl PoolProviderMemInPlace + ?Sized),
sched_tc_pool: &mut (impl PoolProvider + ?Sized),
) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
@ -173,7 +173,7 @@ mod tests {
use crate::pool::{StaticMemoryPool, StaticPoolConfig};
use crate::pus::tests::TEST_APID;
use crate::pus::{
scheduler::{self, PusSchedulerInterface, TcInfo},
scheduler::{self, PusSchedulerProvider, TcInfo},
tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness},
verification::{RequestId, TcStateAccepted, VerificationToken},
EcssTcInSharedStoreConverter,
@ -201,7 +201,7 @@ mod tests {
impl Pus11HandlerWithStoreTester {
pub fn new() -> Self {
let test_scheduler = TestScheduler::default();
let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)]);
let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false);
let sched_tc_pool = StaticMemoryPool::new(pool_cfg.clone());
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new();
Self {
@ -232,12 +232,12 @@ mod tests {
inserted_tcs: VecDeque<TcInfo>,
}
impl PusSchedulerInterface for TestScheduler {
impl PusSchedulerProvider for TestScheduler {
type TimeProvider = cds::TimeProvider;
fn reset(
&mut self,
_store: &mut (impl crate::pool::PoolProviderMemInPlace + ?Sized),
_store: &mut (impl crate::pool::PoolProvider + ?Sized),
) -> Result<(), crate::pool::StoreError> {
self.reset_count += 1;
Ok(())

View File

@ -15,11 +15,11 @@
//! ```
//! use std::sync::{Arc, mpsc, RwLock};
//! use std::time::Duration;
//! use satrs_core::pool::{PoolProviderMemInPlaceWithGuards, StaticMemoryPool, StaticPoolConfig};
//! use satrs_core::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
//! use satrs_core::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
//! use satrs_core::seq_count::SeqCountProviderSimple;
//! use satrs_core::pus::MpscTmInStoreSender;
//! use satrs_core::tmtc::tm_helper::SharedTmStore;
//! use satrs_core::pus::MpscTmInSharedPoolSender;
//! use satrs_core::tmtc::tm_helper::SharedTmPool;
//! use spacepackets::ecss::PusPacket;
//! use spacepackets::SpHeader;
//! use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader};
@ -28,12 +28,12 @@
//! const EMPTY_STAMP: [u8; 7] = [0; 7];
//! const TEST_APID: u16 = 0x02;
//!
//! let pool_cfg = StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)]);
//! let pool_cfg = StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false);
//! let tm_pool = StaticMemoryPool::new(pool_cfg.clone());
//! let shared_tm_store = SharedTmStore::new(tm_pool);
//! let shared_tm_store = SharedTmPool::new(tm_pool);
//! let tm_store = shared_tm_store.clone_backing_pool();
//! let (verif_tx, verif_rx) = mpsc::channel();
//! let sender = MpscTmInStoreSender::new(0, "Test Sender", shared_tm_store, verif_tx);
//! let sender = MpscTmInSharedPoolSender::new(0, "Test Sender", shared_tm_store, verif_tx);
//! let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
//! let mut reporter = VerificationReporterWithSender::new(&cfg , Box::new(sender));
//!
@ -56,9 +56,7 @@
//! {
//! let mut rg = tm_store.write().expect("Error locking shared pool");
//! let store_guard = rg.read_with_guard(addr);
//! let slice = store_guard.read().expect("Error reading TM slice");
//! tm_len = slice.len();
//! tm_buf[0..tm_len].copy_from_slice(slice);
//! tm_len = store_guard.read(&mut tm_buf).expect("Error reading TM slice");
//! }
//! let (pus_tm, _) = PusTmReader::new(&tm_buf[0..tm_len], 7)
//! .expect("Error reading verification TM");
@ -1325,15 +1323,15 @@ mod std_mod {
#[cfg(test)]
mod tests {
use crate::pool::{PoolProviderMemInPlaceWithGuards, StaticMemoryPool, StaticPoolConfig};
use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
use crate::pus::tests::CommonTmInfo;
use crate::pus::verification::{
EcssTmSenderCore, EcssTmtcError, FailParams, FailParamsWithStep, RequestId, TcStateNone,
VerificationReporter, VerificationReporterCfg, VerificationReporterWithSender,
VerificationToken,
};
use crate::pus::{EcssChannel, MpscTmInStoreSender, PusTmWrapper};
use crate::tmtc::tm_helper::SharedTmStore;
use crate::pus::{EcssChannel, MpscTmInSharedPoolSender, PusTmWrapper};
use crate::tmtc::tm_helper::SharedTmPool;
use crate::ChannelId;
use alloc::boxed::Box;
use alloc::format;
@ -1486,10 +1484,11 @@ mod tests {
#[test]
fn test_mpsc_verif_send_sync() {
let pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(8, 8)]));
let shared_tm_store = SharedTmStore::new(pool);
let pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(8, 8)], false));
let shared_tm_store = SharedTmPool::new(pool);
let (tx, _) = mpsc::channel();
let mpsc_verif_sender = MpscTmInStoreSender::new(0, "verif_sender", shared_tm_store, tx);
let mpsc_verif_sender =
MpscTmInSharedPoolSender::new(0, "verif_sender", shared_tm_store, tx);
is_send(&mpsc_verif_sender);
}
@ -1546,7 +1545,7 @@ mod tests {
let mut sender = TestSender::default();
let fail_code = EcssEnumU16::new(2);
let fail_params = FailParams::new(Some(stamp_buf.as_slice()), &fail_code, None);
b.vr.acceptance_failure(tok, &mut sender, fail_params)
b.vr.acceptance_failure(tok, &sender, fail_params)
.expect("Sending acceptance success failed");
acceptance_fail_check(&mut sender, tok.req_id, stamp_buf);
}
@ -1681,12 +1680,10 @@ mod tests {
);
let accepted_token =
b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP))
b.vr.acceptance_success(tok, &sender, Some(&EMPTY_STAMP))
.expect("Sending acceptance success failed");
let empty =
b.vr.start_failure(accepted_token, &mut sender, fail_params)
.expect("Start failure failure");
assert_eq!(empty, ());
b.vr.start_failure(accepted_token, &mut sender, fail_params)
.expect("Start failure failure");
start_fail_check(&mut sender, tok.req_id, fail_data_raw);
}
@ -1778,23 +1775,23 @@ mod tests {
let mut sender = TestSender::default();
let accepted_token = b
.rep()
.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP))
.acceptance_success(tok, &sender, Some(&EMPTY_STAMP))
.expect("Sending acceptance success failed");
let started_token = b
.rep()
.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0]))
.start_success(accepted_token, &sender, Some(&[0, 1, 0, 1, 0, 1, 0]))
.expect("Sending start success failed");
b.rep()
.step_success(
&started_token,
&mut sender,
&sender,
Some(&EMPTY_STAMP),
EcssEnumU8::new(0),
)
.expect("Sending step 0 success failed");
b.vr.step_success(
&started_token,
&mut sender,
&sender,
Some(&EMPTY_STAMP),
EcssEnumU8::new(1),
)
@ -2138,14 +2135,15 @@ mod tests {
}
#[test]
// TODO: maybe a bit more extensive testing, all I have time for right now
fn test_seq_count_increment() {
let pool_cfg = StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)]);
let pool_cfg =
StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false);
let tm_pool = StaticMemoryPool::new(pool_cfg.clone());
let shared_tm_store = SharedTmStore::new(tm_pool);
let shared_tm_store = SharedTmPool::new(tm_pool);
let shared_tm_pool = shared_tm_store.clone_backing_pool();
let (verif_tx, verif_rx) = mpsc::channel();
let sender = MpscTmInStoreSender::new(0, "Verification Sender", shared_tm_store, verif_tx);
let sender =
MpscTmInSharedPoolSender::new(0, "Verification Sender", shared_tm_store, verif_tx);
let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
let mut reporter = VerificationReporterWithSender::new(&cfg, Box::new(sender));
@ -2174,9 +2172,9 @@ mod tests {
{
let mut rg = shared_tm_pool.write().expect("Error locking shared pool");
let store_guard = rg.read_with_guard(addr);
let slice = store_guard.read().expect("Error reading TM slice");
tm_len = slice.len();
tm_buf[0..tm_len].copy_from_slice(slice);
tm_len = store_guard
.read(&mut tm_buf)
.expect("Error reading TM slice");
}
let (pus_tm, _) =
PusTmReader::new(&tm_buf[0..tm_len], 7).expect("Error reading verification TM");

View File

@ -8,39 +8,38 @@ pub use std_mod::*;
#[cfg(feature = "std")]
pub mod std_mod {
use crate::pool::{
PoolProviderMemInPlace, SharedStaticMemoryPool, StaticMemoryPool, StoreAddr,
};
use crate::pool::{PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StoreAddr};
use crate::pus::EcssTmtcError;
use spacepackets::ecss::tm::PusTmCreator;
use spacepackets::ecss::WritablePusPacket;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct SharedTmStore {
pub shared_pool: SharedStaticMemoryPool,
}
pub struct SharedTmPool(pub SharedStaticMemoryPool);
impl SharedTmStore {
impl SharedTmPool {
pub fn new(shared_pool: StaticMemoryPool) -> Self {
Self {
shared_pool: Arc::new(RwLock::new(shared_pool)),
}
Self(Arc::new(RwLock::new(shared_pool)))
}
pub fn clone_backing_pool(&self) -> SharedStaticMemoryPool {
self.shared_pool.clone()
self.0.clone()
}
pub fn shared_pool(&self) -> &SharedStaticMemoryPool {
&self.0
}
pub fn shared_pool_mut(&mut self) -> &mut SharedStaticMemoryPool {
&mut self.0
}
pub fn add_pus_tm(&self, pus_tm: &PusTmCreator) -> Result<StoreAddr, EcssTmtcError> {
let mut pg = self
.shared_pool
.write()
.map_err(|_| EcssTmtcError::StoreLock)?;
let (addr, buf) = pg.free_element(pus_tm.len_written())?;
pus_tm
.write_to_bytes(buf)
.expect("writing PUS TM to store failed");
let mut pg = self.0.write().map_err(|_| EcssTmtcError::StoreLock)?;
let addr = pg.free_element(pus_tm.len_written(), |buf| {
pus_tm
.write_to_bytes(buf)
.expect("writing PUS TM to store failed");
})?;
Ok(addr)
}
}

View File

@ -1,6 +1,4 @@
use satrs_core::pool::{
PoolGuard, PoolProviderMemInPlace, StaticMemoryPool, StaticPoolConfig, StoreAddr,
};
use satrs_core::pool::{PoolGuard, PoolProvider, StaticMemoryPool, StaticPoolConfig, StoreAddr};
use std::ops::DerefMut;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
@ -11,7 +9,7 @@ const DUMMY_DATA: [u8; 4] = [0, 1, 2, 3];
#[test]
fn threaded_usage() {
let pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)]);
let pool_cfg = StaticPoolConfig::new(vec![(16, 6), (32, 3), (8, 12)], false);
let shared_pool = Arc::new(RwLock::new(StaticMemoryPool::new(pool_cfg)));
let shared_clone = shared_pool.clone();
let (tx, rx): (Sender<StoreAddr>, Receiver<StoreAddr>) = mpsc::channel();
@ -27,8 +25,10 @@ fn threaded_usage() {
addr = rx.recv().expect("Receiving store address failed");
let mut pool_access = shared_clone.write().unwrap();
let pg = PoolGuard::new(pool_access.deref_mut(), addr);
let read_res = pg.read().expect("Reading failed");
assert_eq!(read_res, DUMMY_DATA);
let mut read_buf: [u8; 4] = [0; 4];
let read_bytes = pg.read(&mut read_buf).expect("Reading failed");
assert_eq!(read_buf, DUMMY_DATA);
assert_eq!(read_bytes, 4);
}
let pool_access = shared_clone.read().unwrap();
assert!(!pool_access.has_element_at(&addr).expect("Invalid address"));

View File

@ -1,15 +1,14 @@
#[cfg(feature = "crossbeam")]
//#[cfg(feature = "crossbeam")]
pub mod crossbeam_test {
use hashbrown::HashMap;
use satrs_core::pool::{
PoolProviderMemInPlace, PoolProviderMemInPlaceWithGuards, StaticMemoryPool,
StaticPoolConfig,
PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig,
};
use satrs_core::pus::verification::{
FailParams, RequestId, VerificationReporterCfg, VerificationReporterWithSender,
};
use satrs_core::pus::CrossbeamTmInStoreSender;
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};
use spacepackets::ecss::tm::PusTmReader;
use spacepackets::ecss::{EcssEnumU16, EcssEnumU8, PusPacket, WritablePusPacket};
@ -36,13 +35,14 @@ pub mod crossbeam_test {
// each reporter have an own sequence count provider.
let cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
// Shared pool object to store the verification PUS telemetry
let pool_cfg = StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)]);
let shared_tm_store = SharedTmStore::new(StaticMemoryPool::new(pool_cfg.clone()));
let pool_cfg =
StaticPoolConfig::new(vec![(10, 32), (10, 64), (10, 128), (10, 1024)], false);
let shared_tm_pool = SharedTmPool::new(StaticMemoryPool::new(pool_cfg.clone()));
let shared_tc_pool_0 = Arc::new(RwLock::new(StaticMemoryPool::new(pool_cfg)));
let shared_tc_pool_1 = shared_tc_pool_0.clone();
let (tx, rx) = crossbeam_channel::bounded(10);
let sender =
CrossbeamTmInStoreSender::new(0, "verif_sender", shared_tm_store.clone(), tx.clone());
CrossbeamTmInStoreSender::new(0, "verif_sender", shared_tm_pool.clone(), tx.clone());
let mut reporter_with_sender_0 =
VerificationReporterWithSender::new(&cfg, Box::new(sender));
let mut reporter_with_sender_1 = reporter_with_sender_0.clone();
@ -59,15 +59,21 @@ pub mod crossbeam_test {
let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
let pus_tc_0 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true);
req_id_0 = RequestId::new(&pus_tc_0);
let (addr, buf) = tc_guard.free_element(pus_tc_0.len_written()).unwrap();
pus_tc_0.write_to_bytes(buf).unwrap();
let addr = tc_guard
.free_element(pus_tc_0.len_written(), |buf| {
pus_tc_0.write_to_bytes(buf).unwrap();
})
.unwrap();
tx_tc_0.send(addr).unwrap();
let mut sph = SpHeader::tc_unseg(TEST_APID, 1, 0).unwrap();
let tc_header = PusTcSecondaryHeader::new_simple(5, 1);
let pus_tc_1 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true);
req_id_1 = RequestId::new(&pus_tc_1);
let (addr, buf) = tc_guard.free_element(pus_tc_0.len_written()).unwrap();
pus_tc_1.write_to_bytes(buf).unwrap();
let addr = tc_guard
.free_element(pus_tc_0.len_written(), |buf| {
pus_tc_1.write_to_bytes(buf).unwrap();
})
.unwrap();
tx_tc_1.send(addr).unwrap();
}
let verif_sender_0 = thread::spawn(move || {
@ -79,9 +85,7 @@ pub mod crossbeam_test {
{
let mut tc_guard = shared_tc_pool_0.write().unwrap();
let pg = tc_guard.read_with_guard(tc_addr);
let buf = pg.read().unwrap();
tc_len = buf.len();
tc_buf[0..tc_len].copy_from_slice(buf);
tc_len = pg.read(&mut tc_buf).unwrap();
}
let (_tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap();
@ -117,9 +121,7 @@ pub mod crossbeam_test {
{
let mut tc_guard = shared_tc_pool_1.write().unwrap();
let pg = tc_guard.read_with_guard(tc_addr);
let buf = pg.read().unwrap();
tc_len = buf.len();
tc_buf[0..tc_len].copy_from_slice(buf);
tc_len = pg.read(&mut tc_buf).unwrap();
}
let (tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap();
let token = reporter_with_sender_1.add_tc(&tc);
@ -145,13 +147,13 @@ pub mod crossbeam_test {
.recv_timeout(Duration::from_millis(50))
.expect("Packet reception timeout");
let tm_len;
let shared_tm_store = shared_tm_store.clone_backing_pool();
let shared_tm_store = shared_tm_pool.clone_backing_pool();
{
let mut rg = shared_tm_store.write().expect("Error locking shared pool");
let store_guard = rg.read_with_guard(verif_addr);
let slice = store_guard.read().expect("Error reading TM slice");
tm_len = slice.len();
tm_buf[0..tm_len].copy_from_slice(slice);
tm_len = store_guard
.read(&mut tm_buf)
.expect("Error reading TM slice");
}
let (pus_tm, _) =
PusTmReader::new(&tm_buf[0..tm_len], 7).expect("Error reading verification TM");

View File

@ -103,34 +103,43 @@ pub mod pool {
use super::*;
pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) {
(
StaticMemoryPool::new(StaticPoolConfig::new(vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
])),
StaticMemoryPool::new(StaticPoolConfig::new(vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
])),
StaticMemoryPool::new(StaticPoolConfig::new(
vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
],
true,
)),
StaticMemoryPool::new(StaticPoolConfig::new(
vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
],
true,
)),
)
}
pub fn create_sched_tc_pool() -> StaticMemoryPool {
StaticMemoryPool::new(StaticPoolConfig::new(vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
]))
StaticMemoryPool::new(StaticPoolConfig::new(
vec![
(30, 32),
(15, 64),
(15, 128),
(15, 256),
(15, 1024),
(15, 2048),
],
true,
))
}
}

View File

@ -17,6 +17,7 @@ use log::info;
use pus::test::create_test_service_dynamic;
use satrs_core::hal::std::tcp_server::ServerConfig;
use satrs_core::hal::std::udp_server::UdpTcServer;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
@ -37,14 +38,13 @@ use crate::pus::{PusReceiver, PusTcMpscRouter};
use crate::requests::RequestWithToken;
use crate::tcp::{SyncTcpTmSource, TcpTask};
use crate::tmtc::{
PusTcSourceProviderSharedPool, SharedTcPool, TcArgs, TmArgs, TmtcTaskDynamic, TmtcTaskStatic,
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
};
use crate::udp::{StaticUdpTmHandler, UdpTmtcServer};
use satrs_core::pus::event_man::EventRequestWithToken;
use satrs_core::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
use satrs_core::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInStoreSender};
use satrs_core::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInSharedPoolSender};
use satrs_core::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::{CcsdsDistributor, TargetId};
use satrs_core::ChannelId;
use satrs_example::TargetIdWithApid;
@ -65,7 +65,7 @@ fn create_verification_reporter(verif_sender: impl EcssTmSender) -> Verification
#[allow(dead_code)]
fn static_tmtc_pool_main() {
let (tm_pool, tc_pool) = create_static_pools();
let shared_tm_store = SharedTmStore::new(tm_pool);
let shared_tm_pool = SharedTmPool::new(tm_pool);
let shared_tc_pool = SharedTcPool {
pool: Arc::new(RwLock::new(tc_pool)),
};
@ -75,10 +75,10 @@ fn static_tmtc_pool_main() {
// Every software component which needs to generate verification telemetry, receives a cloned
// verification reporter.
let verif_reporter = create_verification_reporter(MpscTmInStoreSender::new(
let verif_reporter = create_verification_reporter(MpscTmInSharedPoolSender::new(
TmSenderId::PusVerification as ChannelId,
"verif_sender",
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
));
@ -88,22 +88,13 @@ fn static_tmtc_pool_main() {
let mut request_map = HashMap::new();
request_map.insert(acs_target_id, acs_thread_tx);
let tc_source_wrapper = PusTcSourceProviderSharedPool {
// This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source.
let tc_source = PusTcSourceProviderSharedPool {
shared_pool: shared_tc_pool.clone(),
tc_source: tc_source_tx,
};
// Create clones here to allow moving the values
let tc_args = TcArgs {
tc_source: tc_source_wrapper.clone(),
tc_receiver: tc_source_rx,
};
let tm_args = TmArgs {
tm_store: shared_tm_store.clone(),
tm_sink_sender: tm_funnel_tx.clone(),
tm_udp_server_rx: tm_server_rx,
};
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
// certain events.
@ -112,10 +103,10 @@ fn static_tmtc_pool_main() {
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(
MpscTmInStoreSender::new(
MpscTmInSharedPoolSender::new(
TmSenderId::AllEvents as ChannelId,
"ALL_EVENTS_TX",
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
),
verif_reporter.clone(),
@ -135,7 +126,7 @@ fn static_tmtc_pool_main() {
action_service_receiver: pus_action_tx,
};
let pus_test_service = create_test_service_static(
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
@ -143,15 +134,15 @@ fn static_tmtc_pool_main() {
pus_test_rx,
);
let pus_scheduler_service = create_scheduler_service_static(
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
tc_source_wrapper,
tc_source.clone(),
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service = create_event_service_static(
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
@ -159,7 +150,7 @@ fn static_tmtc_pool_main() {
event_request_tx,
);
let pus_action_service = create_action_service_static(
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
@ -167,7 +158,7 @@ fn static_tmtc_pool_main() {
request_map.clone(),
);
let pus_hk_service = create_hk_service_static(
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
@ -182,11 +173,10 @@ fn static_tmtc_pool_main() {
pus_test_service,
);
let ccsds_receiver = CcsdsReceiver {
tc_source: tc_args.tc_source.clone(),
};
let mut tmtc_task = TmtcTaskStatic::new(
tc_args,
let ccsds_receiver = CcsdsReceiver { tc_source };
let mut tmtc_task = TcSourceTaskStatic::new(
shared_tc_pool.clone(),
tc_source_rx,
PusReceiver::new(verif_reporter.clone(), pus_router),
);
@ -197,8 +187,8 @@ fn static_tmtc_pool_main() {
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
tm_handler: StaticUdpTmHandler {
tm_rx: tm_args.tm_udp_server_rx,
tm_store: tm_args.tm_store.clone_backing_pool(),
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone_backing_pool(),
},
};
@ -213,10 +203,10 @@ fn static_tmtc_pool_main() {
.expect("tcp server creation failed");
let mut acs_task = AcsTask::new(
MpscTmInStoreSender::new(
MpscTmInSharedPoolSender::new(
TmSenderId::AcsSubsystem as ChannelId,
"ACS_TASK_SENDER",
shared_tm_store.clone(),
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
),
acs_thread_rx,
@ -224,7 +214,7 @@ fn static_tmtc_pool_main() {
);
let mut tm_funnel = TmFunnelStatic::new(
shared_tm_store,
shared_tm_pool,
sync_tm_tcp_source,
tm_funnel_rx,
tm_server_tx,
@ -399,7 +389,7 @@ fn dyn_tmtc_pool_main() {
let ccsds_receiver = CcsdsReceiver { tc_source };
let mut tmtc_task = TmtcTaskDynamic::new(
let mut tmtc_task = TcSourceTaskDynamic::new(
tc_source_rx,
PusReceiver::new(verif_reporter.clone(), pus_router),
);

View File

@ -6,12 +6,12 @@ use satrs_core::pus::verification::{
};
use satrs_core::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInStoreSender,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
};
use satrs_core::spacepackets::ecss::tc::PusTcReader;
use satrs_core::spacepackets::ecss::PusPacket;
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_core::ChannelId;
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use satrs_example::TargetIdWithApid;
@ -19,14 +19,14 @@ use std::collections::HashMap;
use std::sync::mpsc::{self, Sender};
pub fn create_action_service_static(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>,
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
) -> Pus8Wrapper<EcssTcInSharedStoreConverter> {
let action_srv_tm_sender = MpscTmInStoreSender::new(
let action_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusAction as ChannelId,
"PUS_8_TM_SENDER",
shared_tm_store.clone(),

View File

@ -7,22 +7,22 @@ use satrs_core::pus::event_srv::PusService5EventHandler;
use satrs_core::pus::verification::VerificationReporterWithSender;
use satrs_core::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInStoreSender, PusPacketHandlerResult,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusServiceHelper,
};
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_core::ChannelId;
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
pub fn create_event_service_static(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>,
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> Pus5Wrapper<EcssTcInSharedStoreConverter> {
let event_srv_tm_sender = MpscTmInStoreSender::new(
let event_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusEvent as ChannelId,
"PUS_5_TM_SENDER",
shared_tm_store.clone(),

View File

@ -7,11 +7,11 @@ use satrs_core::pus::verification::{
};
use satrs_core::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInStoreSender,
EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
};
use satrs_core::spacepackets::ecss::{hk, PusPacket};
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_core::ChannelId;
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use satrs_example::TargetIdWithApid;
@ -19,14 +19,14 @@ use std::collections::HashMap;
use std::sync::mpsc::{self, Sender};
pub fn create_hk_service_static(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>,
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_map: HashMap<TargetIdWithApid, mpsc::Sender<RequestWithToken>>,
) -> Pus3Wrapper<EcssTcInSharedStoreConverter> {
let hk_srv_tm_sender = MpscTmInStoreSender::new(
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusHk as ChannelId,
"PUS_3_TM_SENDER",
shared_tm_store.clone(),

View File

@ -2,16 +2,16 @@ use std::sync::mpsc;
use std::time::Duration;
use log::{error, info, warn};
use satrs_core::pool::{PoolProviderMemInPlace, StaticMemoryPool, StoreAddr};
use satrs_core::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
use satrs_core::pus::scheduler::{PusScheduler, TcInfo};
use satrs_core::pus::scheduler_srv::PusService11SchedHandler;
use satrs_core::pus::verification::VerificationReporterWithSender;
use satrs_core::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInStoreSender, PusPacketHandlerResult,
MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
PusServiceHelper,
};
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_core::ChannelId;
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
@ -54,6 +54,7 @@ impl TcReleaser for mpsc::Sender<Vec<u8>> {
pub struct Pus11Wrapper<TcInMemConverter: EcssTcInMemConverter> {
pub pus_11_handler: PusService11SchedHandler<TcInMemConverter, PusScheduler>,
pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096],
pub tc_releaser: Box<dyn TcReleaser + Send>,
}
@ -70,7 +71,11 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
let released_tcs = self
.pus_11_handler
.scheduler_mut()
.release_telecommands(releaser, &mut self.sched_tc_pool)
.release_telecommands_with_buffer(
releaser,
&mut self.sched_tc_pool,
&mut self.releaser_buf,
)
.expect("releasing TCs failed");
if released_tcs > 0 {
info!("{released_tcs} TC(s) released from scheduler");
@ -103,14 +108,14 @@ impl<TcInMemConverter: EcssTcInMemConverter> Pus11Wrapper<TcInMemConverter> {
}
pub fn create_scheduler_service_static(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>,
verif_reporter: VerificationReporterWithSender,
tc_releaser: PusTcSourceProviderSharedPool,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> Pus11Wrapper<EcssTcInSharedStoreConverter> {
let sched_srv_tm_sender = MpscTmInStoreSender::new(
let sched_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusSched as ChannelId,
"PUS_11_TM_SENDER",
shared_tm_store.clone(),
@ -136,6 +141,7 @@ pub fn create_scheduler_service_static(
Pus11Wrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
tc_releaser: Box::new(tc_releaser),
}
}
@ -172,6 +178,7 @@ pub fn create_scheduler_service_dynamic(
Pus11Wrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
tc_releaser: Box::new(tc_source_sender),
}
}

View File

@ -5,27 +5,27 @@ use satrs_core::pus::test::PusService17TestHandler;
use satrs_core::pus::verification::{FailParams, VerificationReporterWithSender};
use satrs_core::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
MpscTmInStoreSender, PusPacketHandlerResult, PusServiceHelper,
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper,
};
use satrs_core::spacepackets::ecss::tc::PusTcReader;
use satrs_core::spacepackets::ecss::PusPacket;
use satrs_core::spacepackets::time::cds::TimeProvider;
use satrs_core::spacepackets::time::TimeWriter;
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::tm_helper::SharedTmPool;
use satrs_core::ChannelId;
use satrs_core::{events::EventU32, pus::EcssTcInSharedStoreConverter};
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID, TEST_EVENT};
use std::sync::mpsc::{self, Sender};
pub fn create_test_service_static(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::Sender<StoreAddr>,
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> Service17CustomWrapper<EcssTcInSharedStoreConverter> {
let test_srv_tm_sender = MpscTmInStoreSender::new(
let test_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusTest as ChannelId,
"PUS_17_TM_SENDER",
shared_tm_store.clone(),

View File

@ -76,7 +76,6 @@ pub struct TcpTask<MpscErrorType: 'static> {
SyncTcpTmSource,
CcsdsDistributor<MpscErrorType>,
>,
phantom: std::marker::PhantomData<MpscErrorType>,
}
impl<MpscErrorType: 'static + core::fmt::Debug> TcpTask<MpscErrorType> {
@ -92,7 +91,6 @@ impl<MpscErrorType: 'static + core::fmt::Debug> TcpTask<MpscErrorType> {
tc_receiver,
Box::new(PACKET_ID_LOOKUP),
)?,
phantom: std::marker::PhantomData,
})
}

View File

@ -3,15 +3,16 @@ use std::{
sync::mpsc::{Receiver, Sender},
};
use log::info;
use satrs_core::{
pool::{PoolProviderMemInPlace, StoreAddr},
pool::{PoolProvider, StoreAddr},
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
spacepackets::{
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
time::cds::MIN_CDS_FIELD_LEN,
CcsdsPacket,
},
tmtc::tm_helper::SharedTmStore,
tmtc::tm_helper::SharedTmPool,
};
use crate::tcp::SyncTcpTmSource;
@ -63,21 +64,26 @@ impl TmFunnelCommon {
*entry += 1;
}
Self::packet_printout(&zero_copy_writer);
// This operation has to come last!
zero_copy_writer.finish();
}
fn packet_printout(tm: &PusTmZeroCopyWriter) {
info!("Sending PUS TM[{},{}]", tm.service(), tm.subservice());
}
}
pub struct TmFunnelStatic {
common: TmFunnelCommon,
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
tm_funnel_rx: Receiver<StoreAddr>,
tm_server_tx: Sender<StoreAddr>,
}
impl TmFunnelStatic {
pub fn new(
shared_tm_store: SharedTmStore,
shared_tm_store: SharedTmPool,
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: Receiver<StoreAddr>,
tm_server_tx: Sender<StoreAddr>,
@ -96,16 +102,21 @@ impl TmFunnelStatic {
// the CRC.
let shared_pool = self.shared_tm_store.clone_backing_pool();
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
let tm_raw = pool_guard
.modify(&addr)
let mut tm_copy = Vec::new();
pool_guard
.modify(&addr, |buf| {
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
tm_copy = buf.to_vec()
})
.expect("Reading TM from pool failed");
let zero_copy_writer = PusTmZeroCopyWriter::new(tm_raw, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
self.tm_server_tx
.send(addr)
.expect("Sending TM to server failed");
self.common.sync_tm_tcp_source.add_tm(tm_raw);
// We could also do this step in the update closure, but I'd rather avoid this, could
// lead to nested locking.
self.common.sync_tm_tcp_source.add_tm(&tm_copy);
}
}
}

View File

@ -5,30 +5,11 @@ use std::sync::mpsc::{self, Receiver, SendError, Sender, TryRecvError};
use thiserror::Error;
use crate::pus::PusReceiver;
use satrs_core::pool::{PoolProviderMemInPlace, SharedStaticMemoryPool, StoreAddr, StoreError};
use satrs_core::pool::{PoolProvider, SharedStaticMemoryPool, StoreAddr, StoreError};
use satrs_core::spacepackets::ecss::tc::PusTcReader;
use satrs_core::spacepackets::ecss::PusPacket;
use satrs_core::tmtc::tm_helper::SharedTmStore;
use satrs_core::tmtc::ReceivesCcsdsTc;
pub struct TmArgs {
pub tm_store: SharedTmStore,
pub tm_sink_sender: Sender<StoreAddr>,
pub tm_udp_server_rx: Receiver<StoreAddr>,
}
pub struct TcArgs {
pub tc_source: PusTcSourceProviderSharedPool,
pub tc_receiver: Receiver<StoreAddr>,
}
impl TcArgs {
#[allow(dead_code)]
fn split(self) -> (PusTcSourceProviderSharedPool, Receiver<StoreAddr>) {
(self.tc_source, self.tc_receiver)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum MpscStoreAndSendError {
#[error("Store error: {0}")]
@ -47,8 +28,9 @@ pub struct SharedTcPool {
impl SharedTcPool {
pub fn add_pus_tc(&mut self, pus_tc: &PusTcReader) -> Result<StoreAddr, StoreError> {
let mut pg = self.pool.write().expect("error locking TC store");
let (addr, buf) = pg.free_element(pus_tc.len_packed())?;
buf[0..pus_tc.len_packed()].copy_from_slice(pus_tc.raw_data());
let addr = pg.free_element(pus_tc.len_packed(), |buf| {
buf[0..pus_tc.len_packed()].copy_from_slice(pus_tc.raw_data());
})?;
Ok(addr)
}
}
@ -110,16 +92,23 @@ impl ReceivesCcsdsTc for PusTcSourceProviderDynamic {
}
}
pub struct TmtcTaskStatic {
tc_args: TcArgs,
// TC source components where static pools are the backing memory of the received telecommands.
pub struct TcSourceTaskStatic {
shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>,
tc_buf: [u8; 4096],
pus_receiver: PusReceiver,
}
impl TmtcTaskStatic {
pub fn new(tc_args: TcArgs, pus_receiver: PusReceiver) -> Self {
impl TcSourceTaskStatic {
pub fn new(
shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>,
pus_receiver: PusReceiver,
) -> Self {
Self {
tc_args,
shared_tc_pool,
tc_receiver,
tc_buf: [0; 4096],
pus_receiver,
}
@ -130,17 +119,15 @@ impl TmtcTaskStatic {
}
pub fn poll_tc(&mut self) -> bool {
match self.tc_args.tc_receiver.try_recv() {
match self.tc_receiver.try_recv() {
Ok(addr) => {
let pool = self
.tc_args
.tc_source
.shared_pool
.shared_tc_pool
.pool
.read()
.expect("locking tc pool failed");
let data = pool.read(&addr).expect("reading pool failed");
self.tc_buf[0..data.len()].copy_from_slice(data);
pool.read(&addr, &mut self.tc_buf)
.expect("reading pool failed");
drop(pool);
match PusTcReader::new(&self.tc_buf) {
Ok((pus_tc, _)) => {
@ -171,12 +158,13 @@ impl TmtcTaskStatic {
}
}
pub struct TmtcTaskDynamic {
// TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic {
pub tc_receiver: Receiver<Vec<u8>>,
pus_receiver: PusReceiver,
}
impl TmtcTaskDynamic {
impl TcSourceTaskDynamic {
pub fn new(tc_receiver: Receiver<Vec<u8>>, pus_receiver: PusReceiver) -> Self {
Self {
tc_receiver,

View File

@ -6,7 +6,7 @@ use std::{
use log::{info, warn};
use satrs_core::{
hal::std::udp_server::{ReceiveResult, UdpTcServer},
pool::{PoolProviderMemInPlaceWithGuards, SharedStaticMemoryPool, StoreAddr},
pool::{PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr},
tmtc::CcsdsError,
};
@ -29,20 +29,13 @@ impl UdpTmHandler for StaticUdpTmHandler {
}
let mut store_lock = store_lock.unwrap();
let pg = store_lock.read_with_guard(addr);
let read_res = pg.read();
let read_res = pg.read_as_vec();
if read_res.is_err() {
warn!("Error reading TM pool data");
continue;
}
let buf = read_res.unwrap();
if buf.len() > 9 {
let service = buf[7];
let subservice = buf[8];
info!("Sending PUS TM[{service},{subservice}]")
} else {
info!("Sending PUS TM");
}
let result = socket.send_to(buf, recv_addr);
let result = socket.send_to(&buf, recv_addr);
if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}")
}