View Javadoc

1   /*
2    *  Copyright 2006 Simon Raess
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  package net.sf.beep4j.internal;
17  
18  import java.nio.ByteBuffer;
19  import java.nio.CharBuffer;
20  import java.nio.charset.Charset;
21  
22  import net.sf.beep4j.ProtocolException;
23  
24  /**
25   * ParseState to read the header of a BEEP message. If a valid header
26   * is found, the method {@link ParseStateContext#handleHeader(String[])}
27   * is invoked and the tokenized header is passed in.
28   * 
29   * @author Simon Raess
30   */
31  final class HeaderState implements ParseState {
32  	
33  	private static final int NOT_FOUND = -1;
34  
35  	private static final byte CR = '\r';
36  	
37  	private static final byte LF = '\n';
38  	
39  	private static final int MAX_HEADER_LENGTH = 61;
40  
41  	private final ByteBuffer tmp = ByteBuffer.allocate(MAX_HEADER_LENGTH);
42  	
43  	public boolean process(ByteBuffer buffer, ParseStateContext context) {
44  		int position = buffer.position();
45  		int index = findLF(buffer);
46  		
47  		if (index != NOT_FOUND) {
48  			checkHeaderLength(tmp.position() + index - 1);
49  			
50  			int limit = buffer.limit();
51  			buffer.limit(index + position);			
52  			tmp.put(buffer);
53  			buffer.limit(limit);
54  			tmp.flip();
55  			
56  			// Verify that the CR is present
57  			tmp.position(tmp.limit() - 1);
58  			if (tmp.get() != CR) {
59  				throw new ProtocolException("found LF but missing CR in header");
60  			}
61  			tmp.rewind();
62  			
63  			// Rewind to before the CR
64  			tmp.limit(tmp.limit() - 1);
65  			
66  			// Step past the LF
67  			buffer.position(buffer.position() + 1);
68  			
69  			String[] tokens = tokenize(tmp);	
70  			context.handleHeader(tokens);
71  			
72  			tmp.clear();
73  			
74  			return buffer.hasRemaining();
75  			
76  		} else {
77  			checkHeaderLength(tmp.position() + buffer.remaining() - 1);
78  			tmp.put(buffer);
79  			return false;
80  		}
81  	}
82  
83  	private void checkHeaderLength(int length) {
84  		if (length > MAX_HEADER_LENGTH) {
85  			throw new ProtocolException("header longer than maximum: " 
86  					+ length + " > " + MAX_HEADER_LENGTH);
87  		}
88  	}
89  		
90  	private String[] tokenize(ByteBuffer header) {
91  		boolean space = false;
92  		
93  		ByteBuffer copy = header.asReadOnlyBuffer();
94  		int remaining = copy.remaining();
95  		for (int i = 0; i < remaining; i++) {
96  			byte current = copy.get();
97  			if (space && current == (byte) ' ') {
98  				throw new ProtocolException("two consecutive spaces in header");
99  			}
100 			space = current == (byte) ' ';
101 		}
102 		
103 		Charset charset = Charset.forName("US-ASCII");
104 		CharBuffer buffer = charset.decode(header);
105 			
106 		String[] tmp = buffer.toString().split(" ");			
107 		return tmp;
108 	}
109 	
110 	private int findLF(ByteBuffer buf) {
111 		if (buf.hasRemaining()) {
112 			buf.mark();
113 			int remaining = buf.remaining();
114 
115 			for (int i = 0; i < remaining; i++) {
116 				if (buf.get() == LF) {
117 					buf.reset();
118 					return i;
119 				}
120 			}
121 
122 			buf.reset();
123 		}
124 
125 		return NOT_FOUND;
126 	}
127 
128 }