/* This file is part of GNUnet. Copyright (C) 2012, 2013 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gnunet.identity; import com.google.common.collect.Lists; import org.gnunet.identity.messages.*; import org.gnunet.mq.Envelope; import org.gnunet.requests.Request; import org.gnunet.requests.RequestIdentifier; import org.gnunet.requests.SequentialRequestContainer; import org.gnunet.util.*; import org.gnunet.util.crypto.EcdsaPrivateKey; import org.gnunet.util.crypto.EcdsaPublicKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; /** * Make requests to the identity service. */ public class Identity { private static final Logger logger = LoggerFactory .getLogger(Identity.class); private Configuration configuration; private SequentialRequestContainer requests; private Client client; private IdentityListCallback identityListCallback; private List knownEgos = Lists.newLinkedList(); private static Ego anonymousEgo; /** * Look up the given ego name. * * @param configuration configuration to use for connecting to * the identity service * @param egoName ego name to look up * @param identityCallback handler for lookup result * @return a handle to cancel the lookup */ public static Cancelable lookup(Configuration configuration, final String egoName, final IdentityCallback identityCallback) { final Identity myIdentity = new Identity(); final CancellationToken cancellationToken = new CancellationToken(); myIdentity.connect(configuration, new IdentityListCallback() { boolean done = false; @Override public void onEgoAdd(Ego ego) { if (done) { throw new AssertionError(); } if (ego.name.equals(egoName)) { done = true; cancellationToken.cancel(); identityCallback.onEgo(ego); } } @Override public void onEgoDelete(Ego ego) { if (done) { throw new AssertionError(); } logger.error("unsolicited ego delete on lookup"); done = true; cancellationToken.cancel(); identityCallback.onError("identity service misbehaved"); } @Override public void onEgoRename(String oldName, Ego ego) { if (done) { throw new AssertionError(); } logger.error("unsolicited ego rename on lookup"); done = true; cancellationToken.cancel(); identityCallback.onError("identity service misbehaved"); } @Override public void onListEnd() { if (done) { throw new AssertionError(); } done = true; cancellationToken.cancel(); identityCallback.onError("identity not found"); } }); cancellationToken.add(new Cancelable() { @Override public void cancel() { myIdentity.disconnect(); } }); return cancellationToken; } /** * An ego. */ public static class Ego { private String name; private EcdsaPrivateKey privateKey; /** * Create an ego with the given name and private key. * * @param egoName the ego name * @param privateKey the ego's private key */ public Ego(String egoName, EcdsaPrivateKey privateKey) { this.name = egoName; this.privateKey = privateKey; } /** * Get the ego's private key. * * @return the ego's private key */ public EcdsaPrivateKey getPrivateKey() { return privateKey; } /** * Compute the ego's public key from its private key. * * @return the ego's public key */ public EcdsaPublicKey getPublicKey() { return privateKey.getPublicKey(); } /** * Get the ego's name. * * @return the ego's name */ public String getName() { return name; } } public abstract class IdentityRequest extends Request { public void onError(String errorMessage) { throw new AssertionError("unexpected error message: " + errorMessage); } public void onResult() { throw new AssertionError("unexpected result"); } } public class GetDefaultRequest extends IdentityRequest { IdentityCallback identityCallback; String serviceName; GetDefaultRequest(String serviceName, IdentityCallback identityCallback) { this.identityCallback = identityCallback; this.serviceName = serviceName; } @Override public Envelope assembleRequest() { GetDefaultMessage m = new GetDefaultMessage(); m.nameLength = serviceName.length() + 1; m.serviceName = serviceName; return new Envelope(m); } @Override public void onError(String errorMessage) { identityCallback.onError(errorMessage); } } public class SetDefaultRequest extends IdentityRequest { IdentityContinuation cont; String serviceName; Ego ego; SetDefaultRequest(String serviceName, Ego ego) { this.serviceName = serviceName; this.ego = ego; } @Override public Envelope assembleRequest() { SetDefaultMessage m = new SetDefaultMessage(); m.nameLength = serviceName.length() + 1; m.serviceName = serviceName; m.privateKey = ego.privateKey; return new Envelope(m); } @Override public void onError(String errorMessage) { cont.onError(errorMessage); } @Override public void onResult() { cont.onDone(); } } public class RenameRequest extends IdentityRequest { IdentityContinuation cont; String oldName; String newName; RenameRequest(String newName, String oldName) { this.oldName = oldName; this.newName = newName; } @Override public Envelope assembleRequest() { RenameMessage m = new RenameMessage(); m.newName = newName; m.newNameLength = newName.length() + 1; m.oldName = oldName; m.oldNameLength = oldName.length() + 1; return new Envelope(m); } @Override public void onError(String errorMessage) { cont.onError(errorMessage); } @Override public void onResult() { cont.onDone(); } } public class DeleteRequest extends IdentityRequest { IdentityContinuation cont; String name; DeleteRequest(String name) { this.name = name; } @Override public Envelope assembleRequest() { DeleteMessage m = new DeleteMessage(); m.name = name; m.nameLength = name.length() + 1; return new Envelope(m); } @Override public void onError(String errorMessage) { cont.onError(errorMessage); } @Override public void onResult() { cont.onDone(); } } public class CreateRequest extends IdentityRequest { final EcdsaPrivateKey privateKey; IdentityContinuation cont; String name; CreateRequest(String name, EcdsaPrivateKey privateKey, IdentityContinuation cont) { this.cont = cont; this.privateKey = privateKey; this.name = name; } @Override public Envelope assembleRequest() { CreateRequestMessage m = new CreateRequestMessage(); m.name = name; m.nameLength = name.length() + 1; m.privateKey = privateKey; return new Envelope(m); } @Override public void onError(String errorMessage) { cont.onError(errorMessage); } @Override public void onResult() { cont.onDone(); } } /** * Get the anonymous ego. The anonymous ego has a publicly * known private key. * * @return the anonymous ego */ public static Ego getAnonymousEgo() { if (anonymousEgo == null) { anonymousEgo = new Ego(null, EcdsaPrivateKey.getAnonymous()); } return anonymousEgo; } /** * Create a handle that can connect to the identity service. * Nothing will happen until calling connect. */ public Identity() { // do nothing } /** * Connect to the identity service. * * @param configuration configuration to use * @param identityListCallback callback that receives initially the list of all egos, * and subsequently changes to egos */ public void connect(Configuration configuration, IdentityListCallback identityListCallback) { this.configuration = configuration; this.client = new Client("identity", configuration); this.client.installReceiver(new IdentityReceiver()); requests = new SequentialRequestContainer(this.client); this.identityListCallback = identityListCallback; StartMessage m = new StartMessage(); client.send(m); } /** * Get the default ego for a service * * @param serviceName name of the service * @param identityCallback callback that receives the default ego * @return a handle to cancel the operation */ public Cancelable get(String serviceName, IdentityCallback identityCallback) { return requests.addRequest(new GetDefaultRequest(serviceName, identityCallback)); } /** * Set the default ego for a service. * * @param serviceName service * @param ego ego * @return object for cancellation */ public Cancelable set(String serviceName, Ego ego) { return requests.addRequest(new SetDefaultRequest(serviceName, ego)); } /** * Disconnect from the identity service. */ public void disconnect() { client.disconnect(); client = null; } /** * Create a new ego with the given name. * * @param name ego name * @param cont continuation * @return object for cancellation */ public Cancelable create(String name, IdentityContinuation cont) { EcdsaPrivateKey privateKey = EcdsaPrivateKey.createRandom(); return requests.addRequest(new CreateRequest(name, privateKey, cont)); } /** * Rename an ego. * * @param oldName old name of the ego * @param newName new name of the ego * @return object for cancellation */ public Cancelable rename(String oldName, String newName) { return requests.addRequest(new RenameRequest(newName, oldName)); } /** * Delete an ego. * * @param name name of the ego to delete * @return object for cancellation */ public Cancelable delete(String name) { return requests.addRequest(new DeleteRequest(name)); } private Ego getEgoForKey(EcdsaPrivateKey privateKey) { for (Ego ex : knownEgos) { if (ex.privateKey.equals(privateKey)) { return ex; } } return null; } public class IdentityReceiver extends RunaboutMessageReceiver { @Override public void handleError() { logger.warn("identity service disconnected"); // FIXME: should have exp. backoff client.reconnect(); } public void visit(ResultCodeMessage m) { RequestIdentifier rId = requests.getRequestIdentifier(); IdentityRequest r = rId.getRequest(); if (null == r) { logger.warn("unsolicited result code message"); return; } if (m.errorMessage != null) { r.onError(m.errorMessage); } else { r.onResult(); } rId.retire(); } public void visit(SetDefaultMessage m) { RequestIdentifier rId = requests.getRequestIdentifier(); IdentityRequest r = rId.getRequest(); if (!(r instanceof GetDefaultRequest)) { logger.error("unexpected 'default ego' response"); return; } GetDefaultRequest gdr = (GetDefaultRequest) r; Ego ego = getEgoForKey(m.privateKey); if (null != ego) gdr.identityCallback.onEgo(ego); rId.retire(); } public void visit(final UpdateListMessage m) { if (m.endOfList != 0) { if (null != identityListCallback) identityListCallback.onListEnd(); return; } if (m.nameLength == 0) { Ego e = getEgoForKey(m.privateKey); if (null != e) { knownEgos.remove(e); } if (null != identityListCallback) identityListCallback.onEgoDelete(e); } else { Ego existingEgo = getEgoForKey(m.privateKey); if (existingEgo == null) { Ego ego = new Ego(m.egoName, m.privateKey); knownEgos.add(ego); if (null != identityListCallback) identityListCallback.onEgoAdd(ego); } else { // rename String oldName = existingEgo.name; existingEgo.name = m.egoName; if (null != identityListCallback) identityListCallback.onEgoRename(oldName, existingEgo); } } } } }