blob: 82be0e4bce7aa7d43c6aa1e5d324474155641c78 [file] [log] [blame]
Saurav Dase3274c82015-05-24 17:21:56 -07001/*
2 * Copyright 2015 Open Networking Laboratory
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.driver.pipeline;
17
18import com.google.common.cache.Cache;
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.RemovalCause;
21import com.google.common.cache.RemovalNotification;
22
23import org.onlab.osgi.ServiceDirectory;
24import org.onlab.packet.Ethernet;
25import org.onlab.packet.IPv4;
26import org.onlab.util.KryoNamespace;
27import org.onosproject.core.ApplicationId;
28import org.onosproject.core.CoreService;
29import org.onosproject.net.DeviceId;
30import org.onosproject.net.behaviour.NextGroup;
31import org.onosproject.net.behaviour.Pipeliner;
32import org.onosproject.net.behaviour.PipelinerContext;
33import org.onosproject.net.driver.AbstractHandlerBehaviour;
34import org.onosproject.net.flow.DefaultFlowRule;
35import org.onosproject.net.flow.DefaultTrafficSelector;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.FlowRule;
38import org.onosproject.net.flow.FlowRuleOperations;
39import org.onosproject.net.flow.FlowRuleOperationsContext;
40import org.onosproject.net.flow.FlowRuleService;
41import org.onosproject.net.flow.TrafficSelector;
42import org.onosproject.net.flow.TrafficTreatment;
43import org.onosproject.net.flow.criteria.Criteria;
44import org.onosproject.net.flow.criteria.Criterion;
45import org.onosproject.net.flow.criteria.EthCriterion;
46import org.onosproject.net.flow.criteria.EthTypeCriterion;
47import org.onosproject.net.flow.criteria.IPCriterion;
48import org.onosproject.net.flow.criteria.IPProtocolCriterion;
49import org.onosproject.net.flow.criteria.PortCriterion;
50import org.onosproject.net.flow.criteria.VlanIdCriterion;
51import org.onosproject.net.flowobjective.FilteringObjective;
52import org.onosproject.net.flowobjective.FlowObjectiveStore;
53import org.onosproject.net.flowobjective.ForwardingObjective;
54import org.onosproject.net.flowobjective.NextObjective;
55import org.onosproject.net.flowobjective.Objective;
56import org.onosproject.net.flowobjective.ObjectiveError;
57import org.onosproject.net.group.DefaultGroupBucket;
58import org.onosproject.net.group.DefaultGroupDescription;
59import org.onosproject.net.group.DefaultGroupKey;
60import org.onosproject.net.group.Group;
61import org.onosproject.net.group.GroupBucket;
62import org.onosproject.net.group.GroupBuckets;
63import org.onosproject.net.group.GroupDescription;
64import org.onosproject.net.group.GroupEvent;
65import org.onosproject.net.group.GroupKey;
66import org.onosproject.net.group.GroupListener;
67import org.onosproject.net.group.GroupService;
68import org.slf4j.Logger;
69
70import java.util.Collection;
71import java.util.Collections;
72import java.util.Set;
73import java.util.concurrent.Executors;
74import java.util.concurrent.ScheduledExecutorService;
75import java.util.concurrent.TimeUnit;
76import java.util.stream.Collectors;
77
78import static org.onlab.util.Tools.groupedThreads;
79import static org.slf4j.LoggerFactory.getLogger;
80
81/**
82 * Pica pipeline handler.
83 */
84public class PicaPipeline extends AbstractHandlerBehaviour implements Pipeliner {
85
86 protected static final int VLAN_TABLE = 252;
87 protected static final int ETHTYPE_TABLE = 252;
88 protected static final int IP_UNICAST_TABLE = 251;
89 protected static final int ACL_TABLE = 251;
90
91 private static final int CONTROLLER_PRIORITY = 255;
92 private static final int DROP_PRIORITY = 0;
93 private static final int HIGHEST_PRIORITY = 0xffff;
94
95 private final Logger log = getLogger(getClass());
96
97 private ServiceDirectory serviceDirectory;
98 private FlowRuleService flowRuleService;
99 private CoreService coreService;
100 private GroupService groupService;
101 private FlowObjectiveStore flowObjectiveStore;
102 private DeviceId deviceId;
103 private ApplicationId appId;
104
105 private KryoNamespace appKryo = new KryoNamespace.Builder()
106 .register(GroupKey.class)
107 .register(DefaultGroupKey.class)
108 .register(PicaGroup.class)
109 .register(byte[].class)
110 .build();
111
112 private Cache<GroupKey, NextObjective> pendingGroups;
113
114 private ScheduledExecutorService groupChecker =
115 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner",
116 "ovs-pica-%d"));
117
118 @Override
119 public void init(DeviceId deviceId, PipelinerContext context) {
120 this.serviceDirectory = context.directory();
121 this.deviceId = deviceId;
122
123 pendingGroups = CacheBuilder.newBuilder()
124 .expireAfterWrite(20, TimeUnit.SECONDS)
125 .removalListener((RemovalNotification<GroupKey, NextObjective> notification) -> {
126 if (notification.getCause() == RemovalCause.EXPIRED) {
127 fail(notification.getValue(), ObjectiveError.GROUPINSTALLATIONFAILED);
128 }
129 }).build();
130
131 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
132
133 coreService = serviceDirectory.get(CoreService.class);
134 flowRuleService = serviceDirectory.get(FlowRuleService.class);
135 groupService = serviceDirectory.get(GroupService.class);
136 flowObjectiveStore = context.store();
137
138 groupService.addListener(new InnerGroupListener());
139
140 appId = coreService.registerApplication(
141 "org.onosproject.driver.OVSPicaPipeline");
142
143 initializePipeline();
144 }
145
146 @Override
147 public void filter(FilteringObjective filteringObjective) {
148 if (filteringObjective.type() == FilteringObjective.Type.PERMIT) {
149 processFilter(filteringObjective,
150 filteringObjective.op() == Objective.Operation.ADD,
151 filteringObjective.appId());
152 } else {
153 fail(filteringObjective, ObjectiveError.UNSUPPORTED);
154 }
155 }
156
157 @Override
158 public void forward(ForwardingObjective fwd) {
159 Collection<FlowRule> rules;
160 FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder();
161
162 rules = processForward(fwd);
163 switch (fwd.op()) {
164 case ADD:
165 rules.stream()
166 .filter(rule -> rule != null)
167 .forEach(flowBuilder::add);
168 break;
169 case REMOVE:
170 rules.stream()
171 .filter(rule -> rule != null)
172 .forEach(flowBuilder::remove);
173 break;
174 default:
175 fail(fwd, ObjectiveError.UNKNOWN);
176 log.warn("Unknown forwarding type {}", fwd.op());
177 }
178
179
180 flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() {
181 @Override
182 public void onSuccess(FlowRuleOperations ops) {
183 pass(fwd);
184 }
185
186 @Override
187 public void onError(FlowRuleOperations ops) {
188 fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
189 }
190 }));
191
192 }
193
194 @Override
195 public void next(NextObjective nextObjective) {
196 switch (nextObjective.type()) {
197 case SIMPLE:
198 Collection<TrafficTreatment> treatments = nextObjective.next();
199 if (treatments.size() == 1) {
200 TrafficTreatment treatment = treatments.iterator().next();
201 GroupBucket bucket =
202 DefaultGroupBucket.createIndirectGroupBucket(treatment);
203 final GroupKey key = new DefaultGroupKey(appKryo.serialize(nextObjective.id()));
204 GroupDescription groupDescription
205 = new DefaultGroupDescription(deviceId,
206 GroupDescription.Type.INDIRECT,
207 new GroupBuckets(Collections
208 .singletonList(bucket)),
209 key,
210 null, // let group service determine group id
211 nextObjective.appId());
212 groupService.addGroup(groupDescription);
213 pendingGroups.put(key, nextObjective);
214 }
215 break;
216 case HASHED:
217 case BROADCAST:
218 case FAILOVER:
219 fail(nextObjective, ObjectiveError.UNSUPPORTED);
220 log.warn("Unsupported next objective type {}", nextObjective.type());
221 break;
222 default:
223 fail(nextObjective, ObjectiveError.UNKNOWN);
224 log.warn("Unknown next objective type {}", nextObjective.type());
225 }
226
227 }
228
229 private Collection<FlowRule> processForward(ForwardingObjective fwd) {
230 switch (fwd.flag()) {
231 case SPECIFIC:
232 return processSpecific(fwd);
233 case VERSATILE:
234 return processVersatile(fwd);
235 default:
236 fail(fwd, ObjectiveError.UNKNOWN);
237 log.warn("Unknown forwarding flag {}", fwd.flag());
238 }
239 return Collections.emptySet();
240 }
241
242 private Collection<FlowRule> processVersatile(ForwardingObjective fwd) {
243 log.debug("Processing versatile forwarding objective");
244 TrafficSelector selector = fwd.selector();
245
246 EthTypeCriterion ethType =
247 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
248 if (ethType == null) {
249 log.error("Versatile forwarding objective must include ethType");
250 fail(fwd, ObjectiveError.UNKNOWN);
251 return Collections.emptySet();
252 }
253 if (ethType.ethType() == Ethernet.TYPE_ARP) {
254 log.warn("Driver automatically handles ARP packets by punting to controller "
255 + " from ETHER table");
256 pass(fwd);
257 return Collections.emptySet();
258 } else if (ethType.ethType() == Ethernet.TYPE_LLDP ||
259 ethType.ethType() == Ethernet.TYPE_BSN) {
260 log.warn("Driver currently does not currently handle LLDP packets");
261 fail(fwd, ObjectiveError.UNSUPPORTED);
262 return Collections.emptySet();
263 } else if (ethType.ethType() == Ethernet.TYPE_IPV4) {
264 IPCriterion ipSrc = (IPCriterion) selector
265 .getCriterion(Criterion.Type.IPV4_SRC);
266 IPCriterion ipDst = (IPCriterion) selector
267 .getCriterion(Criterion.Type.IPV4_DST);
268 IPProtocolCriterion ipProto = (IPProtocolCriterion) selector
269 .getCriterion(Criterion.Type.IP_PROTO);
270 if (ipSrc != null) {
271 log.warn("Driver does not currently handle matching Src IP");
272 fail(fwd, ObjectiveError.UNSUPPORTED);
273 return Collections.emptySet();
274 }
275 if (ipDst != null) {
276 log.error("Driver handles Dst IP matching as specific forwarding "
277 + "objective, not versatile");
278 fail(fwd, ObjectiveError.UNSUPPORTED);
279 return Collections.emptySet();
280 }
281 if (ipProto != null && ipProto.protocol() == IPv4.PROTOCOL_TCP) {
282 log.warn("Driver automatically punts all packets reaching the "
283 + "LOCAL table to the controller");
284 pass(fwd);
285 return Collections.emptySet();
286 }
287 }
288
289 log.warn("Driver does not support given versatile forwarding objective");
290 fail(fwd, ObjectiveError.UNSUPPORTED);
291 return Collections.emptySet();
292 }
293
294 private Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
295 log.debug("Processing specific forwarding objective");
296 TrafficSelector selector = fwd.selector();
297 EthTypeCriterion ethType =
298 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
299 if (ethType == null || ethType.ethType() != Ethernet.TYPE_IPV4) {
300 fail(fwd, ObjectiveError.UNSUPPORTED);
301 return Collections.emptySet();
302 }
303
304 TrafficSelector filteredSelector =
305 DefaultTrafficSelector.builder()
306 .matchEthType(Ethernet.TYPE_IPV4)
307 .matchIPDst(
308 ((IPCriterion)
309 selector.getCriterion(Criterion.Type.IPV4_DST)).ip())
310 .build();
311
312 TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
313
314 if (fwd.nextId() != null) {
315 NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
316 GroupKey key = appKryo.deserialize(next.data());
317 Group group = groupService.getGroup(deviceId, key);
318 if (group == null) {
319 log.warn("The group left!");
320 fail(fwd, ObjectiveError.GROUPMISSING);
321 return Collections.emptySet();
322 }
323 tb.group(group.id());
324 }
325
326 FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
327 .fromApp(fwd.appId())
328 .withPriority(fwd.priority())
329 .forDevice(deviceId)
330 .withSelector(filteredSelector)
331 .withTreatment(tb.build());
332
333 if (fwd.permanent()) {
334 ruleBuilder.makePermanent();
335 } else {
336 ruleBuilder.makeTemporary(fwd.timeout());
337 }
338
339 ruleBuilder.forTable(IP_UNICAST_TABLE);
340
341
342 return Collections.singletonList(ruleBuilder.build());
343
344 }
345
346 private void processFilter(FilteringObjective filt, boolean install,
347 ApplicationId applicationId) {
348 // This driver only processes filtering criteria defined with switch
349 // ports as the key
350 PortCriterion p;
351 if (!filt.key().equals(Criteria.dummy()) &&
352 filt.key().type() == Criterion.Type.IN_PORT) {
353 p = (PortCriterion) filt.key();
354 } else {
355 log.warn("No key defined in filtering objective from app: {}. Not"
356 + "processing filtering objective", applicationId);
357 fail(filt, ObjectiveError.UNKNOWN);
358 return;
359 }
360 // convert filtering conditions for switch-intfs into flowrules
361 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
362 for (Criterion c : filt.conditions()) {
363 if (c.type() == Criterion.Type.ETH_DST) {
364 EthCriterion e = (EthCriterion) c;
365 log.debug("adding rule for MAC: {}", e.mac());
366 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
367 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
368 selector.matchEthDst(e.mac());
369 treatment.transition(IP_UNICAST_TABLE);
370 FlowRule rule = DefaultFlowRule.builder()
371 .forDevice(deviceId)
372 .withSelector(selector.build())
373 .withTreatment(treatment.build())
374 .withPriority(CONTROLLER_PRIORITY)
375 .fromApp(applicationId)
376 .makePermanent()
377 .forTable(ETHTYPE_TABLE).build();
378 ops = install ? ops.add(rule) : ops.remove(rule);
379 } else if (c.type() == Criterion.Type.VLAN_VID) {
380 VlanIdCriterion v = (VlanIdCriterion) c;
381 log.debug("adding rule for VLAN: {}", v.vlanId());
382 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
383 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
384 selector.matchVlanId(v.vlanId());
385 selector.matchInPort(p.port());
386 treatment.transition(ETHTYPE_TABLE);
387 treatment.deferred().popVlan();
388 FlowRule rule = DefaultFlowRule.builder()
389 .forDevice(deviceId)
390 .withSelector(selector.build())
391 .withTreatment(treatment.build())
392 .withPriority(CONTROLLER_PRIORITY)
393 .fromApp(applicationId)
394 .makePermanent()
395 .forTable(VLAN_TABLE).build();
396 ops = install ? ops.add(rule) : ops.remove(rule);
397 } else if (c.type() == Criterion.Type.IPV4_DST) {
398 IPCriterion ip = (IPCriterion) c;
399 log.debug("adding rule for IP: {}", ip.ip());
400 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
401 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
402 selector.matchEthType(Ethernet.TYPE_IPV4);
403 selector.matchIPDst(ip.ip());
404 treatment.transition(ACL_TABLE);
405 FlowRule rule = DefaultFlowRule.builder()
406 .forDevice(deviceId)
407 .withSelector(selector.build())
408 .withTreatment(treatment.build())
409 .withPriority(HIGHEST_PRIORITY)
410 .fromApp(applicationId)
411 .makePermanent()
412 .forTable(IP_UNICAST_TABLE).build();
413
414 ops = install ? ops.add(rule) : ops.remove(rule);
415 } else {
416 log.warn("Driver does not currently process filtering condition"
417 + " of type: {}", c.type());
418 fail(filt, ObjectiveError.UNSUPPORTED);
419 }
420 }
421 // apply filtering flow rules
422 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
423 @Override
424 public void onSuccess(FlowRuleOperations ops) {
425 pass(filt);
426 log.info("Applied filtering rules");
427 }
428
429 @Override
430 public void onError(FlowRuleOperations ops) {
431 fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED);
432 log.info("Failed to apply filtering rules");
433 }
434 }));
435 }
436
437 private void pass(Objective obj) {
438 if (obj.context().isPresent()) {
439 obj.context().get().onSuccess(obj);
440 }
441 }
442
443 private void fail(Objective obj, ObjectiveError error) {
444 if (obj.context().isPresent()) {
445 obj.context().get().onError(obj, error);
446 }
447 }
448
449 private void initializePipeline() {
450 processVlanTable(true);
451 processEtherTable(true);
452 processIpUnicastTable(true);
453 //processACLTable(true);
454 }
455
456 private void processVlanTable(boolean install) {
457 TrafficSelector.Builder selector;
458 TrafficTreatment.Builder treatment;
459 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
460 FlowRule rule;
461
462
463 //Drop rule
464 selector = DefaultTrafficSelector.builder();
465 treatment = DefaultTrafficTreatment.builder();
466
467 treatment.drop();
468
469 rule = DefaultFlowRule.builder()
470 .forDevice(deviceId)
471 .withSelector(selector.build())
472 .withTreatment(treatment.build())
473 .withPriority(DROP_PRIORITY)
474 .fromApp(appId)
475 .makePermanent()
476 .forTable(VLAN_TABLE).build();
477
478 ops = install ? ops.add(rule) : ops.remove(rule);
479
480 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
481 @Override
482 public void onSuccess(FlowRuleOperations ops) {
483 log.info("Provisioned vlan table");
484 }
485
486 @Override
487 public void onError(FlowRuleOperations ops) {
488 log.info("Failed to provision vlan table");
489 }
490 }));
491 }
492
493 private void processEtherTable(boolean install) {
494 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
495 TrafficTreatment.Builder treatment = DefaultTrafficTreatment
496 .builder();
497 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
498 FlowRule rule;
499
500 selector.matchEthType(Ethernet.TYPE_ARP);
501 treatment.punt();
502
503 rule = DefaultFlowRule.builder()
504 .forDevice(deviceId)
505 .withSelector(selector.build())
506 .withTreatment(treatment.build())
507 .withPriority(CONTROLLER_PRIORITY)
508 .fromApp(appId)
509 .makePermanent()
510 .forTable(ETHTYPE_TABLE).build();
511
512 ops = install ? ops.add(rule) : ops.remove(rule);
513
514 selector = DefaultTrafficSelector.builder();
515 treatment = DefaultTrafficTreatment.builder();
516
517 selector.matchEthType(Ethernet.TYPE_IPV4);
518 treatment.transition(IP_UNICAST_TABLE);
519
520 rule = DefaultFlowRule.builder()
521 .forDevice(deviceId)
522 .withPriority(CONTROLLER_PRIORITY)
523 .withSelector(selector.build())
524 .withTreatment(treatment.build())
525 .fromApp(appId)
526 .makePermanent()
527 .forTable(ETHTYPE_TABLE).build();
528
529 ops = install ? ops.add(rule) : ops.remove(rule);
530
531 //Drop rule
532 selector = DefaultTrafficSelector.builder();
533 treatment = DefaultTrafficTreatment.builder();
534
535 treatment.drop();
536
537 rule = DefaultFlowRule.builder()
538 .forDevice(deviceId)
539 .withSelector(selector.build())
540 .withTreatment(treatment.build())
541 .withPriority(DROP_PRIORITY)
542 .fromApp(appId)
543 .makePermanent()
544 .forTable(ETHTYPE_TABLE).build();
545
546
547 ops = install ? ops.add(rule) : ops.remove(rule);
548
549 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
550 @Override
551 public void onSuccess(FlowRuleOperations ops) {
552 log.info("Provisioned ether table");
553 }
554
555 @Override
556 public void onError(FlowRuleOperations ops) {
557 log.info("Failed to provision ether table");
558 }
559 }));
560
561 }
562
563
564 private void processIpUnicastTable(boolean install) {
565 TrafficSelector.Builder selector;
566 TrafficTreatment.Builder treatment;
567 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
568 FlowRule rule;
569
570 //Drop rule
571 selector = DefaultTrafficSelector.builder();
572 treatment = DefaultTrafficTreatment.builder();
573
574 treatment.drop();
575
576 rule = DefaultFlowRule.builder()
577 .forDevice(deviceId)
578 .withSelector(selector.build())
579 .withTreatment(treatment.build())
580 .withPriority(DROP_PRIORITY)
581 .fromApp(appId)
582 .makePermanent()
583 .forTable(IP_UNICAST_TABLE).build();
584
585 ops = install ? ops.add(rule) : ops.remove(rule);
586
587 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
588 @Override
589 public void onSuccess(FlowRuleOperations ops) {
590 log.info("Provisioned FIB table");
591 }
592
593 @Override
594 public void onError(FlowRuleOperations ops) {
595 log.info("Failed to provision FIB table");
596 }
597 }));
598 }
599
600
601 private class InnerGroupListener implements GroupListener {
602 @Override
603 public void event(GroupEvent event) {
604 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
605 GroupKey key = event.subject().appCookie();
606
607 NextObjective obj = pendingGroups.getIfPresent(key);
608 if (obj != null) {
609 flowObjectiveStore.putNextGroup(obj.id(), new PicaGroup(key));
610 pass(obj);
611 pendingGroups.invalidate(key);
612 }
613 }
614 }
615 }
616
617
618 private class GroupChecker implements Runnable {
619
620 @Override
621 public void run() {
622 Set<GroupKey> keys = pendingGroups.asMap().keySet().stream()
623 .filter(key -> groupService.getGroup(deviceId, key) != null)
624 .collect(Collectors.toSet());
625
626 keys.stream().forEach(key -> {
627 NextObjective obj = pendingGroups.getIfPresent(key);
628 if (obj == null) {
629 return;
630 }
631 pass(obj);
632 pendingGroups.invalidate(key);
633 log.info("Heard back from group service for group {}. "
634 + "Applying pending forwarding objectives", obj.id());
635 flowObjectiveStore.putNextGroup(obj.id(), new PicaGroup(key));
636 });
637 }
638 }
639
640 private class PicaGroup implements NextGroup {
641
642 private final GroupKey key;
643
644 public PicaGroup(GroupKey key) {
645 this.key = key;
646 }
647
648 @SuppressWarnings("unused")
649 public GroupKey key() {
650 return key;
651 }
652
653 @Override
654 public byte[] data() {
655 return appKryo.serialize(key);
656 }
657
658 }
659}