blob: 479b1d4cb9bb4438fc470486cff21542bfa66035 [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
17package org.onosproject.pipelines.fabric.pipeliner;
18
Yi Tseng20f9e7b2018-05-24 23:27:39 +080019import org.onlab.packet.VlanId;
Yi Tseng0b809722017-11-03 10:23:26 -070020import org.onosproject.net.DeviceId;
Yi Tseng20f9e7b2018-05-24 23:27:39 +080021import org.onosproject.net.PortNumber;
Yi Tseng4fd28432018-02-01 14:48:03 -080022import org.onosproject.net.driver.Driver;
Yi Tseng0b809722017-11-03 10:23:26 -070023import org.onosproject.net.flow.DefaultFlowRule;
24import org.onosproject.net.flow.DefaultTrafficSelector;
25import org.onosproject.net.flow.DefaultTrafficTreatment;
Yi Tseng0b809722017-11-03 10:23:26 -070026import org.onosproject.net.flow.TrafficSelector;
27import org.onosproject.net.flow.TrafficTreatment;
Yi Tseng20f9e7b2018-05-24 23:27:39 +080028import org.onosproject.net.flow.criteria.Criterion;
Yi Tseng0b809722017-11-03 10:23:26 -070029import org.onosproject.net.flow.criteria.PiCriterion;
Yi Tseng20f9e7b2018-05-24 23:27:39 +080030import org.onosproject.net.flow.criteria.VlanIdCriterion;
Yi Tseng0b809722017-11-03 10:23:26 -070031import org.onosproject.net.flow.instructions.Instruction;
Yi Tseng20f9e7b2018-05-24 23:27:39 +080032import org.onosproject.net.flow.instructions.L2ModificationInstruction;
Yi Tseng4fd28432018-02-01 14:48:03 -080033import org.onosproject.net.flowobjective.DefaultNextObjective;
Yi Tseng0b809722017-11-03 10:23:26 -070034import org.onosproject.net.flowobjective.NextObjective;
Yi Tseng6e9b6f52018-02-27 10:40:51 +010035import org.onosproject.net.flowobjective.Objective;
Yi Tseng0b809722017-11-03 10:23:26 -070036import org.onosproject.net.flowobjective.ObjectiveError;
Yi Tseng1b154bd2017-11-20 17:48:19 -080037import org.onosproject.net.group.DefaultGroupBucket;
38import org.onosproject.net.group.DefaultGroupDescription;
Esin Karaman24fda8a2018-01-26 11:52:28 +000039import org.onosproject.net.group.DefaultGroupKey;
Yi Tseng1b154bd2017-11-20 17:48:19 -080040import org.onosproject.net.group.GroupBucket;
41import org.onosproject.net.group.GroupBuckets;
42import org.onosproject.net.group.GroupDescription;
Esin Karaman24fda8a2018-01-26 11:52:28 +000043import org.onosproject.net.group.GroupKey;
44import org.onosproject.net.pi.runtime.PiAction;
Yi Tseng1b154bd2017-11-20 17:48:19 -080045import org.onosproject.net.pi.runtime.PiActionGroupId;
Esin Karaman24fda8a2018-01-26 11:52:28 +000046import org.onosproject.net.pi.runtime.PiActionParam;
Yi Tseng1b154bd2017-11-20 17:48:19 -080047import org.onosproject.net.pi.runtime.PiGroupKey;
Yi Tseng43ee7e82018-04-12 16:37:34 +080048import org.onosproject.pipelines.fabric.FabricConstants;
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020049import org.onosproject.pipelines.fabric.FabricUtils;
Yi Tseng0b809722017-11-03 10:23:26 -070050import org.slf4j.Logger;
51
Yi Tseng1b154bd2017-11-20 17:48:19 -080052import java.util.List;
Carmelo Cascone58136812018-07-19 03:40:16 +020053import java.util.Optional;
Yi Tseng1b154bd2017-11-20 17:48:19 -080054import java.util.stream.Collectors;
Yi Tseng0b809722017-11-03 10:23:26 -070055
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020056import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
Yi Tseng0b809722017-11-03 10:23:26 -070057import static org.slf4j.LoggerFactory.getLogger;
58
59/**
60 * Handling next objective for fabric pipeliner.
61 */
62public class FabricNextPipeliner {
63 private static final Logger log = getLogger(FabricNextPipeliner.class);
Yi Tseng4fd28432018-02-01 14:48:03 -080064 private static final String NO_HASHED_TABLE = "noHashedTable";
Yi Tseng0b809722017-11-03 10:23:26 -070065
Yi Tseng0b809722017-11-03 10:23:26 -070066 protected DeviceId deviceId;
Yi Tseng4fd28432018-02-01 14:48:03 -080067 protected Driver driver;
Yi Tseng0b809722017-11-03 10:23:26 -070068
Yi Tseng4fd28432018-02-01 14:48:03 -080069 public FabricNextPipeliner(DeviceId deviceId, Driver driver) {
Yi Tseng0b809722017-11-03 10:23:26 -070070 this.deviceId = deviceId;
Yi Tseng4fd28432018-02-01 14:48:03 -080071 this.driver = driver;
Yi Tseng0b809722017-11-03 10:23:26 -070072 }
73
74 public PipelinerTranslationResult next(NextObjective nextObjective) {
75 PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
Yi Tseng1b154bd2017-11-20 17:48:19 -080076
Charles Chanb1f02f22018-08-31 20:19:33 -070077 if (nextObjective.op() != Objective.Operation.ADD_TO_EXISTING &&
78 nextObjective.op() != Objective.Operation.REMOVE_FROM_EXISTING) {
79 processNextVlanMeta(nextObjective, resultBuilder);
80 }
Yi Tseng20f9e7b2018-05-24 23:27:39 +080081
Yi Tseng0b809722017-11-03 10:23:26 -070082 switch (nextObjective.type()) {
83 case SIMPLE:
Yi Tseng1b154bd2017-11-20 17:48:19 -080084 processSimpleNext(nextObjective, resultBuilder);
85 break;
86 case HASHED:
87 processHashedNext(nextObjective, resultBuilder);
Yi Tseng0b809722017-11-03 10:23:26 -070088 break;
Esin Karaman24fda8a2018-01-26 11:52:28 +000089 case BROADCAST:
90 processBroadcastNext(nextObjective, resultBuilder);
91 break;
Yi Tseng0b809722017-11-03 10:23:26 -070092 default:
93 log.warn("Unsupported next type {}", nextObjective);
94 resultBuilder.setError(ObjectiveError.UNSUPPORTED);
95 break;
96 }
97
Yi Tseng0b809722017-11-03 10:23:26 -070098 return resultBuilder.build();
99 }
100
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800101 private void processNextVlanMeta(NextObjective next,
102 PipelinerTranslationResult.Builder resultBuilder) {
103 TrafficSelector meta = next.meta();
104 if (meta == null) {
105 // do nothing if there is no metadata in the next objective.
106 return;
107 }
108 VlanIdCriterion vlanIdCriterion =
109 (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
110
111 if (vlanIdCriterion == null) {
112 // do nothing if we can't find vlan from next objective metadata.
113 return;
114 }
115
116 VlanId vlanId = vlanIdCriterion.vlanId();
117 TrafficSelector selector = buildNextIdSelector(next.id());
118 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
119 .setVlanId(vlanId)
120 .build();
121
122 resultBuilder.addFlowRule(DefaultFlowRule.builder()
123 .withSelector(selector)
124 .withTreatment(treatment)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800125 .forTable(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META)
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800126 .makePermanent()
127 .withPriority(next.priority())
128 .forDevice(deviceId)
129 .fromApp(next.appId())
130 .build());
131 }
132
Yi Tseng1b154bd2017-11-20 17:48:19 -0800133 private void processSimpleNext(NextObjective next,
134 PipelinerTranslationResult.Builder resultBuilder) {
135
Yi Tseng0b809722017-11-03 10:23:26 -0700136 if (next.next().size() > 1) {
137 log.warn("Only one treatment in simple next objective");
Yi Tseng1b154bd2017-11-20 17:48:19 -0800138 resultBuilder.setError(ObjectiveError.BADPARAMS);
139 return;
Yi Tseng0b809722017-11-03 10:23:26 -0700140 }
141
142 TrafficSelector selector = buildNextIdSelector(next.id());
143 TrafficTreatment treatment = next.next().iterator().next();
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800144 PortNumber outputPort = getOutputPort(treatment);
Yi Tseng0b809722017-11-03 10:23:26 -0700145
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800146 if (outputPort == null) {
Yi Tseng0b809722017-11-03 10:23:26 -0700147 log.warn("At least one output instruction in simple next objective");
Yi Tseng1b154bd2017-11-20 17:48:19 -0800148 resultBuilder.setError(ObjectiveError.BADPARAMS);
149 return;
Yi Tseng0b809722017-11-03 10:23:26 -0700150 }
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800151
Yi Tseng1b154bd2017-11-20 17:48:19 -0800152 resultBuilder.addFlowRule(DefaultFlowRule.builder()
153 .withSelector(selector)
154 .withTreatment(treatment)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800155 .forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
Yi Tseng1b154bd2017-11-20 17:48:19 -0800156 .makePermanent()
157 .withPriority(next.priority())
158 .forDevice(deviceId)
159 .fromApp(next.appId())
160 .build());
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800161
162 if (includesPopVlanInst(treatment)) {
163 processVlanPopRule(outputPort, next, resultBuilder);
164 }
Yi Tseng1b154bd2017-11-20 17:48:19 -0800165 }
166
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800167 private boolean includesPopVlanInst(TrafficTreatment treatment) {
168 return treatment.allInstructions()
169 .stream()
170 .filter(inst -> inst.type() == Instruction.Type.L2MODIFICATION)
171 .map(inst -> (L2ModificationInstruction) inst)
172 .anyMatch(inst -> inst.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP);
173 }
174
175 private void processVlanPopRule(PortNumber port, NextObjective next,
176 PipelinerTranslationResult.Builder resultBuilder) {
177 TrafficSelector meta = next.meta();
178 VlanIdCriterion vlanIdCriterion =
179 (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
180 VlanId vlanId = vlanIdCriterion.vlanId();
181
182 PiCriterion egressVlanTableMatch = PiCriterion.builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800183 .matchExact(FabricConstants.STANDARD_METADATA_EGRESS_PORT,
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800184 (short) port.toLong())
185 .build();
186 // Add VLAN pop rule to egress pipeline table
187 TrafficSelector selector = DefaultTrafficSelector.builder()
188 .matchPi(egressVlanTableMatch)
189 .matchVlanId(vlanId)
190 .build();
191 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
192 .popVlan()
193 .build();
194 resultBuilder.addFlowRule(DefaultFlowRule.builder()
195 .withSelector(selector)
196 .withTreatment(treatment)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800197 .forTable(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800198 .makePermanent()
199 .withPriority(next.priority())
200 .forDevice(deviceId)
201 .fromApp(next.appId())
202 .build());
203 }
204
205 private void processHashedNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
Yi Tseng4fd28432018-02-01 14:48:03 -0800206 boolean noHashedTable = Boolean.parseBoolean(driver.getProperty(NO_HASHED_TABLE));
207
208 if (noHashedTable) {
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800209 if (next.next().isEmpty()) {
Yi Tseng4fd28432018-02-01 14:48:03 -0800210 return;
211 }
212 // use first action if not support hashed group
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800213 TrafficTreatment treatment = next.next().iterator().next();
Yi Tseng4fd28432018-02-01 14:48:03 -0800214
215 NextObjective.Builder simpleNext = DefaultNextObjective.builder()
216 .addTreatment(treatment)
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800217 .withId(next.id())
218 .fromApp(next.appId())
Yi Tseng4fd28432018-02-01 14:48:03 -0800219 .makePermanent()
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800220 .withMeta(next.meta())
221 .withPriority(next.priority())
Yi Tseng4fd28432018-02-01 14:48:03 -0800222 .withType(NextObjective.Type.SIMPLE);
223
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800224 if (next.context().isPresent()) {
225 processSimpleNext(simpleNext.add(next.context().get()), resultBuilder);
Yi Tseng4fd28432018-02-01 14:48:03 -0800226 } else {
227 processSimpleNext(simpleNext.add(), resultBuilder);
228 }
229 return;
230 }
231
Yi Tseng1b154bd2017-11-20 17:48:19 -0800232 // create hash groups
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800233 int groupId = next.id();
234 List<GroupBucket> bucketList = next.next().stream()
Yi Tseng1b154bd2017-11-20 17:48:19 -0800235 .map(DefaultGroupBucket::createSelectGroupBucket)
236 .collect(Collectors.toList());
237
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800238 // Egress VLAN handling
239 next.next().forEach(treatment -> {
240 PortNumber outputPort = getOutputPort(treatment);
241 if (includesPopVlanInst(treatment) && outputPort != null) {
242 processVlanPopRule(outputPort, next, resultBuilder);
243 }
244 });
245
246 if (bucketList.size() != next.next().size()) {
Yi Tseng1b154bd2017-11-20 17:48:19 -0800247 // some action not converted
248 // set error
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800249 log.warn("Expected bucket size {}, got {}", next.next().size(), bucketList.size());
Yi Tseng1b154bd2017-11-20 17:48:19 -0800250 resultBuilder.setError(ObjectiveError.BADPARAMS);
251 return;
252 }
253
254 GroupBuckets buckets = new GroupBuckets(bucketList);
Yi Tseng43ee7e82018-04-12 16:37:34 +0800255 PiGroupKey groupKey = new PiGroupKey(FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
256 FabricConstants.FABRIC_INGRESS_NEXT_ECMP_SELECTOR,
Yi Tseng1b154bd2017-11-20 17:48:19 -0800257 groupId);
258
259 resultBuilder.addGroup(new DefaultGroupDescription(deviceId,
260 GroupDescription.Type.SELECT,
261 buckets,
262 groupKey,
263 groupId,
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800264 next.appId()));
Yi Tseng1b154bd2017-11-20 17:48:19 -0800265
266 // flow
Yi Tseng6e9b6f52018-02-27 10:40:51 +0100267 // If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, means we modify
268 // group buckets only, no changes for flow rule
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800269 if (next.op() == Objective.Operation.ADD_TO_EXISTING ||
270 next.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
Yi Tseng6e9b6f52018-02-27 10:40:51 +0100271 return;
272 }
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800273 TrafficSelector selector = buildNextIdSelector(next.id());
Yi Tseng1b154bd2017-11-20 17:48:19 -0800274 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800275 .piTableAction(PiActionGroupId.of(next.id()))
Yi Tseng0b809722017-11-03 10:23:26 -0700276 .build();
Yi Tseng1b154bd2017-11-20 17:48:19 -0800277
278 resultBuilder.addFlowRule(DefaultFlowRule.builder()
279 .withSelector(selector)
280 .withTreatment(treatment)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800281 .forTable(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
Yi Tseng1b154bd2017-11-20 17:48:19 -0800282 .makePermanent()
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800283 .withPriority(next.priority())
Yi Tseng1b154bd2017-11-20 17:48:19 -0800284 .forDevice(deviceId)
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800285 .fromApp(next.appId())
Yi Tseng1b154bd2017-11-20 17:48:19 -0800286 .build());
Yi Tseng0b809722017-11-03 10:23:26 -0700287 }
288
289 private TrafficSelector buildNextIdSelector(int nextId) {
Yi Tseng1b154bd2017-11-20 17:48:19 -0800290 PiCriterion nextIdCriterion = PiCriterion.builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800291 .matchExact(FabricConstants.FABRIC_METADATA_NEXT_ID, nextId)
Yi Tseng0b809722017-11-03 10:23:26 -0700292 .build();
293 return DefaultTrafficSelector.builder()
Yi Tseng1b154bd2017-11-20 17:48:19 -0800294 .matchPi(nextIdCriterion)
Yi Tseng0b809722017-11-03 10:23:26 -0700295 .build();
296 }
Esin Karaman24fda8a2018-01-26 11:52:28 +0000297
298 private void processBroadcastNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
Carmelo Cascone58136812018-07-19 03:40:16 +0200299 final GroupDescription allGroup = getAllGroup(next);
300 if (allGroup == null) {
301 // Error already logged.
Esin Karaman24fda8a2018-01-26 11:52:28 +0000302 resultBuilder.setError(ObjectiveError.BADPARAMS);
303 return;
304 }
305
Carmelo Cascone58136812018-07-19 03:40:16 +0200306 resultBuilder.addGroup(allGroup);
Esin Karaman24fda8a2018-01-26 11:52:28 +0000307 //flow rule
Carmelo Cascone58136812018-07-19 03:40:16 +0200308 final TrafficSelector selector = buildNextIdSelector(next.id());
309 final PiActionParam groupIdParam = new PiActionParam(
310 FabricConstants.GID, allGroup.givenGroupId());
Esin Karaman24fda8a2018-01-26 11:52:28 +0000311
Carmelo Cascone58136812018-07-19 03:40:16 +0200312 final PiAction setMcGroupAction = PiAction.builder()
Esin Karaman24fda8a2018-01-26 11:52:28 +0000313 .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP)
314 .withParameter(groupIdParam)
315 .build();
Carmelo Cascone58136812018-07-19 03:40:16 +0200316 final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Esin Karaman24fda8a2018-01-26 11:52:28 +0000317 .piTableAction(setMcGroupAction)
318 .build();
319
Carmelo Cascone58136812018-07-19 03:40:16 +0200320 resultBuilder.addFlowRule(
321 DefaultFlowRule.builder()
322 .withSelector(selector)
323 .withTreatment(treatment)
324 .forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
325 .makePermanent()
326 .withPriority(next.priority())
327 .forDevice(deviceId)
328 .fromApp(next.appId())
329 .build());
Esin Karaman24fda8a2018-01-26 11:52:28 +0000330
331 // Egress VLAN handling
Carmelo Cascone58136812018-07-19 03:40:16 +0200332 next.next().forEach(t -> {
333 PortNumber outputPort = getOutputPort(t);
334 if (includesPopVlanInst(t) && outputPort != null) {
Esin Karaman24fda8a2018-01-26 11:52:28 +0000335 processVlanPopRule(outputPort, next, resultBuilder);
336 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200337 if (t.allInstructions().size() > 2) {
338 // More than OUTPUT and VLAN_POP...
339 log.warn("Some instructions of BROADCAST NextObjective might" +
340 "not have been applied, supported only " +
341 "OUTPUT and VLAN_POP, but found {}", t);
342 }
Esin Karaman24fda8a2018-01-26 11:52:28 +0000343 });
344 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200345
346 private GroupDescription getAllGroup(NextObjective next) {
347 final List<GroupBucket> bucketList = next.next().stream()
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200348 .map(FabricUtils::getOutputInstruction)
Carmelo Cascone58136812018-07-19 03:40:16 +0200349 .filter(Optional::isPresent)
350 .map(Optional::get)
351 .map(i -> DefaultTrafficTreatment.builder().add(i).build())
352 .map(DefaultGroupBucket::createAllGroupBucket)
353 .collect(Collectors.toList());
354
355 if (bucketList.size() != next.next().size()) {
356 log.warn("Got BROADCAST NextObjective with {} treatments but " +
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200357 "found only {} OUTPUT instructions, cannot " +
Carmelo Cascone58136812018-07-19 03:40:16 +0200358 "translate to ALL groups",
359 next.next().size(), bucketList.size());
360 return null;
361 }
362
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200363 // FIXME: remove once support for clone sessions is available
364 // Right now we add a CPU port to all multicast groups. The egress
365 // pipeline is expected to drop replicated packets to the CPU if a clone
366 // was not requested in the ingress pipeline.
367 bucketList.add(
368 DefaultGroupBucket.createAllGroupBucket(
369 DefaultTrafficTreatment.builder()
370 .setOutput(PortNumber.CONTROLLER)
371 .build()));
372
Carmelo Cascone58136812018-07-19 03:40:16 +0200373 final int groupId = next.id();
374 final GroupBuckets buckets = new GroupBuckets(bucketList);
375 // Used DefaultGroupKey instead of PiGroupKey
376 // as we don't have any action profile to apply to the groups of ALL type
377 final GroupKey groupKey = new DefaultGroupKey(FabricPipeliner.KRYO.serialize(groupId));
378
379 return new DefaultGroupDescription(deviceId,
380 GroupDescription.Type.ALL,
381 buckets,
382 groupKey,
383 groupId,
384 next.appId());
385 }
Yi Tseng0b809722017-11-03 10:23:26 -0700386}