From 395d49ba71129601ff99630104958dc38757d496 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 1 Jun 2012 15:23:22 +0200 Subject: [PATCH] Basic implementation of CREDENTIAL frame, parser and generator. --- .../eclipse/jetty/spdy/SessionException.java | 1 - .../jetty/spdy/frames/ControlFrameType.java | 3 +- .../jetty/spdy/frames/CredentialFrame.java | 49 ++++ .../spdy/generator/CredentialGenerator.java | 71 +++++ .../jetty/spdy/generator/Generator.java | 1 + .../jetty/spdy/parser/ControlFrameParser.java | 1 + .../spdy/parser/CredentialBodyParser.java | 262 ++++++++++++++++++ .../frames/CredentialGenerateParseTest.java | 88 ++++++ 8 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java create mode 100644 jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java create mode 100644 jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java create mode 100644 jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java index 3e0c1950e58..fa9a55e7c3a 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java @@ -20,7 +20,6 @@ import org.eclipse.jetty.spdy.api.SessionStatus; public class SessionException extends RuntimeException { - private final SessionStatus sessionStatus; public SessionException(SessionStatus sessionStatus) diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java index bf638d3bf56..e3b8f40ae7f 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java @@ -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) { diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java new file mode 100644 index 00000000000..ac3e65b9d40 --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java @@ -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; + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java new file mode 100644 index 00000000000..cb7bf319ec2 --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java @@ -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 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 serializeCertificates(Certificate[] certificates) + { + // TODO + return new ArrayList<>(); + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java index 70462c6ddd8..4f114b75451 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java @@ -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); } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java index af8b78f6548..d7c9a6586c7 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java @@ -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() diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java new file mode 100644 index 00000000000..c39af7b6b0d --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java @@ -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 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 + } +} diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java new file mode 100644 index 00000000000..2b210b940d6 --- /dev/null +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java @@ -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()); + } +}