1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.squareup.okhttp.internal.spdy;
18
19import com.squareup.okhttp.internal.Platform;
20import com.squareup.okhttp.internal.Util;
21import java.io.ByteArrayOutputStream;
22import java.io.Closeable;
23import java.io.DataOutputStream;
24import java.io.IOException;
25import java.io.OutputStream;
26import java.util.List;
27import java.util.zip.Deflater;
28
29/** Write spdy/3 frames. */
30final class SpdyWriter implements Closeable {
31  final DataOutputStream out;
32  private final ByteArrayOutputStream nameValueBlockBuffer;
33  private final DataOutputStream nameValueBlockOut;
34
35  SpdyWriter(OutputStream out) {
36    this.out = new DataOutputStream(out);
37
38    Deflater deflater = new Deflater();
39    deflater.setDictionary(SpdyReader.DICTIONARY);
40    nameValueBlockBuffer = new ByteArrayOutputStream();
41    nameValueBlockOut = new DataOutputStream(
42        Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
43  }
44
45  public synchronized void synStream(int flags, int streamId, int associatedStreamId, int priority,
46      int slot, List<String> nameValueBlock) throws IOException {
47    writeNameValueBlockToBuffer(nameValueBlock);
48    int length = 10 + nameValueBlockBuffer.size();
49    int type = SpdyConnection.TYPE_SYN_STREAM;
50
51    int unused = 0;
52    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
53    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
54    out.writeInt(streamId & 0x7fffffff);
55    out.writeInt(associatedStreamId & 0x7fffffff);
56    out.writeShort((priority & 0x7) << 13 | (unused & 0x1f) << 8 | (slot & 0xff));
57    nameValueBlockBuffer.writeTo(out);
58    out.flush();
59  }
60
61  public synchronized void synReply(int flags, int streamId, List<String> nameValueBlock)
62      throws IOException {
63    writeNameValueBlockToBuffer(nameValueBlock);
64    int type = SpdyConnection.TYPE_SYN_REPLY;
65    int length = nameValueBlockBuffer.size() + 4;
66
67    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
68    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
69    out.writeInt(streamId & 0x7fffffff);
70    nameValueBlockBuffer.writeTo(out);
71    out.flush();
72  }
73
74  public synchronized void headers(int flags, int streamId, List<String> nameValueBlock)
75      throws IOException {
76    writeNameValueBlockToBuffer(nameValueBlock);
77    int type = SpdyConnection.TYPE_HEADERS;
78    int length = nameValueBlockBuffer.size() + 4;
79
80    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
81    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
82    out.writeInt(streamId & 0x7fffffff);
83    nameValueBlockBuffer.writeTo(out);
84    out.flush();
85  }
86
87  public synchronized void rstStream(int streamId, int statusCode) throws IOException {
88    int flags = 0;
89    int type = SpdyConnection.TYPE_RST_STREAM;
90    int length = 8;
91    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
92    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
93    out.writeInt(streamId & 0x7fffffff);
94    out.writeInt(statusCode);
95    out.flush();
96  }
97
98  public synchronized void data(int flags, int streamId, byte[] data) throws IOException {
99    int length = data.length;
100    out.writeInt(streamId & 0x7fffffff);
101    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
102    out.write(data);
103    out.flush();
104  }
105
106  private void writeNameValueBlockToBuffer(List<String> nameValueBlock) throws IOException {
107    nameValueBlockBuffer.reset();
108    int numberOfPairs = nameValueBlock.size() / 2;
109    nameValueBlockOut.writeInt(numberOfPairs);
110    for (String s : nameValueBlock) {
111      nameValueBlockOut.writeInt(s.length());
112      nameValueBlockOut.write(s.getBytes("UTF-8"));
113    }
114    nameValueBlockOut.flush();
115  }
116
117  public synchronized void settings(int flags, Settings settings) throws IOException {
118    int type = SpdyConnection.TYPE_SETTINGS;
119    int size = settings.size();
120    int length = 4 + size * 8;
121    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
122    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
123    out.writeInt(size);
124    for (int i = 0; i <= Settings.COUNT; i++) {
125      if (!settings.isSet(i)) continue;
126      int settingsFlags = settings.flags(i);
127      out.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
128      out.writeInt(settings.get(i));
129    }
130    out.flush();
131  }
132
133  public synchronized void noop() throws IOException {
134    int type = SpdyConnection.TYPE_NOOP;
135    int length = 0;
136    int flags = 0;
137    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
138    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
139    out.flush();
140  }
141
142  public synchronized void ping(int flags, int id) throws IOException {
143    int type = SpdyConnection.TYPE_PING;
144    int length = 4;
145    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
146    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
147    out.writeInt(id);
148    out.flush();
149  }
150
151  public synchronized void goAway(int flags, int lastGoodStreamId, int statusCode)
152      throws IOException {
153    int type = SpdyConnection.TYPE_GOAWAY;
154    int length = 8;
155    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
156    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
157    out.writeInt(lastGoodStreamId);
158    out.writeInt(statusCode);
159    out.flush();
160  }
161
162  public synchronized void windowUpdate(int streamId, int deltaWindowSize) throws IOException {
163    int type = SpdyConnection.TYPE_WINDOW_UPDATE;
164    int flags = 0;
165    int length = 8;
166    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
167    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
168    out.writeInt(streamId);
169    out.writeInt(deltaWindowSize);
170    out.flush();
171  }
172
173  @Override public void close() throws IOException {
174    Util.closeAll(out, nameValueBlockOut);
175  }
176}
177