17899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath/* 27899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Copyright (C) 2012 The Android Open Source Project 37899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 47899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); 57899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * you may not use this file except in compliance with the License. 67899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * You may obtain a copy of the License at 77899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 87899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * http://www.apache.org/licenses/LICENSE-2.0 97899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 107899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Unless required by applicable law or agreed to in writing, software 117899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, 127899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * See the License for the specific language governing permissions and 147899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * limitations under the License. 157899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath */ 167899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 172231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpackage com.squareup.okhttp.internal; 187899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 197899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.ByteArrayOutputStream; 207899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.Closeable; 217899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.EOFException; 227899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.IOException; 237899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.InputStream; 247899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.nio.charset.Charset; 257899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 2654cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.ISO_8859_1; 2754cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.US_ASCII; 2854cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.UTF_8; 2954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 307899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath/** 317899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Buffers input from an {@link InputStream} for reading lines. 327899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 337899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * This class is used for buffered reading of lines. For purposes of this class, a line ends with 347899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated line at 357899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * end of input is invalid and will be ignored, the caller may use {@code hasUnterminatedLine()} 367899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * to detect it after catching the {@code EOFException}. 377899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 387899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * This class is intended for reading input that strictly consists of lines, such as line-based 397899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * cache entries or cache journal. Unlike the {@link BufferedReader} which in conjunction with 407899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * {@link InputStreamReader} provides similar functionality, this class uses different 417899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * end-of-input reporting and a more restrictive definition of a line. 427899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 437899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 447899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * and 10, respectively, and the representation of no other character contains these values. 457899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. 467899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * The default charset is US_ASCII. 477899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath */ 487899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathpublic class StrictLineReader implements Closeable { 4954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final byte CR = (byte) '\r'; 5054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final byte LF = (byte) '\n'; 5154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 5254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final InputStream in; 5354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final Charset charset; 5454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 5554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end 5654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // and the data in the range [pos, end) is buffered for reading. At end of input, if there is 5754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // an unterminated line, we set end == -1, otherwise end == pos. If the underlying 5854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. 5954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private byte[] buf; 6054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private int pos; 6154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private int end; 6254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 6354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 6454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code StrictLineReader} with the default capacity and charset. 6554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 6654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 6754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} is null. 6854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 6954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in) { 7054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this(in, 8192); 7154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 7254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 7354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 7454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code LineReader} with the specified capacity and the default charset. 7554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 7654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 7754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param capacity the capacity of the buffer. 7854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} is null. 7954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IllegalArgumentException for negative or zero {@code capacity}. 8054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 8154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in, int capacity) { 8254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this(in, capacity, US_ASCII); 8354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 8454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 8554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 8654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code LineReader} with the specified charset and the default capacity. 8754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 8854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 8954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param charset the charset used to decode data. 9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Only US-ASCII, UTF-8 and ISO-8859-1 is supported. 9154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} or {@code charset} is null. 9254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IllegalArgumentException if the specified charset is not supported. 9354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 9454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in, Charset charset) { 9554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this(in, 8192, charset); 9654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 9754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 9854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 9954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code LineReader} with the specified capacity and charset. 10054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 10154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 10254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param capacity the capacity of the buffer. 10354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param charset the charset used to decode data. 10454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Only US-ASCII, UTF-8 and ISO-8859-1 is supported. 10554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} or {@code charset} is null. 10654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IllegalArgumentException if {@code capacity} is negative or zero 10754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * or the specified charset is not supported. 10854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 10954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in, int capacity, Charset charset) { 11054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (in == null || charset == null) { 11154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new NullPointerException(); 1127899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 11354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (capacity < 0) { 11454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("capacity <= 0"); 1157899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 11654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (!(charset.equals(US_ASCII) || charset.equals(UTF_8) || charset.equals(ISO_8859_1))) { 11754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("Unsupported encoding"); 1187899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 1197899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 12054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.in = in; 12154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.charset = charset; 12254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson buf = new byte[capacity]; 12354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 12454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 12554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 12654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Closes the reader by closing the underlying {@code InputStream} and 12754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * marking this reader as closed. 12854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 12954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for errors when closing the underlying {@code InputStream}. 13054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 13154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override 13254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void close() throws IOException { 13354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (in) { 13454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf != null) { 13554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson buf = null; 13654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson in.close(); 13754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1387899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 14154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 14254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, 14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * this end of line marker is not included in the result. 14454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 14554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @return the next line from the input. 14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for underlying {@code InputStream} errors. 14754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws EOFException for the end of source stream. 14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 14954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String readLine() throws IOException { 15054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (in) { 15154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf == null) { 15254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("LineReader is closed"); 15354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 15454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 15554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Read more data if we are at the end of the buffered data. 15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Though it's an error to read after an exception, we will let {@code fillBuf()} 15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // throw again if that happens; thus we need to handle end == -1 as well as end == pos. 15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (pos >= end) { 15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson fillBuf(); 16054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 16154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Try to find LF in the buffered data and return the line if successful. 16254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = pos; i != end; ++i) { 16354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf[i] == LF) { 16454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; 16554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String res = new String(buf, pos, lineEnd - pos, charset); 16654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = i + 1; 16754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return res; 1687899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 16954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 17054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 17154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Let's anticipate up to 80 characters on top of those already read. 17254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { 17354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override 17454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String toString() { 17554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; 17654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return new String(buf, 0, length, charset); 17754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 17854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson }; 17954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 18054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while (true) { 18154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buf, pos, end - pos); 18254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Mark unterminated line in case fillBuf throws EOFException or IOException. 18354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson end = -1; 18454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson fillBuf(); 18554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Try to find LF in the buffered data and return the line if successful. 18654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = pos; i != end; ++i) { 18754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf[i] == LF) { 18854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (i != pos) { 18954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buf, pos, i - pos); 1907899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 19154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = i + 1; 19254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return out.toString(); 19354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1947899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 19554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1967899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 19754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 19854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 19954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 20054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Read an {@code int} from a line containing its decimal representation. 20154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 20254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @return the value of the {@code int} from the next line. 20354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for underlying {@code InputStream} errors or conversion error. 20454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws EOFException for the end of source stream. 20554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 20654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public int readInt() throws IOException { 20754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String intString = readLine(); 20854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 20954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return Integer.parseInt(intString); 21054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (NumberFormatException e) { 21154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("expected an int but was \"" + intString + "\""); 2127899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 21354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 21454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 21554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 21654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Reads new input data into the buffer. Call only with pos == end or end == -1, 21754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * depending on the desired outcome if the function throws. 21854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 21954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for underlying {@code InputStream} errors. 22054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws EOFException for the end of source stream. 22154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 22254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void fillBuf() throws IOException { 22354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int result = in.read(buf, 0, buf.length); 22454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (result == -1) { 22554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new EOFException(); 2267899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 22754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = 0; 22854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson end = result; 22954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 2307899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath} 2317899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 232