/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.sun.jini.discovery.internal;
import com.sun.jini.discovery.DatagramBufferFactory;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.MulticastAnnouncement;
import com.sun.jini.discovery.MulticastRequest;
import com.sun.jini.discovery.UnicastResponse;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CoderResult;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMaxPrincipal;
import net.jini.core.constraint.ClientMaxPrincipalType;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.ClientMinPrincipalType;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.DelegationAbsoluteTime;
import net.jini.core.constraint.DelegationRelativeTime;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.io.MarshalledInstance;
import net.jini.io.UnsupportedConstraintException;
/**
* Provides utility methods for plaintext data operations.
*/
public class Plaintext {
private static final int MAX_USHORT = 0xFFFF;
private static final int SHORT_LEN = 2;
private static final int SERVICE_ID_LEN = 16;
private static final Charset utf = Charset.forName("UTF-8");
private static final Set supportedConstraints = new HashSet();
static {
supportedConstraints.add(Integrity.NO);
supportedConstraints.add(Confidentiality.NO);
supportedConstraints.add(ClientAuthentication.NO);
supportedConstraints.add(ServerAuthentication.NO);
supportedConstraints.add(Delegation.NO);
supportedConstraints.add(ClientMaxPrincipal.class);
supportedConstraints.add(ClientMaxPrincipalType.class);
supportedConstraints.add(ClientMinPrincipal.class);
supportedConstraints.add(ClientMinPrincipalType.class);
supportedConstraints.add(ServerMinPrincipal.class);
supportedConstraints.add(DelegationAbsoluteTime.class);
supportedConstraints.add(DelegationRelativeTime.class);
}
/* Uninstantiable */
private Plaintext() {
}
/**
* Returns the given integer value as an unsigned short, throwing an
* IllegalArgumentException if the value is negative or too large.
*/
public static short intToUshort(int i) {
if (i < 0 || i > MAX_USHORT) {
throw new IllegalArgumentException("invalid value: " + i);
}
return (short) i;
}
/**
* Returns an integer with the unsigned value of the given short.
*/
public static int ushortToInt(short s) {
return s & MAX_USHORT;
}
/**
* Returns a byte array containing the UTF encoding of the given string.
*/
public static byte[] toUtf(String s) throws UTFDataFormatException {
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream(s.length());
DataOutput dout = new DataOutputStream(bout);
dout.writeUTF(s);
return bout.toByteArray();
} catch (UTFDataFormatException e) {
throw e;
} catch (IOException e) {
throw new AssertionError(e);
}
}
/**
* Writes the given string to the provided buffer in UTF format, starting
* at the buffer's current position and not exceeding its limit. If the
* encoding of the given string exceeds the buffer's limit, then a
* BufferOverflowException is thrown and the position of the buffer is left
* unchanged from its initial value.
*/
public static void putUtf(ByteBuffer buf, String s)
throws UTFDataFormatException
{
ByteBuffer dup = buf.duplicate();
dup.putShort((short) 0);
int start = dup.position();
CoderResult cr =
utf.newEncoder().encode(CharBuffer.wrap(s), dup, true);
if (cr.isUnderflow()) {
buf.putShort(intToUshort(dup.position() - start));
buf.position(dup.position());
} else if (cr.isOverflow()) {
throw new BufferOverflowException();
} else {
throw new UTFDataFormatException(cr.toString());
}
}
/**
* Returns string read from the given buffer in UTF format , starting at
* the buffer's current position and not exceeding its limit. If the
* string's encoding extends past the buffer's limit, then a
* BufferUnderflowException is thrown and the position of the buffer is
* left unchanged from its initial value.
*/
public static String getUtf(ByteBuffer buf) throws UTFDataFormatException {
ByteBuffer dup = buf.duplicate();
int len = ushortToInt(dup.getShort());
if (len > dup.remaining()) {
throw new BufferUnderflowException();
}
dup.limit(dup.position() + len);
try {
String s = utf.newDecoder().decode(dup).toString();
buf.position(dup.position());
return s;
} catch (CharacterCodingException e) {
throw (UTFDataFormatException)
new UTFDataFormatException().initCause(e);
}
}
/**
* Returns normally if the given constraints can be satisfied by a
* plaintext-based format/protocol (such as net.jini.discovery.plaintext,
* or version 1 of the discovery protocols); otherwise, throws an
* UnsupportedConstraintException . Null constraints are considered
* equivalent to empty constraints.
*/
public static void checkConstraints(InvocationConstraints constraints)
throws UnsupportedConstraintException
{
if (constraints == null) {
return;
}
for (Iterator i = constraints.requirements().iterator(); i.hasNext(); )
{
InvocationConstraint c = (InvocationConstraint) i.next();
if (!supported(c)) {
throw new UnsupportedConstraintException(
"unsupported constraint: " + c);
}
}
}
/**
* Encodes multicast request according to the net.jini.discovery.plaintext
* format.
*/
public static void encodeMulticastRequest(MulticastRequest request,
DatagramBufferFactory bufs)
throws IOException
{
try {
LinkedList groups = new LinkedList();
groups.addAll(Arrays.asList(request.getGroups()));
do {
ByteBuffer buf = bufs.newBuffer();
// write client host
putUtf(buf, request.getHost());
// write client port
buf.putShort(intToUshort(request.getPort()));
// write first lookup group
int ngroups = 0;
int ngroupsPos = buf.position();
buf.putShort((short) 0);
if (!groups.isEmpty()) {
putUtf(buf, (String) groups.removeFirst());
ngroups++;
}
// calculate size of known service ID list
ServiceID[] ids = request.getServiceIDs();
int maxIds = Math.min(ids.length, MAX_USHORT);
int maxIdsLen = SHORT_LEN + maxIds * SERVICE_ID_LEN;
// write additional lookup groups, as space allows
if (buf.remaining() > maxIdsLen && !groups.isEmpty()) {
int slim = buf.limit();
buf.limit(slim - maxIdsLen);
try {
do {
putUtf(buf, (String) groups.getFirst());
groups.removeFirst();
ngroups++;
} while (!groups.isEmpty() && ngroups < MAX_USHORT);
} catch (BufferOverflowException e) {
}
buf.limit(slim);
}
buf.putShort(ngroupsPos, intToUshort(ngroups));
// write known service IDs
int nids = Math.min(
maxIds,
(buf.remaining() - SHORT_LEN) / SERVICE_ID_LEN);
buf.putShort(intToUshort(nids));
for (int i = 0; i < nids; i++) {
ServiceID id = ids[i];
buf.putLong(id.getMostSignificantBits());
buf.putLong(id.getLeastSignificantBits());
}
} while (!groups.isEmpty());
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
/**
* Decodes multicast request according to the net.jini.discovery.plaintext
* format.
*/
public static MulticastRequest decodeMulticastRequest(ByteBuffer buf)
throws IOException
{
try {
// read client host
String host = getUtf(buf);
// read client port
int port = ushortToInt(buf.getShort());
// read lookup groups
String[] groups = new String[ushortToInt(buf.getShort())];
for (int i = 0; i < groups.length; i++) {
groups[i] = getUtf(buf);
}
// read known service IDs
ServiceID[] ids = new ServiceID[ushortToInt(buf.getShort())];
for (int i = 0; i < ids.length; i++) {
long hi = buf.getLong();
long lo = buf.getLong();
ids[i] = new ServiceID(hi, lo);
}
return new MulticastRequest(host, port, groups, ids);
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
/**
* Encodes multicast announcement according to the
* net.jini.discovery.plaintext format.
*/
public static void encodeMulticastAnnouncement(
MulticastAnnouncement announcement,
DatagramBufferFactory bufs)
throws IOException
{
try {
LinkedList groups = new LinkedList();
groups.addAll(Arrays.asList(announcement.getGroups()));
do {
ByteBuffer buf = bufs.newBuffer();
int slim = buf.limit();
buf.limit(slim - SERVICE_ID_LEN);
// write sequence number
buf.putLong(announcement.getSequenceNumber());
// write LUS host
putUtf(buf, announcement.getHost());
// write LUS port
buf.putShort(intToUshort(announcement.getPort()));
// write LUS member groups, as space allows
int ngroups = 0;
int ngroupsPos = buf.position();
buf.putShort((short) 0);
try {
while (!groups.isEmpty() && ngroups < MAX_USHORT) {
putUtf(buf, (String) groups.getFirst());
groups.removeFirst();
ngroups++;
}
} catch (BufferOverflowException e) {
if (ngroups == 0) {
throw e;
}
}
buf.putShort(ngroupsPos, intToUshort(ngroups));
// write LUS service ID
ServiceID id = announcement.getServiceID();
buf.limit(slim);
buf.putLong(id.getMostSignificantBits());
buf.putLong(id.getLeastSignificantBits());
} while (!groups.isEmpty());
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
/**
* Decodes multicast announcement according to the
* net.jini.discovery.plaintext format.
*/
public static MulticastAnnouncement decodeMulticastAnnouncement(
ByteBuffer buf)
throws IOException
{
try {
// read sequence number
long seq = buf.getLong();
// read LUS host
String host = getUtf(buf);
// read LUS port
int port = ushortToInt(buf.getShort());
// read LUS member groups
String[] groups = new String[ushortToInt(buf.getShort())];
for (int i = 0; i < groups.length; i++) {
groups[i] = getUtf(buf);
}
// read LUS service ID
long idhi = buf.getLong();
long idlo = buf.getLong();
return new MulticastAnnouncement(
seq, host, port, groups, new ServiceID(idhi, idlo));
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
/**
* Writes unicast response according to the net.jini.discovery.plaintext
* format.
*/
public static void writeUnicastResponse(OutputStream out,
UnicastResponse response,
Collection context)
throws IOException
{
try {
DataOutput dout = new DataOutputStream(out);
// write LUS host
dout.writeUTF(response.getHost());
// write LUS port
dout.writeShort(intToUshort(response.getPort()));
// write LUS member groups
String[] groups = response.getGroups();
dout.writeInt(groups.length);
for (int i = 0; i < groups.length; i++) {
dout.writeUTF(groups[i]);
}
// write LUS proxy
new ObjectOutputStream(out).writeObject(
new MarshalledInstance(response.getRegistrar(), context));
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
/**
* Reads unicast response according to the net.jini.discovery.plaintext
* format.
*/
public static UnicastResponse readUnicastResponse(
InputStream in,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader,
Collection context)
throws IOException, ClassNotFoundException
{
try {
DataInput din = new DataInputStream(in);
// read LUS host
String host = din.readUTF();
// read LUS port
int port = din.readUnsignedShort();
// read LUS member groups
String[] groups = new String[din.readInt()];
for (int i = 0; i < groups.length; i++) {
groups[i] = din.readUTF();
}
// read LUS proxy
MarshalledInstance mi =
(MarshalledInstance) new ObjectInputStream(in).readObject();
ServiceRegistrar reg = (ServiceRegistrar) mi.get(
defaultLoader,
verifyCodebaseIntegrity,
verifierLoader,
context);
return new UnicastResponse(host, port, groups, reg);
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
private static boolean supported(InvocationConstraint ic) {
if (ic instanceof ConstraintAlternatives) {
ConstraintAlternatives ca = (ConstraintAlternatives) ic;
for (Iterator i = ca.elements().iterator(); i.hasNext(); ) {
if (supported((InvocationConstraint) i.next())) {
return true;
}
}
return false;
}
return supportedConstraints.contains(ic) ||
supportedConstraints.contains(ic.getClass());
}
}