docs: public Event read() throws IOException, ClassNotFoundException, JsonException, IllegalStateException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
  		StringBuilder eventBuilder = new StringBuilder();
  		Event event = null;
  		State state = State.NEW_EVENT;
  		Field field = Field.NONE;
  
  		EVENT_READ_LOOP: for (;;) {
  			int i = reader.read();
  			if (i == -1) {
  				return null;
  			}
  			char c = (char) i;
  
  			switch (state) {
  			case NEW_EVENT:
  				assert field == Field.NONE;
  				state = State.NEW_FIELD;
  
  			case NEW_FIELD:
  				if (c == '\r') {
  					// empty field mark event end
  					state = State.EVENT_LF;
  					break;
  				}
  				switch (c) {
  				case 'e':
  					field = Field.EVENT;
  					break;
  				case 'd':
  					field = Field.DATA;
  					break;
  				case 'i':
  					field = Field.ID;
  					break;
  				case 'r':
  					field = Field.RETRY;
  					break;
  				default:
  					throw new IllegalStateException();
  				}
  				state = State.WAIT_COLON;
  				break;
  
  			case WAIT_COLON:
  				if (c != ':') {
  					continue;
  				}
  				switch (field) {
  				case EVENT:
  					state = State.EVENT;
  					break;
  				case DATA:
  					state = State.DATA;
  					this.reader.mark(1);
  					break;
  				case ID:
  					state = State.ID;
  					break;
  				case RETRY:
  					state = State.RETRY;
  					break;
  				case NONE:
  					throw new IllegalStateException();
  				}
  				break;
  
  			case EVENT:
  				if (c != '\r') {
  					eventBuilder.append(c);
  				} else {
  					if (eventBuilder.toString().isEmpty()) {
  						log.debug("Event field is empty");
  						throw new IllegalStateException();
  					}
  					state = State.FIELD_LF;
  				}
  				break;
  
  			case DATA:
  				reader.reset();
  
  				// data:jsonCRLFCRLF, data:base64CRLFCRLF or data:CRLFCRLF for keep alive
  				String message = this.reader.readLine();
  				this.reader.readLine(); // extract event end from stream
  
  				if (message.isEmpty()) {
  					// keep alive
  					state = State.NEW_EVENT;
  					field = Field.NONE;
  					continue;
  				}
  
  				if (this.secretKey != null) {
  					Cipher cipher = Cipher.getInstance("AES");
  					cipher.init(Cipher.DECRYPT_MODE, secretKey);
  					message = new String(cipher.doFinal(Base64.decode(message)));
  				}
  
  				StringReader jsonReader = new StringReader(message);
  				event = json.parseObject(jsonReader);
  				jsonReader.close();
  				break EVENT_READ_LOOP;
  
  			case ID:
  			case RETRY:
  				if (c == '\r') {
  					state = State.FIELD_LF;
  				}
  				break;
  
  			case FIELD_CR:
  				if (c != '\r') {
  					throw new IllegalStateException();
  				}
  				state = State.FIELD_LF;
  				break;
  
  			case FIELD_LF:
  				if (c != '\n') {
  					throw new IllegalStateException();
  				}
  				state = State.NEW_FIELD;
  				break;
  
  			case EVENT_CR:
  				if (c != '\r') {
  					throw new IllegalStateException();
  				}
  				state = State.EVENT_LF;
  				break;
  
  			case EVENT_LF:
  				if (c != '\n') {
  					throw new IllegalStateException();
  				}
  				if (event instanceof KeepAliveEvent) {
  					eventBuilder.setLength(0);
  					state = State.NEW_EVENT;
  					field = Field.NONE;
  				} else {
  					break EVENT_READ_LOOP;
  				}
  			}
  		}
  
  		return event;
  	}