All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.msl.Proxy Maven / Gradle / Ivy

There is a newer version: 1.2226.0
Show newest version
/**
 * Copyright (c) 2015 Netflix, Inc.  All rights reserved.
 */
package com.netflix.msl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import com.netflix.msl.msg.MessageStreamFactory;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action1;

import com.netflix.msl.crypto.ICryptoContext;
import com.netflix.msl.entityauth.EntityAuthenticationData;
import com.netflix.msl.entityauth.EntityAuthenticationFactory;
import com.netflix.msl.msg.ErrorMessageRegistry;
import com.netflix.msl.msg.MessageContext;
import com.netflix.msl.msg.MessageDebugContext;
import com.netflix.msl.msg.MessageInputStream;
import com.netflix.msl.msg.MslControl;
import com.netflix.msl.msg.MslControl.MslChannel;
import com.netflix.msl.msg.ReceiveMessageContext;
import com.netflix.msl.msg.RespondMessageContext;
import com.netflix.msl.tokens.MslUser;
import com.netflix.msl.util.FailoverMslContext;
import com.netflix.msl.util.MslContext;
import com.netflix.msl.util.ProxyMslContext;

/**
 * 

A MSL proxy that first attempts to process messages locally, then * forwards them for external processing if unable to do so, and finally * attempts to accept and respond if unable to communicate with the external * processor.

* *

The {@link #receive(ICryptoContext, ByteBuffer, ByteBuffer, int, MessageDebugContext)} * method is used to process an incoming MSL message. If the message is * processed successfully, the resulting {@link MessageInputStream} will be * returned. If unsuccessful, any data written into the output stream must be * delivered to the remote entity.

* *

The {@link #respond(ICryptoContext, Response, ByteBuffer, MessageInputStream, int, MessageDebugContext)} * method is used to create a response to a previous MSL message. If the * response is generated successfully and the application data will be sent, * {@link Boolean#TRUE} is returned. If the application data will not be sent * due to an inability to satisfy the application's security and message * requirements, {@link Boolean#FALSE} is returned. In all cases any data * written into the output stream must be delivered to the remote entity.

* *

The methods {@link #receiveExternally(ByteBuffer, ByteBuffer)} * {@link #respondExternally(MessageInputStream, Response, ByteBuffer)} * be implemented to perform any external message processing. These methods * will be called when the proxy is unable to process a message locally.

* * @author Wesley Miaw */ public abstract class Proxy { /** * An {@link InputStream} wrapper around a {@link ByteBuffer}. */ private static class ByteBufferInputStream extends InputStream { /** *

Create a new byte buffer input stream with the provided backing * byte buffer.

* * @param buffer backing byte buffer. */ public ByteBufferInputStream(final ByteBuffer buffer) { this.buffer = buffer; } /* (non-Javadoc) * @see java.io.InputStream#read() */ @Override public int read() throws IOException { if (available() == 0) return -1; return buffer.get(); } /* (non-Javadoc) * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] b, int off, int len) throws IOException { final int available = available(); if (available == 0) return -1; final int count = Math.min(len - off, available); buffer.get(b, 0, count); return count; } /* (non-Javadoc) * @see java.io.InputStream#skip(long) */ @Override public long skip(final long n) throws IOException { if (n < 0) return 0; final int count = Math.min((int)n, available()); buffer.position(buffer.position() + count); return count; } /* (non-Javadoc) * @see java.io.InputStream#available() */ @Override public int available() throws IOException { return buffer.remaining(); } /* (non-Javadoc) * @see java.io.InputStream#mark(int) */ @Override public synchronized void mark(final int readlimit) { buffer.mark(); } /* (non-Javadoc) * @see java.io.InputStream#reset() */ @Override public synchronized void reset() throws IOException { buffer.reset(); } /* (non-Javadoc) * @see java.io.InputStream#markSupported() */ @Override public boolean markSupported() { return true; } /** Backing byte buffer. */ private final ByteBuffer buffer; } /** * An {@link OutputStream} wrapper around a {@link ByteBuffer}. */ private static class ByteBufferOutputStream extends OutputStream { /** *

Create a new byte buffer output stream with the provided backing * byte buffer.

* * @param buffer backing byte buffer. */ public ByteBufferOutputStream(final ByteBuffer buffer) { this.buffer = buffer; } /* (non-Javadoc) * @see java.io.OutputStream#write(int) */ @Override public void write(final int b) throws IOException { try { buffer.put((byte)(b & 0xff)); } catch (final BufferOverflowException | ReadOnlyBufferException e) { throw new IOException(e); } } /* (non-Javadoc) * @see java.io.OutputStream#write(byte[], int, int) */ @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (off < 0 || len < 0 || off + len > b.length) throw new IndexOutOfBoundsException(); try { buffer.put(b, off, len); } catch (final BufferOverflowException | ReadOnlyBufferException e) { throw new IOException(e); } } /** Backing byte buffer. */ private final ByteBuffer buffer; } /** *

A response data container.

*/ public static class Response { /** *

Construct a new response data container with the provided * data.

* * @param appdata the application data to include in the response. May * be empty. * @param entityServiceTokens entity-associated service token * name/value pairs. May be empty. * @param userServiceTokens user-associated service token name/value * pairs. May be empty. * @param user a user to attach to the response. May be {@code null}. */ public Response(final byte[] appdata, final Map entityServiceTokens, final Map userServiceTokens, final MslUser user) { this.appdata = appdata; this.entityServiceTokens = Collections.unmodifiableMap(entityServiceTokens); this.userServiceTokens = Collections.unmodifiableMap(userServiceTokens); this.user = user; } /** Application data. */ public byte[] appdata; /** Entity service tokens. */ public Map entityServiceTokens; /** User service tokens. */ public Map userServiceTokens; /** MSL user. May be {@code null}. */ public MslUser user; } /** *

Create a new proxy.

* *

The local entity authentication data and factory will be used to * authenticate the local identity when generating responses.

* *

The MSL token crypto context will be used to verify and decrypt the * MSL tokens of received messages.

* * @param registry MSL error message registry. * @param entityAuthData local entity authentication data. * @param entityAuthFactory local entity authentication factory. * @param mslCryptoContext MSL token crypto context. */ public Proxy(final MessageStreamFactory messageStreamFactory, final ErrorMessageRegistry registry, final EntityAuthenticationData entityAuthData, final EntityAuthenticationFactory entityAuthFactory, final ICryptoContext mslCryptoContext) { mslCtrl = new MslControl(0, messageStreamFactory, registry); proxyMslCtx = new ProxyMslContext(entityAuthData, entityAuthFactory, mslCryptoContext); failoverMslCtx = new FailoverMslContext(entityAuthData, entityAuthFactory, mslCryptoContext); } /** *

This observable receives a request from the remote entity and attempts * to process it in the following order: *

    *
  1. Locally if the message does not require authentication, key exchange, * or other external dependencies.
  2. *
  3. Through the external service being proxied.
  4. *
  5. With failover behaviors if the external service cannot be accessed * and the message does not require authentication to occur.
  6. *
*/ private class ReceiveObservable implements OnSubscribe { /** *

Create a new receive observable.

* *

The input stream must contain the entire request data, which * will be read before any attempt is made to process it.

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input stream. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public ReceiveObservable(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.tokenCryptoContext = tokenCryptoContext; this.in = in; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Receive a request over the provided byte buffer.

* *

If {@link MessageInputStream} is returned then the MSL message * was successfully processed and the MSL header and application data * can be accessed directly.

* *

If {@code null} is returned then the MSL message does not contain * any application data. Any MSL error and handshake responses will * have been written into the provided output stream and must be * delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses * will have been written into the provided output stream and must be * delivered to the remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link IOException} if there is an error reading from the input * or writing to the output stream.
  • *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due * to a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to * a non-MSL reason.
  • *

* * @param observer the event observer. */ @Override public void call(final Subscriber observer) { try { // Mark the byte buffers so their positions can be reset. in.mark(); out.mark(); // First attempt to proxy the request. final Observable local = receiveLocally(tokenCryptoContext, in, out, timeout, dbgCtx); local.subscribe(new Action1() { @Override public void call(final MessageInputStream mis) { observer.onNext(mis); observer.onCompleted(); return; } }, new Action1() { @Override public void call(final Throwable t) { // A MslException indicates external processing is // required. if (t instanceof MslException) { callExternal(observer); return; } observer.onError(t); return; } }); } catch (final Throwable t) { observer.onError(t); return; } } /** * This method has the same behavior as {@link #call(Subscriber)}. */ private void callExternal(final Subscriber observer) { try { // Reset the byte buffers. in.reset(); out.reset(); // Second attempt to process externally. final Observable ext = receiveExternally(in, out); ext.subscribe(new Action1() { @Override public void call(final MessageInputStream mis) { observer.onNext(mis); observer.onCompleted(); return; } }, new Action1() { @Override public void call(final Throwable t) { // If there was a problem communicating with or a // transient failure at the external service, failover // processing is required. if (t instanceof ProxyIoException || t instanceof ProxyTransientException) { callFailover(observer); return; } observer.onError(t); return; } }); } catch (final Throwable t) { observer.onError(t); return; } } /** * This method has the same behavior as {@link #call(Subscriber)}. */ private void callFailover(final Subscriber observer) { try { // Reset the byte buffers. in.reset(); out.reset(); // Third attempt to process in failover mode. final Observable failover = receiveFailover(tokenCryptoContext, in, out, timeout, dbgCtx); failover.subscribe(observer); } catch (final Throwable t) { observer.onError(t); return; } } /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity input stream. */ final ByteBuffer in; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

This observable receives a request from the remote entity and attempts * to process it locally.

*/ private class ReceiveLocallyObservable implements OnSubscribe { /** *

Create a new receive locally observable.

* *

The input stream must contain the entire request data, which * will be read before any attempt is made to process it.

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input stream. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public ReceiveLocallyObservable(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.tokenCryptoContext = tokenCryptoContext; this.in = in; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Receive and locally process a request over the provided byte * buffer.

* *

If {@link MessageInputStream} is returned then the MSL message * was successfully processed and the MSL header and application data * can be accessed directly.

* *

If {@code null} is returned then the MSL message does not contain * any application data. Any MSL error and handshake responses will * have been written into the provided output stream and must be * delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output byte buffer. If external * processing is not indicated then any such data must be delivered to the * remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link IOException} if there is an error reading from the input * or writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due * to a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to * a non-MSL reason.
  • *
  • {@link MslException} if the message must be processed * externally.
  • *

* * @param observer the event observer. */ @Override public void call(Subscriber observer) { // Attempt to proxy the request. try { final MessageContext msgCtx = new ReceiveMessageContext(tokenCryptoContext, dbgCtx); final ByteBufferInputStream bbis = new ByteBufferInputStream(in); final ByteBufferOutputStream bbos = new ByteBufferOutputStream(out); final Future proxyFuture = mslCtrl.receive(proxyMslCtx, msgCtx, bbis, bbos, timeout); final MessageInputStream mis = proxyFuture.get(); observer.onNext(mis); observer.onCompleted(); return; } catch (final InterruptedException e) { observer.onError(e); return; } catch (final ExecutionException e) { // Throw the exception if it is not a MSL exception indicating // external processing. final Throwable cause = e.getCause(); if (!(cause instanceof MslException)) { observer.onError(new ProxyException("Unexpected exception thrown by proxied MslControl.receive().", cause)); return; } final MslException mslCause = (MslException)cause; if (!ProxyMslError.isExternalProcessingRequired(mslCause.getError())) { observer.onError(new ProxyMslException("MSL exception thrown by proxied MslControl.receive().", mslCause)); return; } // External processing is required. Throw the original cause. observer.onError(mslCause); return; } catch (Throwable t) { observer.onError(t); return; } } /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity input stream. */ final ByteBuffer in; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

This observable receives a request from the remote entity and attempts * to process it in failover mode.

*/ private class ReceiveFailoverObservable implements OnSubscribe { /** *

Create a new receive failover observable.

* *

The input stream must contain the entire request data, which * will be read before any attempt is made to process it.

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input stream. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public ReceiveFailoverObservable(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.tokenCryptoContext = tokenCryptoContext; this.in = in; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Receive and locally process in failover mode a request over the * provided byte buffer.

* *

If {@link MessageInputStream} is returned then the MSL message * was successfully processed and the MSL header and application data * can be accessed directly.

* *

If {@code null} is returned then the MSL message does not contain * any application data. Any MSL error and handshake responses will * have been written into the provided output stream and must be * delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses * will have been written into the provided output stream and must be * delivered to the remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link IOException} if there is an error reading from the input * or writing to the output stream.
  • *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due * to a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to * a non-MSL reason.
  • *

* * @param observer the event observer. */ @Override public void call(Subscriber observer) { // Attempt to process the request in failover mode. try { final MessageContext msgCtx = new ReceiveMessageContext(tokenCryptoContext, dbgCtx); final ByteBufferInputStream bbis = new ByteBufferInputStream(in); final ByteBufferOutputStream bbos = new ByteBufferOutputStream(out); final Future failoverFuture = mslCtrl.receive(failoverMslCtx, msgCtx, bbis, bbos, timeout); final MessageInputStream mis = failoverFuture.get(); observer.onNext(mis); observer.onCompleted(); return; } catch (final InterruptedException e) { observer.onError(e); return; } catch (final ExecutionException e) { // Throw the exception. final Throwable cause = e.getCause(); if (!(cause instanceof MslException)) { observer.onError(new ProxyException("Unexpected exception thrown by failover MslControl.receive().", cause)); return; } final MslException mslCause = (MslException)cause; observer.onError(new ProxyMslException("MSL exception thrown by failover MslControl.recieve().", mslCause)); return; } catch (Throwable t) { observer.onError(t); return; } } /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity input stream. */ final ByteBuffer in; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

Process a request over the provided input byte buffer. The byte * buffer must contain the entire request data, which will be read before * any attempt is made to process it.

* *

The returned {@link Observable} will return the received * {@link MessageInputStream} if the MSL message was successfully * processed. The MSL header and application data can then be accessed * directly.

* *

If the {@link Observable} returns {@code null} then the received MSL * message does not have any application data. Any MSL error and handshake * responses will have been written into the provided output stream and * must be delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output stream and must be delivered * to the remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input byte buffer. * @param out remote entity output byte buffer. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the message. */ public Observable receive(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new ReceiveObservable(tokenCryptoContext, in, out, timeout,dbgCtx)); } /** *

Locally process the request contained in the provided input byte * buffer.

* *

The returned {@link Observable} will return the received * {@link MessageInputStream} if the MSL message was successfully * processed. The MSL header and application data can then be accessed * directly.

* *

If the {@link Observable} returns {@code null} then the received MSL * message does not have any application data. Any MSL error and handshake * responses will have been written into the provided output stream and * must be delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output byte buffer. If external * processing is not indicated then any such data must be delivered to the * remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *
  • {@link MslException} if the message must be processed externally.
  • *

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input byte buffer. * @param out remote entity output byte buffer. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the message. */ protected Observable receiveLocally(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new ReceiveLocallyObservable(tokenCryptoContext, in, out, timeout, dbgCtx)); } /** *

Externally process the request contained in the provided input byte * buffer. The byte buffer must contain the entire request data, which will * be read before any attempt is made to process it.

* *

If {@link MessageInputStream} is returned then the MSL message was * successfully processed and the MSL header and application data can be * accessed directly.

* *

If {@code null} was returned then a reply was automatically sent or * the operation was cancelled or interrupted. Any MSL response (success or * error) will have been written into the provided output stream and must * be delivered to the remote entity. Reasons for a {@code null} return * value include:

*
    *
  • Automatic response to a handshake request.
  • *
  • An error response must be sent.
  • *

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output byte buffer. If failover * processing is not indicated then any such data must be delivered to the * remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link ProxyIoException} if there is an error communicating with the * external service. This indicates the message should be processed * in failover mode.
  • *
  • {@link ProxyTransientException} if there is a transient problem with * the external service. This indicates the message should be processed * in failover mode.
  • *
  • {@link ProxyMslException} if the external service throws a MSL * exception.
  • *
  • {@link ProxyException} if the message cannot be processed by the * external service for a non-MSL reason.
  • *

* *

This function must not call {@link ByteBuffer#mark()} on either the * input or output byte buffers.

* * @param in remote entity input byte buffer. * @param out remote entity output byte buffer. * @return an {@link Observable} for the message. */ protected abstract Observable receiveExternally(final ByteBuffer in, final ByteBuffer out); /** *

Locally process the request contained in the provided input byte * buffer in failover mode.

* *

The returned {@link Observable} will return the received * {@link MessageInputStream} if the MSL message was successfully * processed. The MSL header and application data can then be accessed * directly.

* *

If the {@link Observable} returns {@code null} then the received MSL * message does not have any application data. Any MSL error and handshake * responses will have been written into the provided output stream and * must be delivered to the remote entity.

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output stream and must be delivered * to the remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *

* * @param tokenCryptoContext service token crypto context. * @param in remote entity input byte buffer. * @param out remote entity output byte buffer. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the message. */ protected Observable receiveFailover(final ICryptoContext tokenCryptoContext, final ByteBuffer in, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new ReceiveFailoverObservable(tokenCryptoContext, in, out, timeout, dbgCtx)); } /** *

This observable sends a response to the remote entity and attempts to * generate the response in the following order: *

    *
  1. Locally if the message does not require token renewal, key exchange, * or other external dependencies.
  2. *
  3. Through the external service being proxied.
  4. *
  5. With failover behaviors if the external service cannot be * accessed.
  6. *
*/ private class RespondObservable implements OnSubscribe { /** *

Create a new respond observable.

* *

The provided response data will be used to generate the * response.

* * @param request original request to respond to. * @param responseData application response data. * @param tokenCryptoContext service token crypto context. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public RespondObservable(final MessageInputStream request, final Response responseData, final ICryptoContext tokenCryptoContext, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.request = request; this.responseData = responseData; this.tokenCryptoContext = tokenCryptoContext; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Send a response over the provided byte buffer.

* *

If {@link Boolean#TRUE} was returned then the response was * successfully created and written into the provided output stream and * must be delivered to the remote entity.

* *

If {@link Boolean#FALSE} was returned then the provided response * data could not be sent and a MSL error response will have been * written into the provided output stream and must be delivered to the * remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error response will have been * written into the provided output stream and must be delivered to the * remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link InterruptedException} if the thread was interrupted while processing the message.
  • *
  • {@link IOException} if there is an error writing to the output stream.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a non-MSL reason.
  • *

* * @param observer the event observer. */ @Override public void call(final Subscriber observer) { try { // Mark the byte buffer so its position can be reset. out.mark(); // First attempt to proxy the operation. final Observable local = respondLocally(tokenCryptoContext, responseData, out, request, timeout, dbgCtx); local.subscribe(new Action1() { @Override public void call(final Boolean b) { observer.onNext(b); observer.onCompleted(); return; } }, new Action1() { @Override public void call(final Throwable t) { // A MslException indicates external processing is // required. if (t instanceof MslException) { callExternal(observer); return; } observer.onError(t); return; } }); } catch (final Throwable t) { observer.onError(t); return; } } /** * This method has the same behavior as {@link #call(Subscriber)}. */ private void callExternal(final Subscriber observer) { try { // Reset the byte buffer. out.reset(); // Second attempt to process externally. final Observable ext = respondExternally(request, responseData, out); ext.subscribe(new Action1() { @Override public void call(final Boolean b) { observer.onNext(b); observer.onCompleted(); return; } }, new Action1() { @Override public void call(final Throwable t) { // If there was a problem communicating with or a // transient failure at the external service, failover // processing is required. if (t instanceof ProxyIoException || t instanceof ProxyTransientException) { callFailover(observer); return; } observer.onError(t); return; } }); } catch (final Throwable t) { observer.onError(t); return; } } /** * This method has the same behavior as {@link #call(Subscriber)}. */ private void callFailover(final Subscriber observer) { try { // Reset the byte buffer. out.reset(); // Third attempt to process in failover mode. final Observable failover = respondFailover(tokenCryptoContext, responseData, out, request, timeout, dbgCtx); failover.subscribe(observer); } catch (final Throwable t) { observer.onError(t); return; } } /** Original request. */ private final MessageInputStream request; /** Response data. */ final Response responseData; /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

This observable sends a response to the remote entity and attempts to * generate the response locally.

*/ private class RespondLocallyObservable implements OnSubscribe { /** *

Create a new respond locally observable.

* *

The provided response data will be used to generate the * response.

* * @param request original request to respond to. * @param responseData application response data. * @param tokenCryptoContext service token crypto context. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public RespondLocallyObservable(final MessageInputStream request, final Response responseData, final ICryptoContext tokenCryptoContext, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.request = request; this.responseData = responseData; this.tokenCryptoContext = tokenCryptoContext; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Locally send a response over the provided output stream. The * provided response data will be used to generate the response.

* *

If {@link Boolean#TRUE} is returned the response was successfully * created and written into the provided output stream. The data in the * output stream must be delivered to the remote entity.

* *

If {@link Boolean#FALSE} is returned then the provided response * data could not be sent and a MSL error response will have been * written into the provided output stream and must be delivered to the * remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error and handshake responses * will have been written into the provided output byte buffer. If * external processing is not indicated then any such data must be * delivered to the remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *
  • {@link MslException} if the message must be processed externally.
  • *

* * @param observer the event observer. */ @Override public void call(Subscriber observer) { // Attempt to proxy the operation. try { final MessageContext msgCtx = new RespondMessageContext(responseData.appdata, responseData.entityServiceTokens, responseData.userServiceTokens, tokenCryptoContext, responseData.user, dbgCtx); final ByteArrayInputStream nullInput = new ByteArrayInputStream(new byte[0]); final ByteBufferOutputStream bbos = new ByteBufferOutputStream(out); final Future proxyFuture = mslCtrl.respond(proxyMslCtx, msgCtx, nullInput, bbos, request, timeout); final MslChannel channel = proxyFuture.get(); observer.onNext(channel != null && channel.output != null); observer.onCompleted(); return; } catch (final InterruptedException e) { observer.onError(e); return; } catch (final ExecutionException e) { // Throw the exception if it is not a MSL exception indicating // external processing. final Throwable cause = e.getCause(); if (!(cause instanceof MslException)) { observer.onError(new ProxyException("Unexpected exception thrown by proxied MslControl.respond().", cause)); return; } final MslException mslCause = (MslException)cause; if (!ProxyMslError.isExternalProcessingRequired(mslCause.getError())) { observer.onError(new ProxyMslException("MSL exception thrown by proxied MslControl.respond().", mslCause)); return; } // External processing is required. Throw the original cause. observer.onError(mslCause); return; } catch (final Throwable t) { observer.onError(t); return; } } /** Original request. */ private final MessageInputStream request; /** Response data. */ final Response responseData; /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

This observable sends a response to the remote entity and attempts to * generate the response in failover mode.

*/ private class RespondFailoverObservable implements OnSubscribe { /** *

Create a new respond failover observable.

* *

The provided response data will be used to generate the * response.

* * @param request original request to respond to. * @param responseData application response data. * @param tokenCryptoContext service token crypto context. * @param out remote entity output stream. * @param timeout renewal lock acquisition timeout. * @param dbgCtx message debug context. */ public RespondFailoverObservable(final MessageInputStream request, final Response responseData, final ICryptoContext tokenCryptoContext, final ByteBuffer out, final int timeout, final MessageDebugContext dbgCtx) { this.request = request; this.responseData = responseData; this.tokenCryptoContext = tokenCryptoContext; this.out = out; this.timeout = timeout; this.dbgCtx = dbgCtx; } /** *

Locally send a response over the provided input stream in * failover mode. The provided response data will be used to generate * the response.

* *

If {@link Boolean#TRUE} is returned the response was successfully * created and written into the provided output stream. The data in the * output stream must be delivered to the remote entity.

* *

If {@link Boolean#FALSE} is returned then the provided response * data could not be sent and a MSL error response will have been * written into the provided output stream and must be delivered to the * remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output stream and must be delivered * to the remote entity.

* *

The following checked exceptions may be thrown: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *

* * @param observer the event observer. */ @Override public void call(Subscriber observer) { // Attempt to process the operation in failover mode. try { final MessageContext msgCtx = new RespondMessageContext(responseData.appdata, responseData.entityServiceTokens, responseData.userServiceTokens, tokenCryptoContext, responseData.user, dbgCtx); final ByteArrayInputStream nullInput = new ByteArrayInputStream(new byte[0]); final ByteBufferOutputStream bbos = new ByteBufferOutputStream(out); final Future failoverFuture = mslCtrl.respond(failoverMslCtx, msgCtx, nullInput, bbos, request, timeout); final MslChannel channel = failoverFuture.get(); observer.onNext(channel != null && channel.output != null); return; } catch (final InterruptedException e) { observer.onError(e); return; } catch (final ExecutionException e) { // Throw the exception. final Throwable cause = e.getCause(); if (!(cause instanceof MslException)) { observer.onError(new ProxyException("Unexpected exception thrown by failover MslControl.respond().", cause)); return; } final MslException mslCause = (MslException)cause; observer.onError(new ProxyMslException("MSL exception thrown by failover MslControl.respond().", mslCause)); return; } catch (final Throwable t) { observer.onError(t); return; } } /** Original request. */ private final MessageInputStream request; /** Response data. */ final Response responseData; /** Service token crypto context. */ final ICryptoContext tokenCryptoContext; /** Remote entity output stream. */ final ByteBuffer out; /** Renewal lock acquisition timeout in milliseconds. */ final int timeout; /** Message debug context. */ final MessageDebugContext dbgCtx; } /** *

Send a response over the provided output stream. The provided * response data will be used to generate the response.

* *

The returned {@link Observable} will return {@link Boolean#TRUE} the * response was successfully created and written into the provided output * stream. The data in the output stream must be delivered to the remote * entity.

* *

If the {@link Observable} returns {@link Boolean#FALSE} then the * provided response data could not be sent and a MSL error response will * have been written into the provided output stream and must be delivered * to the remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error response will have been * written into the provided output stream and must be delivered to the * remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *

* * @param tokenCryptoContext service token crypto context. * @param responseData application response data. * @param out remote entity output stream. * @param request original request to respond to. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the response operation. */ public Observable respond(final ICryptoContext tokenCryptoContext, final Response responseData, final ByteBuffer out, final MessageInputStream request, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new RespondObservable(request, responseData, tokenCryptoContext, out, timeout, dbgCtx)); } /** *

Locally send a response over the provided output stream. The provided * response data will be used to generate the response.

* *

The returned {@link Observable} will return {@link Boolean#TRUE} the * response was successfully created and written into the provided output * stream. The data in the output stream must be delivered to the remote * entity.

* *

If the {@link Observable} returns {@link Boolean#FALSE} then the * provided response data could not be sent and a MSL error response will * have been written into the provided output stream and must be delivered * to the remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output byte buffer. If external * processing is not indicated then any such data must be delivered to the * remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *
  • {@link MslException} if the message must be processed externally.
  • *

* * @param tokenCryptoContext service token crypto context. * @param responseData application response data. * @param out remote entity output stream. * @param request original request to respond to. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the response operation. */ protected Observable respondLocally(final ICryptoContext tokenCryptoContext, final Response responseData, final ByteBuffer out, final MessageInputStream request, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new RespondLocallyObservable(request, responseData, tokenCryptoContext, out, timeout, dbgCtx)); } /** *

Externally send a response over the provided input stream. The * provided response data will be used to generate the response.

* *

The returned {@link Observable} will return {@link Boolean#TRUE} the * response was successfully created and written into the provided output * stream. The data in the output stream must be delivered to the remote * entity.

* *

If the {@link Observable} returns {@link Boolean#FALSE} then the * provided response data could not be sent and a MSL error response will * have been written into the provided output stream and must be delivered * to the remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output byte buffer. If failover * processing is not indicated then any such data must be delivered to the * remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link ProxyIoException} if there is an error communicating with the * external service. This indicates the message should be processed * in failover mode.
  • *
  • {@link ProxyTransientException} if there is a transient problem with * the external service. This indicates the message should be processed * in failover mode.
  • *
  • {@link ProxyMslException} if the external service throws a MSL * exception.
  • *
  • {@link ProxyException} if the message cannot be processed by the * external service for a non-MSL reason.
  • *

* *

This function must not call {@link ByteBuffer#mark()} on either the * input or output byte buffers.

* * @param request original request to respond to. * @param responseData application response data. * @param out remote entity output byte buffer. * @return an {@link Observable} for the response operation. */ protected abstract Observable respondExternally(final MessageInputStream request, final Response responseData, final ByteBuffer out); /** *

Locally send a response over the provided input stream in failover * mode. The provided response data will be used to generate the * response.

* *

The returned {@link Observable} will return {@link Boolean#TRUE} the * response was successfully created and written into the provided output * stream. The data in the output stream must be delivered to the remote * entity.

* *

If the {@link Observable} returns {@link Boolean#FALSE} then the * provided response data could not be sent and a MSL error response will * have been written into the provided output stream and must be delivered * to the remote entity. Reasons for an error response include: *

    *
  • The response could not be sent with encryption or integrity * protection when it is required.
  • *
  • A user cannot be attached to the response due to the lack of a * master token.
  • *

* *

If an exception is thrown any MSL error and handshake responses will * have been written into the provided output stream and must be delivered * to the remote entity.

* *

The {@link Observable} may throw any of the following exceptions: *

    *
  • {@link IOException} if there is an error reading from the input or * writing to the output stream.
  • *
  • {@link CancellationException} if the operation was cancelled. *
  • {@link InterruptedException} if the thread was interrupted while * processing the message.
  • *
  • {@link ProxyMslException} if the message cannot be processed due to * a MSL exception.
  • *
  • {@link ProxyException} if the message cannot be processed due to a * non-MSL reason.
  • *

* * @param tokenCryptoContext service token crypto context. * @param responseData application response data. * @param out remote entity output stream. * @param request original request to respond to. * @param timeout MSL renewal lock acquisition timeout in milliseconds. * @param dbgCtx message debug context. * @return an {@link Observable} for the response operation. */ protected Observable respondFailover(final ICryptoContext tokenCryptoContext, final Response responseData, final ByteBuffer out, final MessageInputStream request, final int timeout, final MessageDebugContext dbgCtx) { return Observable.create(new RespondFailoverObservable(request, responseData, tokenCryptoContext, out, timeout, dbgCtx)); } /** MSL control. */ private final MslControl mslCtrl; /** Proxy MSL context. */ private final MslContext proxyMslCtx; /** Failover MSL context. */ private final MslContext failoverMslCtx; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy