1 /*
   2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.jgss;
  27 
  28 import org.ietf.jgss.*;
  29 import sun.security.jgss.spi.*;
  30 
  31 import java.util.*;
  32 import sun.security.jgss.spnego.SpNegoCredElement;
  33 
  34 public class GSSCredentialImpl implements GSSCredential {
  35 
  36     private GSSManagerImpl gssManager = null;
  37     private boolean destroyed = false;
  38 
  39     /*
  40      * We store all elements in a hashtable, using <oid, usage> as the
  41      * key. This makes it easy to locate the specific kind of credential we
  42      * need. The implementation needs to be optimized for the case where
  43      * there is just one element (tempCred).
  44      */
  45     private Hashtable<SearchKey, GSSCredentialSpi> hashtable = null;
  46 
  47     // XXX Optimization for single mech usage
  48     private GSSCredentialSpi tempCred = null;
  49 
  50     public GSSCredentialImpl() {
  51         // Useless
  52     }
  53 
  54     // Used by new ExtendedGSSCredential.ExtendedGSSCredentialImpl(cred)
  55     protected GSSCredentialImpl(GSSCredentialImpl src) {
  56         this.gssManager = src.gssManager;
  57         this.destroyed = src.destroyed;
  58         this.hashtable = src.hashtable;
  59         this.tempCred = src.tempCred;
  60     }
  61 
  62     GSSCredentialImpl(GSSManagerImpl gssManager, int usage)
  63         throws GSSException {
  64         this(gssManager, null, GSSCredential.DEFAULT_LIFETIME,
  65              (Oid[]) null, usage);
  66     }
  67 
  68     GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name,
  69                              int lifetime, Oid mech, int usage)
  70         throws GSSException {
  71         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
  72 
  73         init(gssManager);
  74         add(name, lifetime, lifetime, mech, usage);
  75     }
  76 
  77     GSSCredentialImpl(GSSManagerImpl gssManager, GSSName name,
  78                       int lifetime, Oid[] mechs, int usage)
  79         throws GSSException {
  80         init(gssManager);
  81         boolean defaultList = false;
  82         if (mechs == null) {
  83             mechs = gssManager.getMechs();
  84             defaultList = true;
  85         }
  86 
  87         for (int i = 0; i < mechs.length; i++) {
  88             try {
  89                 add(name, lifetime, lifetime, mechs[i], usage);
  90             } catch (GSSException e) {
  91                 if (defaultList) {
  92                     // Try the next mechanism
  93                     GSSUtil.debug("Ignore " + e + " while acquring cred for "
  94                         + mechs[i]);
  95                     //e.printStackTrace();
  96                 } else throw e; // else try the next mechanism
  97             }
  98         }
  99         if ((hashtable.size() == 0) || (usage != getUsage()))
 100             throw new GSSException(GSSException.NO_CRED);
 101     }
 102 
 103     // Wrap a mech cred into a GSS cred
 104     public GSSCredentialImpl(GSSManagerImpl gssManager,
 105                       GSSCredentialSpi mechElement) throws GSSException {
 106 
 107         init(gssManager);
 108         int usage = GSSCredential.ACCEPT_ONLY;
 109         if (mechElement.isInitiatorCredential()) {
 110             if (mechElement.isAcceptorCredential()) {
 111                 usage = GSSCredential.INITIATE_AND_ACCEPT;
 112             } else {
 113                 usage = GSSCredential.INITIATE_ONLY;
 114             }
 115         }
 116         SearchKey key = new SearchKey(mechElement.getMechanism(),
 117                                         usage);
 118         tempCred = mechElement;
 119         hashtable.put(key, tempCred);
 120         // More mechs that can use this cred, say, SPNEGO
 121         if (!GSSUtil.isSpNegoMech(mechElement.getMechanism())) {
 122             key = new SearchKey(GSSUtil.GSS_SPNEGO_MECH_OID, usage);
 123             hashtable.put(key, new SpNegoCredElement(mechElement));
 124         }
 125     }
 126 
 127     void init(GSSManagerImpl gssManager) {
 128         this.gssManager = gssManager;
 129         hashtable = new Hashtable<SearchKey, GSSCredentialSpi>(
 130                                                 gssManager.getMechs().length);
 131     }
 132 
 133     public void dispose() throws GSSException {
 134         if (!destroyed) {
 135             GSSCredentialSpi element;
 136             Enumeration<GSSCredentialSpi> values = hashtable.elements();
 137             while (values.hasMoreElements()) {
 138                 element = values.nextElement();
 139                 element.dispose();
 140             }
 141             destroyed = true;
 142         }
 143     }
 144 
 145     public GSSCredential impersonate(GSSName name) throws GSSException {
 146         if (destroyed) {
 147             throw new IllegalStateException("This credential is " +
 148                                         "no longer valid");
 149         }
 150         Oid mech = tempCred.getMechanism();
 151         GSSNameSpi nameElement = (name == null ? null :
 152                                   ((GSSNameImpl)name).getElement(mech));
 153         GSSCredentialSpi cred = tempCred.impersonate(nameElement);
 154         return (cred == null ?
 155             null : GSSManagerImpl.wrap(new GSSCredentialImpl(gssManager, cred)));
 156     }
 157 
 158     public GSSName getName() throws GSSException {
 159         if (destroyed) {
 160             throw new IllegalStateException("This credential is " +
 161                                         "no longer valid");
 162         }
 163         return GSSNameImpl.wrapElement(gssManager, tempCred.getName());
 164     }
 165 
 166     public GSSName getName(Oid mech) throws GSSException {
 167 
 168         if (destroyed) {
 169             throw new IllegalStateException("This credential is " +
 170                                         "no longer valid");
 171         }
 172 
 173         SearchKey key = null;
 174         GSSCredentialSpi element = null;
 175 
 176         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
 177 
 178         key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
 179         element = hashtable.get(key);
 180 
 181         if (element == null) {
 182             key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
 183             element = hashtable.get(key);
 184         }
 185 
 186         if (element == null) {
 187             key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
 188             element = hashtable.get(key);
 189         }
 190 
 191         if (element == null) {
 192             throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
 193         }
 194 
 195         return GSSNameImpl.wrapElement(gssManager, element.getName());
 196 
 197     }
 198 
 199     /**
 200      * Returns the remaining lifetime of this credential. The remaining
 201      * lifetime is defined as the minimum lifetime, either for initiate or
 202      * for accept, across all elements contained in it. Not terribly
 203      * useful, but required by GSS-API.
 204      */
 205     public int getRemainingLifetime() throws GSSException {
 206 
 207         if (destroyed) {
 208             throw new IllegalStateException("This credential is " +
 209                                         "no longer valid");
 210         }
 211 
 212         SearchKey tempKey;
 213         GSSCredentialSpi tempCred;
 214         int tempLife = 0, tempInitLife = 0, tempAcceptLife = 0;
 215         int min = INDEFINITE_LIFETIME;
 216 
 217         for (Enumeration<SearchKey> e = hashtable.keys();
 218                                         e.hasMoreElements(); ) {
 219             tempKey = e.nextElement();
 220             tempCred = hashtable.get(tempKey);
 221             if (tempKey.getUsage() == INITIATE_ONLY)
 222                 tempLife = tempCred.getInitLifetime();
 223             else if (tempKey.getUsage() == ACCEPT_ONLY)
 224                 tempLife = tempCred.getAcceptLifetime();
 225             else {
 226                 tempInitLife = tempCred.getInitLifetime();
 227                 tempAcceptLife = tempCred.getAcceptLifetime();
 228                 tempLife = (tempInitLife < tempAcceptLife ?
 229                             tempInitLife:
 230                             tempAcceptLife);
 231             }
 232             if (min > tempLife)
 233                 min = tempLife;
 234         }
 235 
 236         return min;
 237     }
 238 
 239     public int getRemainingInitLifetime(Oid mech) throws GSSException {
 240 
 241         if (destroyed) {
 242             throw new IllegalStateException("This credential is " +
 243                                         "no longer valid");
 244         }
 245 
 246         GSSCredentialSpi element = null;
 247         SearchKey key = null;
 248         boolean found = false;
 249         int max = 0;
 250 
 251         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
 252 
 253         key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
 254         element = hashtable.get(key);
 255 
 256         if (element != null) {
 257             found = true;
 258             if (max < element.getInitLifetime())
 259                 max = element.getInitLifetime();
 260         }
 261 
 262         key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
 263         element = hashtable.get(key);
 264 
 265         if (element != null) {
 266             found = true;
 267             if (max < element.getInitLifetime())
 268                 max = element.getInitLifetime();
 269         }
 270 
 271         if (!found) {
 272             throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
 273         }
 274 
 275         return max;
 276 
 277     }
 278 
 279     public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
 280 
 281         if (destroyed) {
 282             throw new IllegalStateException("This credential is " +
 283                                         "no longer valid");
 284         }
 285 
 286         GSSCredentialSpi element = null;
 287         SearchKey key = null;
 288         boolean found = false;
 289         int max = 0;
 290 
 291         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
 292 
 293         key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
 294         element = hashtable.get(key);
 295 
 296         if (element != null) {
 297             found = true;
 298             if (max < element.getAcceptLifetime())
 299                 max = element.getAcceptLifetime();
 300         }
 301 
 302         key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
 303         element = hashtable.get(key);
 304 
 305         if (element != null) {
 306             found = true;
 307             if (max < element.getAcceptLifetime())
 308                 max = element.getAcceptLifetime();
 309         }
 310 
 311         if (!found) {
 312             throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
 313         }
 314 
 315         return max;
 316 
 317     }
 318 
 319     /**
 320      * Returns the usage mode for this credential. Returns
 321      * INITIATE_AND_ACCEPT if any one element contained in it supports
 322      * INITIATE_AND_ACCEPT or if two different elements exist where one
 323      * support INITIATE_ONLY and the other supports ACCEPT_ONLY.
 324      */
 325     public int getUsage() throws GSSException {
 326 
 327         if (destroyed) {
 328             throw new IllegalStateException("This credential is " +
 329                                         "no longer valid");
 330         }
 331 
 332         SearchKey tempKey;
 333         boolean initiate = false;
 334         boolean accept = false;
 335 
 336         for (Enumeration<SearchKey> e = hashtable.keys();
 337                                         e.hasMoreElements(); ) {
 338             tempKey = e.nextElement();
 339             if (tempKey.getUsage() == INITIATE_ONLY)
 340                 initiate = true;
 341             else if (tempKey.getUsage() == ACCEPT_ONLY)
 342                 accept = true;
 343             else
 344                 return INITIATE_AND_ACCEPT;
 345         }
 346         if (initiate) {
 347             if (accept)
 348                 return INITIATE_AND_ACCEPT;
 349             else
 350                 return INITIATE_ONLY;
 351         } else
 352             return ACCEPT_ONLY;
 353     }
 354 
 355     public int getUsage(Oid mech) throws GSSException {
 356 
 357         if (destroyed) {
 358             throw new IllegalStateException("This credential is " +
 359                                         "no longer valid");
 360         }
 361 
 362         GSSCredentialSpi element = null;
 363         SearchKey key = null;
 364         boolean initiate = false;
 365         boolean accept = false;
 366 
 367         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
 368 
 369         key = new SearchKey(mech, GSSCredential.INITIATE_ONLY);
 370         element = hashtable.get(key);
 371 
 372         if (element != null) {
 373             initiate = true;
 374         }
 375 
 376         key = new SearchKey(mech, GSSCredential.ACCEPT_ONLY);
 377         element = hashtable.get(key);
 378 
 379         if (element != null) {
 380             accept = true;
 381         }
 382 
 383         key = new SearchKey(mech, GSSCredential.INITIATE_AND_ACCEPT);
 384         element = hashtable.get(key);
 385 
 386         if (element != null) {
 387             initiate = true;
 388             accept = true;
 389         }
 390 
 391         if (initiate && accept)
 392             return GSSCredential.INITIATE_AND_ACCEPT;
 393         else if (initiate)
 394             return GSSCredential.INITIATE_ONLY;
 395         else if (accept)
 396             return GSSCredential.ACCEPT_ONLY;
 397         else {
 398             throw new GSSExceptionImpl(GSSException.BAD_MECH, mech);
 399         }
 400     }
 401 
 402     public Oid[] getMechs() throws GSSException {
 403 
 404         if (destroyed) {
 405             throw new IllegalStateException("This credential is " +
 406                                         "no longer valid");
 407         }
 408         Vector<Oid> result = new Vector<Oid>(hashtable.size());
 409 
 410         for (Enumeration<SearchKey> e = hashtable.keys();
 411                                                 e.hasMoreElements(); ) {
 412             SearchKey tempKey = e.nextElement();
 413             result.addElement(tempKey.getMech());
 414         }
 415         return result.toArray(new Oid[0]);
 416     }
 417 
 418     public void add(GSSName name, int initLifetime, int acceptLifetime,
 419                     Oid mech, int usage) throws GSSException {
 420 
 421         if (destroyed) {
 422             throw new IllegalStateException("This credential is " +
 423                                         "no longer valid");
 424         }
 425         if (mech == null) mech = ProviderList.DEFAULT_MECH_OID;
 426 
 427         SearchKey key = new SearchKey(mech, usage);
 428         if (hashtable.containsKey(key)) {
 429             throw new GSSExceptionImpl(GSSException.DUPLICATE_ELEMENT,
 430                                        "Duplicate element found: " +
 431                                        getElementStr(mech, usage));
 432         }
 433 
 434         // XXX If not instance of GSSNameImpl then throw exception
 435         // Application mixing GSS implementations
 436         GSSNameSpi nameElement = (name == null ? null :
 437                                   ((GSSNameImpl)name).getElement(mech));
 438 
 439         tempCred = gssManager.getCredentialElement(nameElement,
 440                                                    initLifetime,
 441                                                    acceptLifetime,
 442                                                    mech,
 443                                                    usage);
 444         /*
 445          * Not all mechanisms support the concept of one credential element
 446          * that can be used for both initiating and accepting a context. In
 447          * the event that an application requests usage INITIATE_AND_ACCEPT
 448          * for a credential from such a mechanism, the GSS framework will
 449          * need to obtain two different credential elements from the
 450          * mechanism, one that will have usage INITIATE_ONLY and another
 451          * that will have usage ACCEPT_ONLY. The mechanism will help the
 452          * GSS-API realize this by returning a credential element with
 453          * usage INITIATE_ONLY or ACCEPT_ONLY prompting it to make another
 454          * call to getCredentialElement, this time with the other usage
 455          * mode.
 456          */
 457 
 458         if (tempCred != null) {
 459             if (usage == GSSCredential.INITIATE_AND_ACCEPT &&
 460                 (!tempCred.isAcceptorCredential() ||
 461                 !tempCred.isInitiatorCredential())) {
 462 
 463                 int currentUsage;
 464                 int desiredUsage;
 465 
 466                 if (!tempCred.isInitiatorCredential()) {
 467                     currentUsage = GSSCredential.ACCEPT_ONLY;
 468                     desiredUsage = GSSCredential.INITIATE_ONLY;
 469                 } else {
 470                     currentUsage = GSSCredential.INITIATE_ONLY;
 471                     desiredUsage = GSSCredential.ACCEPT_ONLY;
 472                 }
 473 
 474                 key = new SearchKey(mech, currentUsage);
 475                 hashtable.put(key, tempCred);
 476 
 477                 tempCred = gssManager.getCredentialElement(nameElement,
 478                                                         initLifetime,
 479                                                         acceptLifetime,
 480                                                         mech,
 481                                                         desiredUsage);
 482 
 483                 key = new SearchKey(mech, desiredUsage);
 484                 hashtable.put(key, tempCred);
 485             } else {
 486                 hashtable.put(key, tempCred);
 487             }
 488         }
 489     }
 490 
 491     public boolean equals(Object another) {
 492 
 493         if (destroyed) {
 494             throw new IllegalStateException("This credential is " +
 495                                         "no longer valid");
 496         }
 497 
 498         if (this == another) {
 499             return true;
 500         }
 501 
 502         if (!(another instanceof GSSCredentialImpl)) {
 503             return false;
 504         }
 505 
 506         // NOTE: The specification does not define the criteria to compare
 507         // credentials.
 508         /*
 509          * XXX
 510          * The RFC says: "Tests if this GSSCredential refers to the same
 511          * entity as the supplied object.  The two credentials must be
 512          * acquired over the same mechanisms and must refer to the same
 513          * principal.  Returns "true" if the two GSSCredentials refer to
 514          * the same entity; "false" otherwise."
 515          *
 516          * Well, when do two credentials refer to the same principal? Do
 517          * they need to have one GSSName in common for the different
 518          * GSSName's that the credential elements return? Or do all
 519          * GSSName's have to be in common when the names are exported with
 520          * their respective mechanisms for the credential elements?
 521          */
 522         return false;
 523 
 524     }
 525 
 526     /**
 527      * Returns a hashcode value for this GSSCredential.
 528      *
 529      * @return a hashCode value
 530      */
 531     public int hashCode() {
 532 
 533         if (destroyed) {
 534             throw new IllegalStateException("This credential is " +
 535                                         "no longer valid");
 536         }
 537 
 538         // NOTE: The specification does not define the criteria to compare
 539         // credentials.
 540         /*
 541          * XXX
 542          * Decide on a criteria for equals first then do this.
 543          */
 544         return 1;
 545     }
 546 
 547     /**
 548      * Returns the specified mechanism's credential-element.
 549      *
 550      * @param mechOid - the oid for mechanism to retrieve
 551      * @param throwExcep - boolean indicating if the function is
 552      *    to throw exception or return null when element is not
 553      *    found.
 554      * @return mechanism credential object
 555      * @exception GSSException of invalid mechanism
 556      */
 557     public GSSCredentialSpi getElement(Oid mechOid, boolean initiate)
 558         throws GSSException {
 559 
 560         if (destroyed) {
 561             throw new IllegalStateException("This credential is " +
 562                                         "no longer valid");
 563         }
 564 
 565         SearchKey key;
 566         GSSCredentialSpi element;
 567 
 568         if (mechOid == null) {
 569             /*
 570              * First see if the default mechanism satisfies the
 571              * desired usage.
 572              */
 573             mechOid = ProviderList.DEFAULT_MECH_OID;
 574             key = new SearchKey(mechOid,
 575                                  initiate? INITIATE_ONLY : ACCEPT_ONLY);
 576             element = hashtable.get(key);
 577             if (element == null) {
 578                 key = new SearchKey(mechOid, INITIATE_AND_ACCEPT);
 579                 element = hashtable.get(key);
 580                 if (element == null) {
 581                     /*
 582                      * Now just return any element that satisfies the
 583                      * desired usage.
 584                      */
 585                     Object[] elements = hashtable.entrySet().toArray();
 586                     for (int i = 0; i < elements.length; i++) {
 587                         element = (GSSCredentialSpi)
 588                             ((Map.Entry)elements[i]).getValue();
 589                         if (element.isInitiatorCredential() == initiate)
 590                             break;
 591                     } // for loop
 592                 }
 593             }
 594         } else {
 595 
 596             if (initiate)
 597                 key = new SearchKey(mechOid, INITIATE_ONLY);
 598             else
 599                 key = new SearchKey(mechOid, ACCEPT_ONLY);
 600 
 601             element = hashtable.get(key);
 602 
 603             if (element == null) {
 604                 key = new SearchKey(mechOid, INITIATE_AND_ACCEPT);
 605                 element = hashtable.get(key);
 606             }
 607         }
 608 
 609         if (element == null)
 610             throw new GSSExceptionImpl(GSSException.NO_CRED,
 611                                        "No credential found for: " +
 612                                        getElementStr(mechOid,
 613                                        initiate? INITIATE_ONLY : ACCEPT_ONLY));
 614         return element;
 615     }
 616 
 617     Set<GSSCredentialSpi> getElements() {
 618         HashSet<GSSCredentialSpi> retVal =
 619                         new HashSet<GSSCredentialSpi>(hashtable.size());
 620         Enumeration<GSSCredentialSpi> values = hashtable.elements();
 621         while (values.hasMoreElements()) {
 622             GSSCredentialSpi o = values.nextElement();
 623             retVal.add(o);
 624         }
 625         return retVal;
 626     }
 627 
 628     private static String getElementStr(Oid mechOid, int usage) {
 629         String displayString = mechOid.toString();
 630         if (usage == GSSCredential.INITIATE_ONLY) {
 631             displayString =
 632                 displayString.concat(" usage: Initiate");
 633         } else if (usage == GSSCredential.ACCEPT_ONLY) {
 634             displayString =
 635                 displayString.concat(" usage: Accept");
 636         } else {
 637             displayString =
 638                 displayString.concat(" usage: Initiate and Accept");
 639         }
 640         return displayString;
 641     }
 642 
 643     public String toString() {
 644 
 645         if (destroyed) {
 646             throw new IllegalStateException("This credential is " +
 647                                         "no longer valid");
 648         }
 649 
 650         GSSCredentialSpi element = null;
 651         StringBuilder sb = new StringBuilder("[GSSCredential: ");
 652         Object[] elements = hashtable.entrySet().toArray();
 653         for (int i = 0; i < elements.length; i++) {
 654             try {
 655                 sb.append('\n');
 656                 element = (GSSCredentialSpi)
 657                     ((Map.Entry)elements[i]).getValue();
 658                 sb.append(element.getName());
 659                 sb.append(' ');
 660                 sb.append(element.getMechanism());
 661                 sb.append(element.isInitiatorCredential() ?
 662                           " Initiate" : "");
 663                 sb.append(element.isAcceptorCredential() ?
 664                           " Accept" : "");
 665                 sb.append(" [");
 666                 sb.append(element.getClass());
 667                 sb.append(']');
 668             } catch (GSSException e) {
 669                 // skip to next element
 670             }
 671         }
 672         sb.append(']');
 673         return sb.toString();
 674     }
 675 
 676     static class SearchKey {
 677         private Oid mechOid = null;
 678         private int usage = GSSCredential.INITIATE_AND_ACCEPT;
 679         public SearchKey(Oid mechOid, int usage) {
 680 
 681             this.mechOid = mechOid;
 682             this.usage = usage;
 683         }
 684         public Oid getMech() {
 685             return mechOid;
 686         }
 687         public int getUsage() {
 688             return usage;
 689         }
 690         public boolean equals(Object other) {
 691             if (! (other instanceof SearchKey))
 692                 return false;
 693             SearchKey that = (SearchKey) other;
 694             return ((this.mechOid.equals(that.mechOid)) &&
 695                     (this.usage == that.usage));
 696         }
 697         public int hashCode() {
 698             return mechOid.hashCode();
 699         }
 700     }
 701 
 702 }