package org.glassfish.grizzly.http2;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http2.frames.ContinuationFrame;
import org.glassfish.grizzly.http2.frames.DataFrame;
import org.glassfish.grizzly.http2.frames.GoAwayFrame;
import org.glassfish.grizzly.http2.frames.HeadersFrame;
import org.glassfish.grizzly.http2.frames.Http2Frame;
import org.glassfish.grizzly.http2.frames.PingFrame;
import org.glassfish.grizzly.http2.frames.PriorityFrame;
import org.glassfish.grizzly.http2.frames.PushPromiseFrame;
import org.glassfish.grizzly.http2.frames.RstStreamFrame;
import org.glassfish.grizzly.http2.frames.SettingsFrame;
import org.glassfish.grizzly.http2.frames.UnknownFrame;
import org.glassfish.grizzly.http2.frames.WindowUpdateFrame;
final class NetLogger {
private static final Logger LOGGER = Grizzly.logger(NetLogger.class);
private static final Level LEVEL = Level.FINE;
private static final String CLOSE_FMT = "'{' \"session\":\"{0}\", \"event\":\"SESSION_CLOSE\" '}'";
private static final String DATA = "DATA";
private static final String DATA_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"fin\":\"{3}\", \"len\":\"{4}\" '}'";
private static final String CONTINUATION = "CONTINUATION";
private static final String CONTINUATION_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"len\":\"{3}\" '}'";
private static final String GOAWAY = "GOAWAY";
private static final String GOAWAY_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"last-stream\":\"{3}\", \"error-code\":\"{4}\", \"detail\":\"{5}\" '}'";
private static final String = "HEADERS";
private static final String = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"parent-stream\":\"{3}\", \"prioritized\":\"{4}\", \"exclusive\":\"{5}\", \"weight\":\"{6}\", \"fin\":\"{7}\", \"len\":\"{8}\", \"headers\":{9} '}'";
private static final String OPEN_FMT = "'{' \"session\":\"{0}\", \"event\":\"SESSION_OPEN\" '}'";
private static final String PING = "PING";
private static final String PING_FMT = "'{' session=\"{0}\", event=\"{1}\", is-ack=\"{2}\", opaque-data=\"{3}\" '}'";
private static final String PRIORITY = "PRIORITY";
private static final String PRIORITY_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"parent-stream\":\"{3}\", \"exclusive\":\"{4}\", \"weight\":\"{5}\" '}'";
private static final String PUSH_PROMISE = "PUSH_PROMISE";
private static final String PUSH_PROMISE_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"promised-stream\":\"{3}\", \"len\":\"{4}\", \"headers\":{5} '}'";
private static final String RST = "RST";
private static final String RST_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"stream\":\"{2}\", \"error-code\":\"{3}\" '}'";
private static final String SETTINGS = "SETTINGS";
private static final String SETTINGS_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"settings\":'{'{2}'}' '}'";
private static final String UNKNOWN = "UNKNOWN";
private static final String UNKNOWN_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"frame-type\":\"{2}\", \"len\":\"{3}\" '}'";
private static final String WINDOW_UPDATE = "WINDOW_UPDATE";
private static final String WINDOW_UPDATE_FMT = "'{' \"session\":\"{0}\", \"event\":\"{1}\", \"delta\":\"{2}\" '}'";
private static final String NOT_AVAILABLE = "None Available";
enum Context {
TX("SEND_"), RX("RECV_");
final String prefix;
Context(final String prefix) {
this.prefix = prefix;
}
String getPrefix() {
return prefix;
}
}
static boolean isActive() {
return LOGGER.isLoggable(LEVEL);
}
static void log(final Context ctx, final Http2Session c, final Http2Frame frame) {
switch (frame.getType()) {
case ContinuationFrame.TYPE:
log(ctx, c, (ContinuationFrame) frame);
break;
case DataFrame.TYPE:
log(ctx, c, (DataFrame) frame);
break;
case GoAwayFrame.TYPE:
log(ctx, c, (GoAwayFrame) frame);
break;
case HeadersFrame.TYPE:
break;
case PingFrame.TYPE:
log(ctx, c, (PingFrame) frame);
break;
case PriorityFrame.TYPE:
log(ctx, c, (PriorityFrame) frame);
break;
case PushPromiseFrame.TYPE:
break;
case RstStreamFrame.TYPE:
log(ctx, c, (RstStreamFrame) frame);
break;
case SettingsFrame.TYPE:
log(ctx, c, (SettingsFrame) frame);
break;
case WindowUpdateFrame.TYPE:
log(ctx, c, (WindowUpdateFrame) frame);
break;
default:
log(ctx, c, (UnknownFrame) frame);
}
}
static void log(final Context ctx, final Http2Session c, final ContinuationFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, CONTINUATION_FMT,
new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + CONTINUATION, frame.getStreamId(), frame.getLength() });
}
}
static void log(final Context ctx, final Http2Session c, final DataFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, DATA_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + DATA, frame.getStreamId(), frame.isEndStream(),
frame.getData().remaining() });
}
}
static void log(final Context ctx, final Http2Session c, final GoAwayFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
final Buffer b = frame.getAdditionalDebugData();
final String details = b != null ? b.toStringContent() : NOT_AVAILABLE;
LOGGER.log(LEVEL, GOAWAY_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + GOAWAY, frame.getStreamId(),
frame.getLastStreamId(), frame.getErrorCode().getCode(), escape(details) });
}
}
static void (final Context ctx, final Http2Session c, final HeadersFrame frame, final Map<String, String> headers) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, HEADERS_FMT,
new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + HEADERS, frame.getStreamId(), frame.getStreamDependency(),
frame.isPrioritized(), frame.isExclusive(), frame.getWeight(), frame.isEndStream(), frame.getLength(), toJSON(headers) });
}
}
static void log(final Context ctx, final Http2Session c, final PingFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, PING_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + PING, frame.isAckSet(), frame.getOpaqueData() });
}
}
static void log(final Context ctx, final Http2Session c, final PriorityFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, PRIORITY_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + PRIORITY, frame.getStreamId(),
frame.getStreamDependency(), frame.isExclusive(), frame.getWeight() });
}
}
static void log(final Context ctx, final Http2Session c, final PushPromiseFrame frame, final Map<String, String> headers) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, PUSH_PROMISE_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + PUSH_PROMISE, frame.getStreamId(),
frame.getPromisedStreamId(), frame.getLength(), toJSON(headers) });
}
}
static void log(final Context ctx, final Http2Session c, final RstStreamFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, RST_FMT,
new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + RST, frame.getStreamId(), frame.getErrorCode().getCode() });
}
}
static void log(final Context ctx, final Http2Session c, final SettingsFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
final int numSettings = frame.getNumberOfSettings();
final StringBuilder sb = new StringBuilder();
if (numSettings > 0) {
for (int i = 0; i < numSettings; i++) {
final SettingsFrame.Setting setting = frame.getSettingByIndex(i);
sb.append('"').append(frame.getSettingNameById(setting.getId())).append('"');
sb.append(": ");
sb.append('"').append(setting.getValue()).append('"');
if (i + 1 < numSettings) {
sb.append(", ");
}
}
}
LOGGER.log(LEVEL, SETTINGS_FMT, new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + SETTINGS, sb.toString() });
}
}
static void log(final Context ctx, final Http2Session c, final WindowUpdateFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, WINDOW_UPDATE_FMT,
new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + WINDOW_UPDATE, frame.getWindowSizeIncrement() });
}
}
static void log(final Context ctx, final Http2Session c, final UnknownFrame frame) {
validateParams(ctx, c, frame);
if (isActive()) {
LOGGER.log(LEVEL, UNKNOWN_FMT,
new Object[] { escape(c.getConnection().toString()), ctx.getPrefix() + UNKNOWN, frame.getType(), frame.getLength() });
}
}
static void logClose(final Http2Session c) {
logSessionEvent(CLOSE_FMT, c);
}
static void logOpen(final Http2Session c) {
logSessionEvent(OPEN_FMT, c);
}
private static void logSessionEvent(final String msg, final Http2Session c) {
if (c == null) {
throw new NullPointerException("Http2Session cannot be null");
}
if (isActive()) {
LOGGER.log(LEVEL, msg, new Object[] { escape(c.getConnection().toString()) });
}
}
private static StringBuilder toJSON(final Map<String, String> headers) {
final StringBuilder result = new StringBuilder(64);
result.append("{ ");
for (Iterator<Map.Entry<String, String>> i = headers.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, String> entry = i.next();
result.append('"').append(entry.getKey()).append("\":\"").append(entry.getValue()).append('"');
if (i.hasNext()) {
result.append(", ");
}
}
result.append(" }");
return result;
}
private static void validateParams(final Context ctx, final Http2Session c, final Http2Frame frame) {
if (ctx == null) {
throw new NullPointerException("Context cannot be null.");
}
if (c == null) {
throw new NullPointerException("Http2Session cannot be null.");
}
if (frame == null) {
throw new NullPointerException("Http2Frame cannot be null.");
}
}
private static String escape(final String s) {
final StringBuilder sb = new StringBuilder(s.length() + 20);
for (int i = 0, len = s.length(); i < len; i++) {
final char c = s.charAt(i);
switch (c) {
case '\'':
sb.append("\'");
break;
case '"':
sb.append("\"");
break;
case '\\':
sb.append("\\");
break;
default:
sb.append(c);
}
}
return sb.toString();
}
}