Basic implementation of CREDENTIAL frame, parser and generator.

This commit is contained in:
Simone Bordet 2012-06-01 15:23:22 +02:00
parent 18141b68a3
commit 395d49ba71
8 changed files with 474 additions and 2 deletions

View File

@ -20,7 +20,6 @@ import org.eclipse.jetty.spdy.api.SessionStatus;
public class SessionException extends RuntimeException
{
private final SessionStatus sessionStatus;
public SessionException(SessionStatus sessionStatus)

View File

@ -29,7 +29,8 @@ public enum ControlFrameType
PING((short)6),
GO_AWAY((short)7),
HEADERS((short)8),
WINDOW_UPDATE((short)9);
WINDOW_UPDATE((short)9),
CREDENTIAL((short)10);
public static ControlFrameType from(short code)
{

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2012 the original author or authors.
*
* 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.eclipse.jetty.spdy.frames;
import java.security.cert.Certificate;
public class CredentialFrame extends ControlFrame
{
private final short slot;
private final byte[] proof;
private final Certificate[] certificateChain;
public CredentialFrame(short version, short slot, byte[] proof, Certificate[] certificateChain)
{
super(version, ControlFrameType.CREDENTIAL, (byte)0);
this.slot = slot;
this.proof = proof;
this.certificateChain = certificateChain;
}
public short getSlot()
{
return slot;
}
public byte[] getProof()
{
return proof;
}
public Certificate[] getCertificateChain()
{
return certificateChain;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2012 the original author or authors.
*
* 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.eclipse.jetty.spdy.generator;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.spdy.ByteBufferPool;
import org.eclipse.jetty.spdy.frames.ControlFrame;
import org.eclipse.jetty.spdy.frames.CredentialFrame;
public class CredentialGenerator extends ControlFrameGenerator
{
public CredentialGenerator(ByteBufferPool bufferPool)
{
super(bufferPool);
}
@Override
public ByteBuffer generate(ControlFrame frame)
{
CredentialFrame credential = (CredentialFrame)frame;
byte[] proof = credential.getProof();
List<byte[]> certificates = serializeCertificates(credential.getCertificateChain());
int certificatesLength = 0;
for (byte[] certificate : certificates)
certificatesLength += certificate.length;
int frameBodyLength = 2 + 4 + proof.length + certificates.size() * 4 + certificatesLength;
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
generateControlFrameHeader(credential, frameBodyLength, buffer);
buffer.putShort(credential.getSlot());
buffer.putInt(proof.length);
buffer.put(proof);
for (byte[] certificate : certificates)
{
buffer.putInt(certificate.length);
buffer.put(certificate);
}
buffer.flip();
return buffer;
}
private List<byte[]> serializeCertificates(Certificate[] certificates)
{
// TODO
return new ArrayList<>();
}
}

View File

@ -42,6 +42,7 @@ public class Generator
generators.put(ControlFrameType.GO_AWAY, new GoAwayGenerator(bufferPool));
generators.put(ControlFrameType.HEADERS, new HeadersGenerator(bufferPool, headersBlockGenerator));
generators.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateGenerator(bufferPool));
generators.put(ControlFrameType.CREDENTIAL, new CredentialGenerator(bufferPool));
dataFrameGenerator = new DataFrameGenerator(bufferPool);
}

View File

@ -46,6 +46,7 @@ public abstract class ControlFrameParser
parsers.put(ControlFrameType.GO_AWAY, new GoAwayBodyParser(this));
parsers.put(ControlFrameType.HEADERS, new HeadersBodyParser(decompressor, this));
parsers.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateBodyParser(this));
parsers.put(ControlFrameType.CREDENTIAL, new CredentialBodyParser(this));
}
public short getVersion()

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) 2012 the original author or authors.
*
* 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.eclipse.jetty.spdy.parser;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jetty.spdy.SessionException;
import org.eclipse.jetty.spdy.api.SessionStatus;
import org.eclipse.jetty.spdy.frames.ControlFrameType;
import org.eclipse.jetty.spdy.frames.CredentialFrame;
public class CredentialBodyParser extends ControlFrameBodyParser
{
private final List<Certificate> certificates = new ArrayList<>();
private final ControlFrameParser controlFrameParser;
private State state = State.SLOT;
private int totalLength;
private int cursor;
private short slot;
private int proofLength;
private byte[] proof;
private int certificateLength;
private byte[] certificate;
public CredentialBodyParser(ControlFrameParser controlFrameParser)
{
this.controlFrameParser = controlFrameParser;
}
@Override
public boolean parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
{
switch (state)
{
case SLOT:
{
if (buffer.remaining() >= 2)
{
slot = buffer.getShort();
checkSlotValid();
state = State.PROOF_LENGTH;
}
else
{
state = State.SLOT_BYTES;
cursor = 2;
}
break;
}
case SLOT_BYTES:
{
byte currByte = buffer.get();
--cursor;
slot += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
checkSlotValid();
state = State.PROOF_LENGTH;
}
break;
}
case PROOF_LENGTH:
{
if (buffer.remaining() >= 4)
{
proofLength = buffer.getInt() & 0x7F_FF_FF_FF;
state = State.PROOF;
}
else
{
state = State.PROOF_LENGTH_BYTES;
cursor = 4;
}
break;
}
case PROOF_LENGTH_BYTES:
{
byte currByte = buffer.get();
--cursor;
proofLength += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
proofLength &= 0x7F_FF_FF_FF;
state = State.PROOF;
}
break;
}
case PROOF:
{
totalLength = controlFrameParser.getLength() - 2 - 4 - proofLength;
proof = new byte[proofLength];
if (buffer.remaining() >= proofLength)
{
buffer.get(proof);
state = State.CERTIFICATE_LENGTH;
if (totalLength == 0)
{
onCredential();
return true;
}
}
else
{
state = State.PROOF_BYTES;
cursor = proofLength;
}
break;
}
case PROOF_BYTES:
{
proof[proofLength - cursor] = buffer.get();
--cursor;
if (cursor == 0)
{
state = State.CERTIFICATE_LENGTH;
if (totalLength == 0)
{
onCredential();
return true;
}
}
break;
}
case CERTIFICATE_LENGTH:
{
if (buffer.remaining() >= 4)
{
certificateLength = buffer.getInt() & 0x7F_FF_FF_FF;
state = State.CERTIFICATE;
}
else
{
state = State.CERTIFICATE_LENGTH_BYTES;
cursor = 4;
}
break;
}
case CERTIFICATE_LENGTH_BYTES:
{
byte currByte = buffer.get();
--cursor;
certificateLength += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
certificateLength &= 0x7F_FF_FF_FF;
state = State.CERTIFICATE;
}
break;
}
case CERTIFICATE:
{
totalLength -= 4 + certificateLength;
certificate = new byte[certificateLength];
if (buffer.remaining() >= certificateLength)
{
buffer.get(certificate);
if (onCertificate())
return true;
}
else
{
state = State.CERTIFICATE_BYTES;
cursor = certificateLength;
}
break;
}
case CERTIFICATE_BYTES:
{
certificate[certificateLength - cursor] = buffer.get();
--cursor;
if (cursor == 0)
{
if (onCertificate())
return true;
}
break;
}
default:
{
throw new IllegalStateException();
}
}
}
return false;
}
private void checkSlotValid()
{
if (slot <= 0)
throw new SessionException(SessionStatus.PROTOCOL_ERROR,
"Invalid slot " + slot + " for " + ControlFrameType.CREDENTIAL + " frame");
}
private boolean onCertificate()
{
certificates.add(deserializeCertificate(certificate));
if (totalLength == 0)
{
onCredential();
return true;
}
else
{
certificateLength = 0;
state = State.CERTIFICATE_LENGTH;
}
return false;
}
private Certificate deserializeCertificate(byte[] bytes)
{
// TODO
return null;
}
private void onCredential()
{
CredentialFrame frame = new CredentialFrame(controlFrameParser.getVersion(), slot,
Arrays.copyOf(proof, proof.length), certificates.toArray(new Certificate[certificates.size()]));
controlFrameParser.onControlFrame(frame);
reset();
}
private void reset()
{
state = State.SLOT;
totalLength = 0;
cursor = 0;
slot = 0;
proofLength = 0;
proof = null;
certificateLength = 0;
certificate = null;
certificates.clear();
}
public enum State
{
SLOT, SLOT_BYTES, PROOF_LENGTH, PROOF_LENGTH_BYTES, PROOF, PROOF_BYTES,
CERTIFICATE_LENGTH, CERTIFICATE_LENGTH_BYTES, CERTIFICATE, CERTIFICATE_BYTES
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2012 the original author or authors.
*
* 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.eclipse.jetty.spdy.frames;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import org.eclipse.jetty.spdy.StandardByteBufferPool;
import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.generator.Generator;
import org.eclipse.jetty.spdy.parser.Parser;
import org.junit.Assert;
import org.junit.Test;
public class CredentialGenerateParseTest
{
@Test
public void testGenerateParse() throws Exception
{
short slot = 1;
byte[] proof = new byte[]{0, 1, 2};
Certificate[] certificates = new Certificate[0]; // TODO
CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
Assert.assertNotNull(buffer);
TestSPDYParserListener listener = new TestSPDYParserListener();
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
parser.addListener(listener);
parser.parse(buffer);
ControlFrame frame2 = listener.getControlFrame();
Assert.assertNotNull(frame2);
Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType());
CredentialFrame credential = (CredentialFrame)frame2;
Assert.assertEquals(SPDY.V3, credential.getVersion());
Assert.assertEquals(0, credential.getFlags());
Assert.assertEquals(slot, credential.getSlot());
Assert.assertArrayEquals(proof, credential.getProof());
Assert.assertArrayEquals(certificates, credential.getCertificateChain());
}
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
short slot = 1;
byte[] proof = new byte[]{0, 1, 2};
Certificate[] certificates = new Certificate[0]; // TODO
CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
Assert.assertNotNull(buffer);
TestSPDYParserListener listener = new TestSPDYParserListener();
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
parser.addListener(listener);
while (buffer.hasRemaining())
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
ControlFrame frame2 = listener.getControlFrame();
Assert.assertNotNull(frame2);
Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType());
CredentialFrame credential = (CredentialFrame)frame2;
Assert.assertEquals(SPDY.V3, credential.getVersion());
Assert.assertEquals(0, credential.getFlags());
Assert.assertEquals(slot, credential.getSlot());
Assert.assertArrayEquals(proof, credential.getProof());
Assert.assertArrayEquals(certificates, credential.getCertificateChain());
}
}