blob: 86e392f4501ff9e4e816fa984efcdce1e3c2c560 [file] [log] [blame]
Jonghwan Hyun722275f2018-05-14 15:44:56 -07001/*
2 * Copyright 2018-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 */
16package org.onosproject.pipelines.basic;
17
18import com.google.common.collect.ImmutableBiMap;
19import com.google.common.collect.Sets;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070020import org.osgi.service.component.annotations.Reference;
21import org.osgi.service.component.annotations.ReferenceCardinality;
Jonghwan Hyun722275f2018-05-14 15:44:56 -070022import org.onlab.util.ImmutableByteSequence;
23import org.onlab.util.SharedExecutors;
24import org.onosproject.core.ApplicationId;
25import org.onosproject.core.CoreService;
26import org.onosproject.inbandtelemetry.api.IntConfig;
27import org.onosproject.inbandtelemetry.api.IntIntent;
28import org.onosproject.inbandtelemetry.api.IntObjective;
29import org.onosproject.inbandtelemetry.api.IntProgrammable;
30import org.onosproject.net.ConnectPoint;
31import org.onosproject.net.DeviceId;
32import org.onosproject.net.Port;
33import org.onosproject.net.PortNumber;
34import org.onosproject.net.device.DeviceService;
35import org.onosproject.net.driver.AbstractHandlerBehaviour;
36import org.onosproject.net.flow.DefaultFlowRule;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.FlowRule;
40import org.onosproject.net.flow.FlowRuleService;
41import org.onosproject.net.flow.TrafficSelector;
42import org.onosproject.net.flow.TrafficTreatment;
43import org.onosproject.net.flow.criteria.Criterion;
44import org.onosproject.net.flow.criteria.IPCriterion;
45import org.onosproject.net.flow.criteria.PiCriterion;
46import org.onosproject.net.flow.criteria.TcpPortCriterion;
47import org.onosproject.net.flow.criteria.UdpPortCriterion;
48import org.onosproject.net.host.HostService;
49import org.onosproject.net.pi.model.PiActionId;
50import org.onosproject.net.pi.model.PiMatchFieldId;
51import org.onosproject.net.pi.model.PiTableId;
52import org.onosproject.net.pi.runtime.PiAction;
53import org.onosproject.net.pi.runtime.PiActionParam;
54import org.slf4j.Logger;
55
56import java.util.ArrayList;
57import java.util.List;
58import java.util.Set;
59import java.util.concurrent.CompletableFuture;
60import java.util.stream.Collectors;
61
62import static org.slf4j.LoggerFactory.getLogger;
63
64public class IntProgrammableImpl extends AbstractHandlerBehaviour implements IntProgrammable {
65
66 // TODO: change this value to the value of diameter of a network.
67 private static final int MAXHOP = 64;
68 private static final int PORTMASK = 0xffff;
69 private static final int IDLE_TIMEOUT = 100;
70 private static final int PKT_INSTANCE_TYPE_INGRESS_CLONE = 1;
71 // Application name of the pipeline which adds this implementation to the pipeconf
72 private static final String PIPELINE_APP_NAME = "org.onosproject.pipelines.basic";
73 private final Logger log = getLogger(getClass());
74 private ApplicationId appId;
75
76 private static final Set<Criterion.Type> SUPPORTED_CRITERION = Sets.newHashSet(
77 Criterion.Type.IPV4_DST, Criterion.Type.IPV4_SRC,
78 Criterion.Type.UDP_SRC, Criterion.Type.UDP_DST,
79 Criterion.Type.TCP_SRC, Criterion.Type.TCP_DST,
80 Criterion.Type.IP_PROTO);
81
Ray Milkeyd84f89b2018-08-17 14:54:17 -070082 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonghwan Hyun722275f2018-05-14 15:44:56 -070083 private FlowRuleService flowRuleService;
84
Ray Milkeyd84f89b2018-08-17 14:54:17 -070085 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonghwan Hyun722275f2018-05-14 15:44:56 -070086 private DeviceService deviceService;
87
Ray Milkeyd84f89b2018-08-17 14:54:17 -070088 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonghwan Hyun722275f2018-05-14 15:44:56 -070089 private HostService hostService;
90
Ray Milkeyd84f89b2018-08-17 14:54:17 -070091 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonghwan Hyun722275f2018-05-14 15:44:56 -070092 private CoreService coreService;
93
94 private DeviceId deviceId;
95 private static final int DEFAULT_PRIORITY = 10000;
96 private static final ImmutableBiMap<Integer, PiActionId> INST_0003_ACTION_MAP =
97 ImmutableBiMap.<Integer, PiActionId>builder()
98 .put(0, IntConstants.ACT_INT_SET_HEADER_0003_I0_ID)
99 .put(1, IntConstants.ACT_INT_SET_HEADER_0003_I1_ID)
100 .put(2, IntConstants.ACT_INT_SET_HEADER_0003_I2_ID)
101 .put(3, IntConstants.ACT_INT_SET_HEADER_0003_I3_ID)
102 .put(4, IntConstants.ACT_INT_SET_HEADER_0003_I4_ID)
103 .put(5, IntConstants.ACT_INT_SET_HEADER_0003_I5_ID)
104 .put(6, IntConstants.ACT_INT_SET_HEADER_0003_I6_ID)
105 .put(7, IntConstants.ACT_INT_SET_HEADER_0003_I7_ID)
106 .put(8, IntConstants.ACT_INT_SET_HEADER_0003_I8_ID)
107 .put(9, IntConstants.ACT_INT_SET_HEADER_0003_I9_ID)
108 .put(10, IntConstants.ACT_INT_SET_HEADER_0003_I10_ID)
109 .put(11, IntConstants.ACT_INT_SET_HEADER_0003_I11_ID)
110 .put(12, IntConstants.ACT_INT_SET_HEADER_0003_I12_ID)
111 .put(13, IntConstants.ACT_INT_SET_HEADER_0003_I13_ID)
112 .put(14, IntConstants.ACT_INT_SET_HEADER_0003_I14_ID)
113 .put(15, IntConstants.ACT_INT_SET_HEADER_0003_I15_ID)
114 .build();
115
116 private static final ImmutableBiMap<Integer, PiActionId> INST_0407_ACTION_MAP =
117 ImmutableBiMap.<Integer, PiActionId>builder()
118 .put(0, IntConstants.ACT_INT_SET_HEADER_0407_I0_ID)
119 .put(1, IntConstants.ACT_INT_SET_HEADER_0407_I1_ID)
120 .put(2, IntConstants.ACT_INT_SET_HEADER_0407_I2_ID)
121 .put(3, IntConstants.ACT_INT_SET_HEADER_0407_I3_ID)
122 .put(4, IntConstants.ACT_INT_SET_HEADER_0407_I4_ID)
123 .put(5, IntConstants.ACT_INT_SET_HEADER_0407_I5_ID)
124 .put(6, IntConstants.ACT_INT_SET_HEADER_0407_I6_ID)
125 .put(7, IntConstants.ACT_INT_SET_HEADER_0407_I7_ID)
126 .put(8, IntConstants.ACT_INT_SET_HEADER_0407_I8_ID)
127 .put(9, IntConstants.ACT_INT_SET_HEADER_0407_I9_ID)
128 .put(10, IntConstants.ACT_INT_SET_HEADER_0407_I10_ID)
129 .put(11, IntConstants.ACT_INT_SET_HEADER_0407_I11_ID)
130 .put(12, IntConstants.ACT_INT_SET_HEADER_0407_I12_ID)
131 .put(13, IntConstants.ACT_INT_SET_HEADER_0407_I13_ID)
132 .put(14, IntConstants.ACT_INT_SET_HEADER_0407_I14_ID)
133 .put(15, IntConstants.ACT_INT_SET_HEADER_0407_I15_ID)
134 .build();
135
136 @Override
137 public void init() {
138 deviceId = this.data().deviceId();
139 flowRuleService = handler().get(FlowRuleService.class);
140 deviceService = handler().get(DeviceService.class);
141 hostService = handler().get(HostService.class);
142 coreService = handler().get(CoreService.class);
143 appId = coreService.getAppId(PIPELINE_APP_NAME);
144 if (appId == null) {
145 log.warn("Application ID is null. Cannot initialize INT-pipeline.");
146 return;
147 }
148
149 Set<PortNumber> hostPorts = deviceService.getPorts(deviceId).stream().filter(port ->
150 hostService.getConnectedHosts(new ConnectPoint(deviceId, port.number())).size() > 0
151 ).map(Port::number).collect(Collectors.toSet());
152 List<FlowRule> flowRules = new ArrayList<>();
153
154 // process_int_transit.tb_int_insert
155 PiActionParam transitIdParam = new PiActionParam(
156 IntConstants.ACT_PRM_SWITCH_ID,
157 ImmutableByteSequence.copyFrom(
158 Integer.parseInt(deviceId.toString().substring(
159 deviceId.toString().length() - 2))));
160 PiAction transitAction = PiAction.builder()
161 .withId(IntConstants.ACT_INT_TRANSIT_ID)
162 .withParameter(transitIdParam)
163 .build();
164 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
165 .piTableAction(transitAction)
166 .build();
167
168 FlowRule transitFlowRule = DefaultFlowRule.builder()
169 .withTreatment(treatment)
170 .fromApp(appId)
171 .withPriority(DEFAULT_PRIORITY)
172 .makePermanent()
173 .forDevice(deviceId)
174 .forTable(IntConstants.TBL_INT_INSERT_ID)
175 .build();
176 flowRules.add(transitFlowRule);
177
178 for (PortNumber portNumber: hostPorts) {
179 // process_set_source_sink.tb_set_source for each host-facing port
180 PiCriterion ingressCriterion = PiCriterion.builder()
181 .matchExact(BasicConstants.HDR_IN_PORT_ID, portNumber.toLong())
182 .build();
183 TrafficSelector srcSelector = DefaultTrafficSelector.builder()
184 .matchPi(ingressCriterion)
185 .build();
186 PiAction setSourceAct = PiAction.builder()
187 .withId(IntConstants.ACT_INT_SET_SOURCE_ID)
188 .build();
189 TrafficTreatment srcTreatment = DefaultTrafficTreatment.builder()
190 .piTableAction(setSourceAct)
191 .build();
192 FlowRule srcFlowRule = DefaultFlowRule.builder()
193 .withSelector(srcSelector)
194 .withTreatment(srcTreatment)
195 .fromApp(appId)
196 .withPriority(DEFAULT_PRIORITY)
197 .makePermanent()
198 .forDevice(deviceId)
199 .forTable(IntConstants.TBL_SET_SOURCE_ID)
200 .build();
201 flowRules.add(srcFlowRule);
202
203 // process_set_source_sink.tb_set_sink
204 PiCriterion egressCriterion = PiCriterion.builder()
205 .matchExact(IntConstants.HDR_OUT_PORT_ID, portNumber.toLong())
206 .build();
207 TrafficSelector sinkSelector = DefaultTrafficSelector.builder()
208 .matchPi(egressCriterion)
209 .build();
210 PiAction setSinkAct = PiAction.builder()
211 .withId(IntConstants.ACT_INT_SET_SINK_ID)
212 .build();
213 TrafficTreatment sinkTreatment = DefaultTrafficTreatment.builder()
214 .piTableAction(setSinkAct)
215 .build();
216 FlowRule sinkFlowRule = DefaultFlowRule.builder()
217 .withSelector(sinkSelector)
218 .withTreatment(sinkTreatment)
219 .fromApp(appId)
220 .withPriority(DEFAULT_PRIORITY)
221 .makePermanent()
222 .forDevice(deviceId)
223 .forTable(IntConstants.TBL_SET_SINK_ID)
224 .build();
225 flowRules.add(sinkFlowRule);
226 }
227 flowRules.forEach(flowRule -> flowRuleService.applyFlowRules(flowRule));
228
229 // Populate tb_int_inst_0003 table
230 INST_0003_ACTION_MAP.forEach((matchValue, actionId) ->
231 populateInstTableEntry(IntConstants.TBL_INT_INST_0003_ID,
232 IntConstants.INT_HDR_INST_MASK_0003_ID,
233 matchValue,
234 actionId,
235 appId));
236 // Populate tb_int_inst_0407 table
237 INST_0407_ACTION_MAP.forEach((matchValue, actionId) ->
238 populateInstTableEntry(IntConstants.TBL_INT_INST_0407_ID,
239 IntConstants.INT_HDR_INST_MASK_0407_ID,
240 matchValue,
241 actionId,
242 appId));
243 }
244
245 @Override
246 public CompletableFuture<Boolean> addIntObjective(IntObjective obj) {
247 // TODO: support different types of watchlist other than flow watchlist
248
249 return CompletableFuture.supplyAsync(
250 () -> processIntObjective(obj, true),
251 SharedExecutors.getPoolThreadExecutor()
252 );
253 }
254
255 @Override
256 public CompletableFuture<Boolean> removeIntObjective(IntObjective obj) {
257 return CompletableFuture.supplyAsync(
258 () -> processIntObjective(obj, false),
259 SharedExecutors.getPoolThreadExecutor()
260 );
261 }
262
263 @Override
264 public CompletableFuture<Boolean> setupIntConfig(IntConfig config) {
265 return CompletableFuture.supplyAsync(
266 () -> setupIntReportInternal(config),
267 SharedExecutors.getPoolThreadExecutor()
268 );
269 }
270
271 private void populateInstTableEntry(PiTableId tableId, PiMatchFieldId matchFieldId,
272 int matchValue, PiActionId actionId, ApplicationId appId) {
273 PiCriterion instCriterion = PiCriterion.builder()
274 .matchExact(matchFieldId, matchValue)
275 .build();
276 TrafficSelector instSelector = DefaultTrafficSelector.builder()
277 .matchPi(instCriterion)
278 .build();
279 PiAction instAction = PiAction.builder()
280 .withId(actionId)
281 .build();
282 TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
283 .piTableAction(instAction)
284 .build();
285
286 FlowRule instFlowRule = DefaultFlowRule.builder()
287 .withSelector(instSelector)
288 .withTreatment(instTreatment)
289 .withPriority(DEFAULT_PRIORITY)
290 .makePermanent()
291 .forDevice(deviceId)
292 .forTable(tableId)
293 .fromApp(appId)
294 .build();
295
296 flowRuleService.applyFlowRules(instFlowRule);
297 }
298
299 private FlowRule buildWatchlistEntry(IntObjective obj) {
300 coreService = handler().get(CoreService.class);
301 appId = coreService.getAppId(PIPELINE_APP_NAME);
302 if (appId == null) {
303 log.warn("Application ID is null. Cannot initialize INT-pipeline.");
304 return null;
305 }
306 int instructionBitmap = buildInstructionBitmap(obj.metadataTypes());
307 PiActionParam maxHopParam = new PiActionParam(
308 IntConstants.ACT_PRM_MAX_HOP_ID,
309 ImmutableByteSequence.copyFrom(MAXHOP));
310 PiActionParam instCntParam = new PiActionParam(
311 IntConstants.ACT_PRM_INS_CNT_ID,
312 ImmutableByteSequence.copyFrom(Integer.bitCount(instructionBitmap)));
313 PiActionParam inst0003Param = new PiActionParam(
314 IntConstants.ACT_PRM_INS_MASK0003_ID,
315 ImmutableByteSequence.copyFrom((instructionBitmap >> 12) & 0xF));
316 PiActionParam inst0407Param = new PiActionParam(
317 IntConstants.ACT_PRM_INS_MASK0407_ID,
318 ImmutableByteSequence.copyFrom((instructionBitmap >> 8) & 0xF));
319
320 PiAction intSourceAction = PiAction.builder()
321 .withId(IntConstants.ACT_INT_SOURCE_DSCP_ID)
322 .withParameter(maxHopParam)
323 .withParameter(instCntParam)
324 .withParameter(inst0003Param)
325 .withParameter(inst0407Param)
326 .build();
327
328 TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
329 .piTableAction(intSourceAction)
330 .build();
331
332 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
333 for (Criterion criterion : obj.selector().criteria()) {
334 switch (criterion.type()) {
335 case IPV4_SRC:
336 sBuilder.matchIPSrc(((IPCriterion) criterion).ip());
337 break;
338 case IPV4_DST:
339 sBuilder.matchIPDst(((IPCriterion) criterion).ip());
340 break;
341 case TCP_SRC:
342 sBuilder.matchPi(
343 PiCriterion.builder().matchTernary(
344 IntConstants.LOCAL_META_SRC_PORT_ID,
345 ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
346 .build());
347 break;
348 case UDP_SRC:
349 sBuilder.matchPi(
350 PiCriterion.builder().matchTernary(
351 IntConstants.LOCAL_META_SRC_PORT_ID,
352 ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
353 .build());
354 break;
355 case TCP_DST:
356 sBuilder.matchPi(
357 PiCriterion.builder().matchTernary(
358 IntConstants.LOCAL_META_DST_PORT_ID,
359 ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
360 .build());
361 break;
362 case UDP_DST:
363 sBuilder.matchPi(
364 PiCriterion.builder().matchTernary(
365 IntConstants.LOCAL_META_DST_PORT_ID,
366 ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
367 .build());
368 break;
369 default:
370 log.warn("Unsupported criterion type: {}", criterion.type());
371 }
372 }
373
374 return DefaultFlowRule.builder()
375 .forDevice(this.data().deviceId())
376 .withSelector(sBuilder.build())
377 .withTreatment(instTreatment)
378 .withPriority(DEFAULT_PRIORITY)
379 .forTable(IntConstants.TBL_INT_SOURCE_ID)
380 .fromApp(appId)
381 .withIdleTimeout(IDLE_TIMEOUT)
382 .build();
383 }
384
385 private int buildInstructionBitmap(Set<IntIntent.IntMetadataType> metadataTypes) {
386 int instBitmap = 0;
387 for (IntIntent.IntMetadataType metadataType : metadataTypes) {
388 switch (metadataType) {
389 case SWITCH_ID:
390 instBitmap |= (1 << 15);
391 break;
392 case L1_PORT_ID:
393 instBitmap |= (1 << 14);
394 break;
395 case HOP_LATENCY:
396 instBitmap |= (1 << 13);
397 break;
398 case QUEUE_OCCUPANCY:
399 instBitmap |= (1 << 12);
400 break;
401 case INGRESS_TIMESTAMP:
402 instBitmap |= (1 << 11);
403 break;
404 case EGRESS_TIMESTAMP:
405 instBitmap |= (1 << 10);
406 break;
407 case L2_PORT_ID:
408 instBitmap |= (1 << 9);
409 break;
410 case EGRESS_TX_UTIL:
411 instBitmap |= (1 << 8);
412 break;
413 default:
414 log.info("Unsupported metadata type {}. Ignoring...", metadataType);
415 break;
416 }
417 }
418 return instBitmap;
419 }
420
421 /**
422 * Returns a subset of Criterion from given selector,
423 * which is unsupported by this INT pipeline.
424 *
425 * @param selector a traffic selector
426 * @return a subset of Criterion from given selector, unsupported by this INT pipeline,
427 * empty if all criteria are supported.
428 */
429 private Set<Criterion> unsupportedSelectors(TrafficSelector selector) {
430 return selector.criteria().stream()
431 .filter(criterion -> !SUPPORTED_CRITERION.contains(criterion.type()))
432 .collect(Collectors.toSet());
433 }
434
435 private boolean processIntObjective(IntObjective obj, boolean install) {
436 flowRuleService = handler().get(FlowRuleService.class);
437 deviceId = this.data().deviceId();
438 if (install && !unsupportedSelectors(obj.selector()).isEmpty()) {
439 log.warn("Device {} does not support criteria {} for INT.",
440 deviceId, unsupportedSelectors(obj.selector()));
441 return false;
442 }
443
444 FlowRule flowRule = buildWatchlistEntry(obj);
445 if (flowRule != null) {
446 if (install) {
447 flowRuleService.applyFlowRules(flowRule);
448 } else {
449 flowRuleService.removeFlowRules(flowRule);
450 }
451 log.debug("IntObjective {} has been {} {}",
452 obj, install ? "installed to" : "removed from", deviceId);
453 return true;
454 } else {
455 log.warn("Failed to {} IntObjective {} on {}",
456 install ? "install" : "remove", obj, deviceId);
457 return false;
458 }
459 }
460
461 private boolean setupIntReportInternal(IntConfig cfg) {
462 flowRuleService = handler().get(FlowRuleService.class);
463
464 FlowRule reportRule = buildReportEntry(cfg, PKT_INSTANCE_TYPE_INGRESS_CLONE);
465 if (reportRule != null) {
466 flowRuleService.applyFlowRules(reportRule);
467 log.info("Report entry {} has been added to {}", reportRule, this.data().deviceId());
468 return true;
469 } else {
470 log.warn("Failed to add report entry on {}", this.data().deviceId());
471 return false;
472 }
473 }
474
475 private FlowRule buildReportEntry(IntConfig cfg, int type) {
476 coreService = handler().get(CoreService.class);
477 appId = coreService.getAppId(PIPELINE_APP_NAME);
478 if (appId == null) {
479 log.warn("Application ID is null. Cannot build report entry.");
480 return null;
481 }
482
483 PiCriterion instTypeCriterion = PiCriterion.builder()
484 .matchExact(IntConstants.STD_META_INSTANCE_TYPE_ID, type)
485 .build();
486 TrafficSelector selector = DefaultTrafficSelector.builder()
487 .matchPi(instTypeCriterion)
488 .build();
489 PiActionParam srcMacParam = new PiActionParam(
490 IntConstants.ACT_PRM_SRC_MAC_ID,
491 ImmutableByteSequence.copyFrom(cfg.sinkMac().toBytes()));
492 PiActionParam nextHopMacParam = new PiActionParam(
493 IntConstants.ACT_PRM_MON_MAC_ID,
494 ImmutableByteSequence.copyFrom(cfg.collectorNextHopMac().toBytes()));
495 PiActionParam srcIpParam = new PiActionParam(
496 IntConstants.ACT_PRM_SRC_IP_ID,
497 ImmutableByteSequence.copyFrom(cfg.sinkIp().toOctets()));
498 PiActionParam monIpParam = new PiActionParam(
499 IntConstants.ACT_PRM_MON_IP_ID,
500 ImmutableByteSequence.copyFrom(cfg.collectorIp().toOctets()));
501 PiActionParam monPortParam = new PiActionParam(
502 IntConstants.ACT_PRM_MON_PORT_ID,
503 ImmutableByteSequence.copyFrom(cfg.collectorPort().toInt()));
504 PiAction reportAction = PiAction.builder()
505 .withId(IntConstants.ACT_DO_REPORT_ENCAP_ID)
506 .withParameter(srcMacParam)
507 .withParameter(nextHopMacParam)
508 .withParameter(srcIpParam)
509 .withParameter(monIpParam)
510 .withParameter(monPortParam)
511 .build();
512 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
513 .piTableAction(reportAction)
514 .build();
515
516 return DefaultFlowRule.builder()
517 .withSelector(selector)
518 .withTreatment(treatment)
519 .fromApp(appId)
520 .withPriority(DEFAULT_PRIORITY)
521 .makePermanent()
522 .forDevice(this.data().deviceId())
523 .forTable(IntConstants.TBL_GENERATE_REPORT_ID)
524 .build();
525 }
526
527}