blob: e005cfdebd19c7d943dda04a2c968ff3b61a3050 [file] [log] [blame]
Yi Tseng0b809722017-11-03 10:23:26 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Carmelo Cascone356ab8b2019-09-25 01:02:53 -070017package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
Yi Tseng0b809722017-11-03 10:23:26 -070018
Daniele Moro01ca2ab2019-06-25 11:48:48 -070019import com.google.common.collect.ImmutableList;
Carmelo Casconeb5324e72018-11-25 02:26:32 -080020import org.junit.Before;
Yi Tseng0b809722017-11-03 10:23:26 -070021import org.junit.Ignore;
22import org.junit.Test;
23import org.onlab.packet.Ethernet;
24import org.onlab.packet.IPv4;
Charles Chan384aea22018-08-23 22:08:02 -070025import org.onlab.packet.MacAddress;
Yi Tseng0b809722017-11-03 10:23:26 -070026import org.onlab.packet.TpPort;
27import org.onlab.packet.UDP;
Daniele Moro01ca2ab2019-06-25 11:48:48 -070028import org.onosproject.net.PortNumber;
Yi Tseng0b809722017-11-03 10:23:26 -070029import org.onosproject.net.flow.DefaultFlowRule;
30import org.onosproject.net.flow.DefaultTrafficSelector;
31import org.onosproject.net.flow.DefaultTrafficTreatment;
32import org.onosproject.net.flow.FlowRule;
33import org.onosproject.net.flow.TrafficSelector;
34import org.onosproject.net.flow.TrafficTreatment;
Charles Chan384aea22018-08-23 22:08:02 -070035import org.onosproject.net.flow.criteria.Criterion;
36import org.onosproject.net.flow.criteria.EthCriterion;
Yi Tseng0b809722017-11-03 10:23:26 -070037import org.onosproject.net.flowobjective.DefaultForwardingObjective;
38import org.onosproject.net.flowobjective.ForwardingObjective;
Daniele Moro01ca2ab2019-06-25 11:48:48 -070039import org.onosproject.net.group.DefaultGroupBucket;
40import org.onosproject.net.group.DefaultGroupDescription;
41import org.onosproject.net.group.DefaultGroupKey;
42import org.onosproject.net.group.GroupBucket;
43import org.onosproject.net.group.GroupBuckets;
Yi Tseng0b809722017-11-03 10:23:26 -070044import org.onosproject.net.group.GroupDescription;
Daniele Moro01ca2ab2019-06-25 11:48:48 -070045import org.onosproject.net.group.GroupKey;
Yi Tseng0b809722017-11-03 10:23:26 -070046import org.onosproject.net.pi.model.PiTableId;
47import org.onosproject.net.pi.runtime.PiAction;
48import org.onosproject.net.pi.runtime.PiActionParam;
Carmelo Cascone2102bfb2020-12-04 16:54:24 -080049import org.onosproject.pipelines.fabric.FabricConstants;
Yi Tseng0b809722017-11-03 10:23:26 -070050
51import java.util.List;
52
53import static org.junit.Assert.assertEquals;
54import static org.junit.Assert.assertTrue;
55
56/**
57 * Test cases for fabric.p4 pipeline forwarding control block.
58 */
Carmelo Cascone2388cc12021-05-26 19:30:30 +020059public class ForwardingObjectiveTranslatorTest extends BaseObjectiveTranslatorTest {
Yi Tseng0b809722017-11-03 10:23:26 -070060
Carmelo Casconeb5324e72018-11-25 02:26:32 -080061 private ForwardingObjectiveTranslator translator;
62
63 @Before
64 public void setup() {
65 super.doSetup();
66 translator = new ForwardingObjectiveTranslator(DEVICE_ID, capabilitiesHashed);
67 }
68
Yi Tseng0b809722017-11-03 10:23:26 -070069 /**
70 * Test versatile flag of forwarding objective with ARP match.
71 */
72 @Test
73 public void testAclArp() {
74 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Yi Tseng0b809722017-11-03 10:23:26 -070075 .punt()
76 .build();
77 // ARP
78 TrafficSelector selector = DefaultTrafficSelector.builder()
79 .matchEthType(Ethernet.TYPE_ARP)
80 .build();
81 ForwardingObjective fwd = DefaultForwardingObjective.builder()
82 .withSelector(selector)
83 .withPriority(PRIORITY)
84 .fromApp(APP_ID)
85 .makePermanent()
86 .withFlag(ForwardingObjective.Flag.VERSATILE)
87 .withTreatment(treatment)
88 .add();
89
Carmelo Casconeb5324e72018-11-25 02:26:32 -080090 ObjectiveTranslation result = translator.translate(fwd);
Yi Tseng0b809722017-11-03 10:23:26 -070091
92 List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
93 List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
94 assertEquals(1, flowRulesInstalled.size());
Daniele Moro01ca2ab2019-06-25 11:48:48 -070095 assertEquals(1, groupsInstalled.size());
Yi Tseng0b809722017-11-03 10:23:26 -070096
97 FlowRule actualFlowRule = flowRulesInstalled.get(0);
Carmelo Casconeb5324e72018-11-25 02:26:32 -080098 PiAction piAction = PiAction.builder()
Daniele Moro01ca2ab2019-06-25 11:48:48 -070099 .withId(FabricConstants.FABRIC_INGRESS_ACL_SET_CLONE_SESSION_ID)
100 .withParameter(new PiActionParam(
101 FabricConstants.CLONE_ID,
102 ForwardingObjectiveTranslator.CLONE_TO_CPU_ID))
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800103 .build();
Yi Tseng0b809722017-11-03 10:23:26 -0700104 FlowRule expectedFlowRule = DefaultFlowRule.builder()
105 .forDevice(DEVICE_ID)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800106 .forTable(FabricConstants.FABRIC_INGRESS_ACL_ACL)
Yi Tseng0b809722017-11-03 10:23:26 -0700107 .withPriority(PRIORITY)
108 .makePermanent()
109 .withSelector(selector)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800110 .withTreatment(DefaultTrafficTreatment.builder()
111 .piTableAction(piAction).build())
Yi Tseng0b809722017-11-03 10:23:26 -0700112 .fromApp(APP_ID)
113 .build();
114
Daniele Moro01ca2ab2019-06-25 11:48:48 -0700115 GroupDescription actualCloneGroup = groupsInstalled.get(0);
116 TrafficTreatment cloneGroupTreatment = DefaultTrafficTreatment.builder()
117 .setOutput(PortNumber.CONTROLLER)
118 .build();
119
120 List<GroupBucket> cloneBuckets = ImmutableList.of(
121 DefaultGroupBucket.createCloneGroupBucket(cloneGroupTreatment));
122
123 GroupBuckets cloneGroupBuckets = new GroupBuckets(cloneBuckets);
124 GroupKey cloneGroupKey = new DefaultGroupKey(
125 FabricPipeliner.KRYO.serialize(ForwardingObjectiveTranslator.CLONE_TO_CPU_ID));
126 GroupDescription expectedCloneGroup = new DefaultGroupDescription(
127 DEVICE_ID,
128 GroupDescription.Type.CLONE,
129 cloneGroupBuckets,
130 cloneGroupKey,
131 ForwardingObjectiveTranslator.CLONE_TO_CPU_ID,
132 APP_ID
133 );
134
Yi Tseng0b809722017-11-03 10:23:26 -0700135 assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
Daniele Moro01ca2ab2019-06-25 11:48:48 -0700136 assertTrue(expectedCloneGroup.equals(actualCloneGroup));
Yi Tseng0b809722017-11-03 10:23:26 -0700137 }
138
139 /**
140 * Test versatile flag of forwarding objective with DHCP match.
141 */
142 @Test
143 public void testAclDhcp() {
144 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
145 .wipeDeferred()
146 .punt()
147 .build();
148 // DHCP
149 TrafficSelector selector = DefaultTrafficSelector.builder()
150 .matchEthType(Ethernet.TYPE_IPV4)
151 .matchIPProtocol(IPv4.PROTOCOL_UDP)
152 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
153 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
154 .build();
155 ForwardingObjective fwd = DefaultForwardingObjective.builder()
156 .withSelector(selector)
157 .withPriority(PRIORITY)
158 .fromApp(APP_ID)
159 .makePermanent()
160 .withFlag(ForwardingObjective.Flag.VERSATILE)
161 .withTreatment(treatment)
162 .add();
163
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800164 ObjectiveTranslation result = translator.translate(fwd);
Yi Tseng0b809722017-11-03 10:23:26 -0700165
166 List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
167 List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
168 assertEquals(1, flowRulesInstalled.size());
169 assertTrue(groupsInstalled.isEmpty());
170
171 FlowRule actualFlowRule = flowRulesInstalled.get(0);
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800172 PiAction piAction = PiAction.builder()
173 .withId(FabricConstants.FABRIC_INGRESS_ACL_PUNT_TO_CPU)
174 .build();
Yi Tseng0b809722017-11-03 10:23:26 -0700175 FlowRule expectedFlowRule = DefaultFlowRule.builder()
176 .forDevice(DEVICE_ID)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800177 .forTable(FabricConstants.FABRIC_INGRESS_ACL_ACL)
Yi Tseng0b809722017-11-03 10:23:26 -0700178 .withPriority(PRIORITY)
179 .makePermanent()
180 .withSelector(selector)
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800181 .withTreatment(DefaultTrafficTreatment.builder()
182 .piTableAction(piAction).build())
Yi Tseng0b809722017-11-03 10:23:26 -0700183 .fromApp(APP_ID)
184 .build();
185
186 assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
187 }
188
189 /**
190 * Test programming L2 unicast rule to bridging table.
191 */
192 @Test
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800193 public void testL2Unicast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700194 TrafficSelector selector = DefaultTrafficSelector.builder()
195 .matchVlanId(VLAN_100)
196 .matchEthDst(HOST_MAC)
197 .build();
Yi Tseng43ee7e82018-04-12 16:37:34 +0800198 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
Charles Chan384aea22018-08-23 22:08:02 -0700199 buildExpectedSelector(selector), selector, NEXT_ID_1);
Yi Tseng0b809722017-11-03 10:23:26 -0700200 }
201
202 @Test
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800203 public void testL2Broadcast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700204 TrafficSelector selector = DefaultTrafficSelector.builder()
205 .matchVlanId(VLAN_100)
206 .build();
Yi Tseng43ee7e82018-04-12 16:37:34 +0800207 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
208 selector, selector, NEXT_ID_1);
Yi Tseng0b809722017-11-03 10:23:26 -0700209 }
210
211 @Test
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800212 public void testIPv4Unicast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700213 TrafficSelector selector = DefaultTrafficSelector.builder()
214 .matchEthType(Ethernet.TYPE_IPV4)
215 .matchIPDst(IPV4_UNICAST_ADDR)
216 .build();
217 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
218 .matchIPDst(IPV4_UNICAST_ADDR)
219 .build();
Charles Chan384aea22018-08-23 22:08:02 -0700220 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800221 expectedSelector, selector, NEXT_ID_1);
Yi Tseng0b809722017-11-03 10:23:26 -0700222 }
223
224 @Test
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800225 public void testIPv4UnicastWithNoNextId() throws FabricPipelinerException {
Yi Tsengdf3eec52018-02-15 14:56:02 -0800226 TrafficSelector selector = DefaultTrafficSelector.builder()
227 .matchEthType(Ethernet.TYPE_IPV4)
228 .matchIPDst(IPV4_UNICAST_ADDR)
229 .build();
230 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
231 .matchIPDst(IPV4_UNICAST_ADDR)
232 .build();
Charles Chan384aea22018-08-23 22:08:02 -0700233 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800234 expectedSelector, selector, null);
Yi Tsengdf3eec52018-02-15 14:56:02 -0800235 }
236
237 @Test
Yi Tseng0b809722017-11-03 10:23:26 -0700238 @Ignore
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800239 public void testIPv4Multicast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700240 TrafficSelector selector = DefaultTrafficSelector.builder()
241 .matchEthType(Ethernet.TYPE_IPV4)
242 .matchVlanId(VLAN_100)
243 .matchIPDst(IPV4_MCAST_ADDR)
244 .build();
245 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
246 .matchIPDst(IPV4_MCAST_ADDR)
247 .build();
Charles Chan384aea22018-08-23 22:08:02 -0700248 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800249 expectedSelector, selector, NEXT_ID_1);
Yi Tseng0b809722017-11-03 10:23:26 -0700250 }
251
252 @Test
253 @Ignore
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800254 public void testIPv6Unicast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700255 TrafficSelector selector = DefaultTrafficSelector.builder()
256 .matchEthType(Ethernet.TYPE_IPV6)
257 .matchIPDst(IPV6_UNICAST_ADDR)
258 .build();
259 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
260 .matchIPDst(IPV6_UNICAST_ADDR)
261 .build();
Charles Chan384aea22018-08-23 22:08:02 -0700262 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800263 expectedSelector, selector, NEXT_ID_1);
264
Yi Tseng0b809722017-11-03 10:23:26 -0700265 }
266
267 @Test
268 @Ignore
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800269 public void testIPv6Multicast() throws FabricPipelinerException {
Yi Tseng0b809722017-11-03 10:23:26 -0700270 TrafficSelector selector = DefaultTrafficSelector.builder()
271 .matchEthType(Ethernet.TYPE_IPV6)
272 .matchVlanId(VLAN_100)
273 .matchIPDst(IPV6_MCAST_ADDR)
274 .build();
275 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
276 .matchIPDst(IPV6_MCAST_ADDR)
277 .build();
Charles Chan384aea22018-08-23 22:08:02 -0700278 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800279 expectedSelector, selector, NEXT_ID_1);
Yi Tseng0b809722017-11-03 10:23:26 -0700280 }
281
282 @Test
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800283 public void testMpls() throws FabricPipelinerException {
Yi Tseng1b154bd2017-11-20 17:48:19 -0800284 TrafficSelector selector = DefaultTrafficSelector.builder()
285 .matchEthType(Ethernet.MPLS_UNICAST)
286 .matchMplsLabel(MPLS_10)
287 .matchMplsBos(true)
288 .build();
289 TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
290 .matchMplsLabel(MPLS_10)
291 .build();
Yi Tseng0b809722017-11-03 10:23:26 -0700292
Charles Chanea89a1c2018-08-13 13:14:18 -0700293 PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, NEXT_ID_1);
Yi Tseng1b154bd2017-11-20 17:48:19 -0800294 PiAction setNextIdAction = PiAction.builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800295 .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT)
Yi Tseng1b154bd2017-11-20 17:48:19 -0800296 .withParameter(nextIdParam)
297 .build();
298 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
299 .piTableAction(setNextIdAction)
300 .build();
Yi Tseng43ee7e82018-04-12 16:37:34 +0800301 testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
302 expectedSelector, selector, NEXT_ID_1, treatment);
Yi Tseng0b809722017-11-03 10:23:26 -0700303 }
304
305 private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector,
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800306 TrafficSelector selector, Integer nextId) throws FabricPipelinerException {
Yi Tsengdf3eec52018-02-15 14:56:02 -0800307 TrafficTreatment setNextIdTreatment;
308 if (nextId == null) {
309 // Ref: RoutingRulePopulator.java->revokeIpRuleForRouter
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800310
311 setNextIdTreatment = DefaultTrafficTreatment.builder().
312 piTableAction(PiAction.builder()
313 .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4)
314 .build())
315 .build();
Yi Tsengdf3eec52018-02-15 14:56:02 -0800316 } else {
Charles Chanea89a1c2018-08-13 13:14:18 -0700317 PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, nextId);
Yi Tseng47eac892018-07-11 02:17:04 +0800318 PiAction.Builder setNextIdAction = PiAction.builder()
319 .withParameter(nextIdParam);
320
321 if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)) {
322 setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING);
Charles Chan384aea22018-08-23 22:08:02 -0700323 } else if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)) {
324 setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4);
325 } else if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6)) {
326 setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V6);
Yi Tseng47eac892018-07-11 02:17:04 +0800327 }
328
Yi Tsengdf3eec52018-02-15 14:56:02 -0800329 setNextIdTreatment = DefaultTrafficTreatment.builder()
Yi Tseng47eac892018-07-11 02:17:04 +0800330 .piTableAction(setNextIdAction.build())
Yi Tsengdf3eec52018-02-15 14:56:02 -0800331 .build();
332 }
Yi Tseng1b154bd2017-11-20 17:48:19 -0800333
334 testSpecificForward(expectedTableId, expectedSelector, selector, nextId, setNextIdTreatment);
335
336 }
337
338 private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector,
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800339 TrafficSelector selector, Integer nextId, TrafficTreatment treatment)
340 throws FabricPipelinerException {
Yi Tsengdf3eec52018-02-15 14:56:02 -0800341 ForwardingObjective.Builder fwd = DefaultForwardingObjective.builder()
Yi Tseng0b809722017-11-03 10:23:26 -0700342 .withSelector(selector)
343 .withPriority(PRIORITY)
344 .fromApp(APP_ID)
345 .makePermanent()
Yi Tsengdf3eec52018-02-15 14:56:02 -0800346 .withTreatment(treatment)
347 .withFlag(ForwardingObjective.Flag.SPECIFIC);
Yi Tseng0b809722017-11-03 10:23:26 -0700348
Yi Tsengdf3eec52018-02-15 14:56:02 -0800349 if (nextId != null) {
350 fwd.nextStep(nextId);
351 }
352
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800353 ObjectiveTranslation actualTranslation = translator.translate(fwd.add());
Yi Tseng0b809722017-11-03 10:23:26 -0700354
355 FlowRule expectedFlowRule = DefaultFlowRule.builder()
356 .forDevice(DEVICE_ID)
357 .forTable(expectedTableId)
358 .withPriority(PRIORITY)
359 .makePermanent()
360 .withSelector(expectedSelector)
Yi Tseng1b154bd2017-11-20 17:48:19 -0800361 .withTreatment(treatment)
Yi Tseng0b809722017-11-03 10:23:26 -0700362 .fromApp(APP_ID)
363 .build();
364
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800365 ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
366 .addFlowRule(expectedFlowRule)
367 .build();
368
369 assertEquals(expectedTranslation, actualTranslation);
Yi Tseng0b809722017-11-03 10:23:26 -0700370 }
Charles Chan384aea22018-08-23 22:08:02 -0700371
372 private TrafficSelector buildExpectedSelector(TrafficSelector selector) {
373 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
374 selector.criteria().forEach(c -> {
375 if (c.type() == Criterion.Type.ETH_DST) {
376 sbuilder.matchEthDstMasked(((EthCriterion) c).mac(), MacAddress.EXACT_MASK);
377 } else {
378 sbuilder.add(c);
379 }
380 });
381 return sbuilder.build();
382 }
Yi Tseng0b809722017-11-03 10:23:26 -0700383}