/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.federation.xmpp; import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.ByteString; import org.dom4j.Element; import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo; import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec; import org.xmpp.packet.IQ; import org.xmpp.packet.Message; import org.xmpp.packet.Packet; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicLong; /** * Common utility code for XMPP packet generation and parsing. */ public class XmppUtil { private static final AtomicLong idSequenceNo = new AtomicLong(0); private static final Random random = new SecureRandom(); public static final IdURIEncoderDecoder waveletNameCodec = new IdURIEncoderDecoder(new JavaUrlCodec()); // If non-null, this fake unique ID will be returned from generateUniqueId() // rather than a random base64 string. @VisibleForTesting public static String fakeUniqueId = null; // Alternately, and better, this callable will be called each time an ID is needed, if non-null. @VisibleForTesting static Callable<String> fakeIdGenerator = null; private XmppUtil() { } /** * Helper method to translate from the XMPP package (1.4 without generics) to * type-safe element lists. */ @SuppressWarnings({"cast", "unchecked"}) public static List<Element> toSafeElementList(List elements) { return (List<Element>) elements; } /** * Checked exception thrown by signer conversion code. */ public static class UnknownSignerType extends Exception { public UnknownSignerType(String algorithm) { super(algorithm); } public UnknownSignerType(String algorithm, Throwable stacked) { super(algorithm, stacked); } } /** * Convert the signer information to XML and place the result within the * passed Element. This method should never fail. */ public static void protocolSignerInfoToXml(ProtocolSignerInfo signerInfo, Element parent) { Element signature = parent.addElement("signature", XmppNamespace.NAMESPACE_WAVE_SERVER); signature.addAttribute("domain", signerInfo.getDomain()); ProtocolSignerInfo.HashAlgorithm hashValue = signerInfo.getHashAlgorithm(); signature.addAttribute("algorithm", hashValue.name()); for (ByteString cert : signerInfo.getCertificateList()) { signature.addElement("certificate").addCDATA(Base64Util.encode(cert)); } } /** * Convert the given Element to a signer information XML element. * * @throws UnknownSignerType when the given hash algorithm is not understood */ public static ProtocolSignerInfo xmlToProtocolSignerInfo(Element signature) throws UnknownSignerType { ProtocolSignerInfo.HashAlgorithm hash; String algorithm = signature.attributeValue("algorithm").toUpperCase(); try { hash = ProtocolSignerInfo.HashAlgorithm.valueOf(algorithm); } catch (IllegalArgumentException e) { throw new UnknownSignerType(algorithm, e); } ProtocolSignerInfo.Builder builder = ProtocolSignerInfo.newBuilder(); builder.setHashAlgorithm(hash); builder.setDomain(signature.attributeValue("domain")); for (Element certElement : toSafeElementList(signature.elements("certificate"))) { builder.addCertificate(Base64Util.decode(certElement.getText())); } return builder.build(); } /** * Convenience method to create a response {@link Message} instance based on * the passed request. Simply returns a new message instance with the same ID, * but with inverse to/from addresses. * * @param request the request message * @return the new response message */ public static Message createResponseMessage(Message request) { Message response = new Message(); response.setID(request.getID()); response.setTo(request.getFrom()); response.setFrom(request.getTo()); return response; } /** * Convenience method to create a response {@link Packet} implementation from * the given source packet. This will return either an {@link IQ} or * {@link Message} depending on the passed type. * * @param request the request message * @return the new response message */ public static Packet createResponsePacket(Packet request) { if (request instanceof Message) { return createResponseMessage((Message) request); } else if (request instanceof IQ) { return IQ.createResultIQ((IQ) request); } else { throw new IllegalArgumentException("Can't respond to unsupported packet type: " + request.getClass()); } } /** * Generate a unique string identifier for use in stanzas. * * @return unique string identifier */ public static String generateUniqueId() { if (fakeIdGenerator != null) { try { return fakeIdGenerator.call(); } catch (Exception e) { // This is used in tests only. throw new RuntimeException(e); } } // TODO(arb): deprecate this. if (fakeUniqueId != null) { return fakeUniqueId; } // Generate a base64 ID based on raw bytes. byte[] bytes = ByteBuffer.allocate(16) .putLong(random.nextLong()).putLong(idSequenceNo.incrementAndGet()).array(); return Base64Util.encode(bytes); } }