Beispiel #1
0
bool Application::init(int argc, const char* argv[]) {

	// set main thread name
	Util::SetCurrentThreadName("Main");

	initApplicationPaths(argv[0]);
	

	// configurations
	string  name("configs");
	getString("application.baseName", name);
	string configPath(_path.parent());
	configPath.append(name);
	configPath.append(".ini");
	if (loadConfigurations(configPath)) {
		setString("application.configPath", configPath);
		vector<string> values;
		FileSystem::Unpack(configPath, values);
		if (!values.empty())
			values.resize(values.size() - 1);
		FileSystem::Pack(values, configPath);
		setString("application.configDir", configPath);
		
	}

	// logs
	Logs::SetLogger(*this);

	string logDir(_path.parent());
	logDir.append("logs");
	string logFileName("log");

	if (loadLogFiles(logDir, logFileName, _logSizeByFile, _logRotation)) {
		FileSystem::CreateDirectory(logDir);
		_logPath = logDir + "/" + logFileName;
		if (_logRotation > 0) {
			_logPath.append(".");
			_logStream.open(_logPath + "0", ios::out | ios::binary | ios::app);
		}  else
			_logStream.open(_logPath, ios::out | ios::binary | ios::app);
	}

	// options
	Exception ex;
	defineOptions(ex, _options);
	if (ex)
        FATAL_ERROR(ex.error());

	if (!_options.process(ex, argc, argv, [this](const string& name, const string& value) { setString("arguments." + name, value); }))
        FATAL_ERROR("Arguments, ",ex.error()," use 'help'")
	else if (ex)
		WARN("Arguments, ",ex.error()," use 'help'")

	if (hasArgument("help")) {
		displayHelp();
		return false;
	}
	return true;
}
Beispiel #2
0
void FlashStream::messageHandler(const string& name,AMFReader& message,FlashWriter& writer) {
	if(name=="play") {
		disengage(&writer);

		string publication;
		message.readString(publication);
		// TODO implements completly NetStream.play method, with possible NetStream.play.failed too!
		Exception ex;
		_pListener = invoker.subscribe(ex,peer,publication,writer);
		if (ex) {
			writer.writeAMFStatus("NetStream.Play.Failed",ex.error());
			return;
		}
		if(message.available())
			_pListener->setNumber("unbuffered",message.readNumber()==-3000);

		if(_bufferTime>0) {
			// To do working the buffertime on receiver side
			BinaryWriter& raw = writer.writeRaw();
			raw.write16(0);
			raw.write32(id);
			_pListener->setNumber("bufferTime",_bufferTime);
		}
		writer.writeAMFStatus("NetStream.Play.Reset","Playing and resetting " + publication); // for entiere playlist
		writer.writeAMFStatus("NetStream.Play.Start","Started playing " + publication); // for item

	} else if(name == "closeStream") {
		disengage(&writer);
	} else if(name=="publish") {

		disengage(&writer);

		string type,publication;
		message.readString(publication);
		size_t query = publication.find('?');
		if (query != string::npos)
			publication = publication.substr(0, query); // TODO use query in Util::UnpackQuery for publication options?
		if(message.available())
			message.readString(type); // TODO support "append" and "appendWithGap"

		Exception ex;
		_pPublication = invoker.publish(ex, peer, publication, type == "record" ? Publication::RECORD : Publication::LIVE);
		if (ex)
			writer.writeAMFStatus("NetStream.Publish.BadName",ex.error());
		else
			writer.writeAMFStatus("NetStream.Publish.Start",publication +" is now published");
	} else if(_pListener && name=="receiveAudio") {
		_pListener->receiveAudio = message.readBoolean();
	} else if(_pListener && name=="receiveVideo") {
		_pListener->receiveVideo = message.readBoolean();
	} else
		ERROR("RTMFPMessage '",name,"' unknown on stream ",id);
}
Beispiel #3
0
bool Application::init(int argc, const char* argv[]) {

	// set main thread name
	Util::SetCurrentThreadName("Main");

	initApplicationPaths(argv[0]);
	
	// configurations
	string configPath;
	if (loadConfigurations(String::Format(configPath,_file.parent(),_file.baseName(),".ini"))) {
		setString("application.configPath", configPath);
		setString("application.configDir", FileSystem::GetParent(configPath));
	}

	// logs
	Logs::SetLogger(*this);

	string logDir(_file.parent());
	logDir.append("logs");
	string logFileName("log");

	Exception ex;
	if (loadLogFiles(logDir, logFileName, _logSizeByFile, _logRotation)) {
		bool success;
		EXCEPTION_TO_LOG(success=FileSystem::CreateDirectory(ex, logDir),file().baseName()," log system")
		if (success) {
			_logPath.assign(FileSystem::MakeFolder(logDir)).append(logFileName);
			if (_logRotation > 0) {
				_logPath += '.';
				_logStream.open(_logPath += '0', ios::out | ios::binary | ios::app);
			}  else
				_logStream.open(_logPath, ios::out | ios::binary | ios::app);
		}
	}

	// options
	defineOptions(ex, _options);
	if (ex)
        FATAL_ERROR(ex.error());

	if (!_options.process(ex, argc, argv, [this](const string& name, const string& value) { setString("arguments." + name, value); }))
        FATAL_ERROR("Arguments, ",ex.error()," use 'help'")
	else if (ex)
		WARN("Arguments, ",ex.error()," use 'help'")

	if (hasArgument("help")) {
		displayHelp();
		return false;
	}
	return true;
}
Beispiel #4
0
void Peer::onConnection(Exception& ex, Writer& writer,DataReader& parameters,DataWriter& response) {
	if(!connected) {
		_pWriter = &writer;

		// reset default protocol parameters
		_parameters.clear();
		Parameters::ForEach forEach([this](const string& key,const string& value) {
			_parameters.setString(key,value);
		});
		string buffer;
		_handler.iterate(String::Format(buffer,protocol,"."), forEach);

		ParameterWriter parameterWriter(_parameters);
		SplitWriter parameterAndResponse(parameterWriter,response);

		_handler.onConnection(ex, *this,parameters,parameterAndResponse);
		if (!ex) {
			(bool&)connected = ((Entities<Client>&)_handler.clients).add(*this);
			if (!connected) {
				ex.set(Exception::PROTOCOL, "Client ", Util::FormatHex(id, ID_SIZE, buffer), " exists already");
				ERROR(ex.error());
				_handler.onDisconnection(*this);
			}
		}
		if (!connected) {
			writer.abort();
			_pWriter = NULL;
		} else {
			OnInitParameters::raise(_parameters);
			DEBUG("Client ",address.toString()," connection")
		}
		writer.open(); // open even if "ex" to send error messages!
	} else
Beispiel #5
0
bool Application::loadConfigurations(string& path) {
	Exception ex;
	if (Util::ReadIniFile(ex, path, *this))
		return true;
	DEBUG("Impossible to load configuration file (", ex.error(), ")");
	return false;
}
Beispiel #6
0
UInt8 QueryReader::valueType() {

	if (String::ICompare(_value, "null") == 0)
		return NIL;

	if (String::ICompare(_value, "false") == 0) {
		_number = 0;
		return BOOLEAN;
	}

	if (String::ICompare(_value, "true") == 0) {
		_number = 1;
		return BOOLEAN;
	}

	Exception ex;
	if (_date.update(ex, _value)) {
		if (ex)
			WARN("QueryReader date, ", ex.error());
		return DATE;
	}

	if (String::ToNumber(_value, _number))
		return NUMBER;

	return STRING;
}
Beispiel #7
0
bool Parse(const char * stDate, Int32 year, UInt8 month, UInt8 day,UInt8 weekDay, UInt8 hour, UInt8 minute, UInt8 second, UInt16 millisecond,Int32 offset,const char* format=NULL) {
	bool bIsParseOk = _Date.update(Ex,stDate,format);
	if (!bIsParseOk) {
		DEBUG("Error during parsing of ", stDate, ", ",Ex.error());
		return false;
	}
	return Check(year, month, day, weekDay, hour, minute, second, millisecond, offset);
}
Beispiel #8
0
void RTMFPWriter::manage(Exception& ex, Invoker& invoker) {
	if(!consumed() && !_band.failed()) {
		
		if(_trigger.raise(ex))
			raiseMessage();

		if (ex) {
			fail("RTMFPWriter can't deliver its data, ",ex.error());
			return;
		}
	}
	if(critical && state()==CLOSED) {
		ex.set(Exception::NETWORK, "Main flow writer closed, session is closing");
		return;
	}
	flush(false);
}
Beispiel #9
0
void RTMFPSession::manage() {
	if(died)
		return;

	Session::manage();

	if (_failed) {
		failSignal();
		return;
	}

	// After 6 mn we considerate than the session has failed
	if(_recvTimestamp.isElapsed(360000000)) {
		fail("Timeout no client message");
		return;
	}

	// To accelerate the deletion of peer ghost (mainly for netgroup efficient), starts a keepalive server after 2 mn
	if(_recvTimestamp.isElapsed(120000000) && !keepAlive()) // TODO check it!
		return;

	// Raise RTMFPWriter
	auto it=_flowWriters.begin();
	while (it != _flowWriters.end()) {
		Exception ex;
		it->second->manage(ex, invoker);
		if (ex) {
			if (it->second->critical) {
				fail(ex.error());
				break;
			}
			continue;
		}
		if (it->second->consumed()) {
			_flowWriters.erase(it++);
			continue;
		}
		++it;
	}

	flush();
}
Beispiel #10
0
void RTMFPSession::flush(UInt8 marker,bool echoTime,RTMFPEngine::Type type) {
	_pLastWriter=NULL;
	if(!_pSender)
		return;
	if (!died && _pSender->available()) {
		PacketWriter& packet(_pSender->packet);
	
		// After 30 sec, send packet without echo time
		if(_recvTimestamp.isElapsed(30000000))
			echoTime = false;

		if(echoTime)
			marker+=4;
		else
			packet.clip(2);

		BinaryWriter writer(packet, 6);
		writer.write8(marker).write16(RTMFP::TimeNow());
		if(echoTime)
			writer.write16(_timeSent+RTMFP::Time(_recvTimestamp.elapsed()));

		_pSender->farId = farId;
		_pSender->encoder.type = type;
		_pSender->address.set(peer.address);

		if (packet.size() > RTMFP_MAX_PACKET_SIZE)
			ERROR("Message exceeds max RTMFP packet size");

		dumpResponse(packet.data() + 6, packet.size() - 6);

		Exception ex;
		_pThread = _socket.send<RTMFPSender>(ex, _pSender,_pThread);
		if (ex)
			ERROR("RTMFP flush, ", ex.error());
	}
	_pSender.reset();
}
Beispiel #11
0
void RTMFPSession::flush(bool echoTime,UInt8 marker) {
	_pLastWriter=NULL;
	if(!_pSender)
		return;
	if (!died && _pSender->available()) {
		BinaryWriter& packet(_pSender->packet);
	
		// After 30 sec, send packet without echo time
		if(peer.lastReceptionTime.isElapsed(30000))
			echoTime = false;

		if(echoTime)
			marker+=4;
		else
			packet.clip(2);

		BinaryWriter writer(packet.data()+6, 5);
		writer.write8(marker).write16(RTMFP::TimeNow());
		if (echoTime)
			writer.write16(_timeSent+RTMFP::Time(peer.lastReceptionTime.elapsed()));

		_pSender->farId = farId;
		_pSender->address.set(peer.address);

		if (packet.size() > RTMFP_MAX_PACKET_SIZE)
			ERROR("Message exceeds max RTMFP packet size on session ",name()," (",packet.size(),">",RTMFP_MAX_PACKET_SIZE,")");

		dumpResponse(packet.data() + 6, packet.size() - 6);

		Exception ex;
		_pThread = Session::send<RTMFProtocol,RTMFPSender>(ex, _pSender,_pThread);
		if (ex)
			ERROR("RTMFP flush, ", ex.error());
	}
	_pSender.reset();
}
Beispiel #12
0
void FlashStream::messageHandler(const string& name, AMFReader& message, FlashWriter& writer) {
	if (name == "play") {
		disengage(&writer);

		string publication;
		message.readString(publication);
		// TODO implements completly NetStream.play method, with possible NetStream.play.failed too!
		Exception ex;
		_pListener = invoker.subscribe(ex, peer, publication, writer); // ex already log displayed
		if (!_pListener) {
			writer.writeAMFStatus("NetStream.Play.Failed", ex.error());
			return;
		}
		
		OnStart::raise(id, writer); // stream begin
		writer.writeAMFStatus("NetStream.Play.Reset", "Playing and resetting " + publication); // for entiere playlist
		writer.writeAMFStatus("NetStream.Play.Start", "Started playing "+publication); // for item
		AMFWriter& amf(writer.writeInfos("|RtmpSampleAccess"));
		amf.writeBoolean(true); // audioSampleAccess
		amf.writeBoolean(true); // videoSampleAccess

		if (_bufferTime > 0)
			_pListener->setNumber("bufferTime", _bufferTime);
	
	} else if (name == "closeStream") {
		disengage(&writer);
	} else if (name == "publish") {

		disengage(&writer);

		string type, publication;
		message.readString(publication);
		size_t query = publication.find('?');
		if (query != string::npos)
			publication = publication.substr(0, query); // TODO use query in Util::UnpackQuery for publication options?
		if (message.available())
			message.readString(type); // TODO support "append" and "appendWithGap"

		Exception ex;
		_pPublication = invoker.publish(ex, peer, publication, type == "record" ? Publication::RECORD : Publication::LIVE);
		if (ex) {
			writer.writeAMFStatus("NetStream.Publish.BadName", ex.error());
			_pPublication = NULL;
		} else
			writer.writeAMFStatus("NetStream.Publish.Start", publication + " is now published");
	} else if (_pListener && name == "receiveAudio") {
		message.readBoolean(_pListener->receiveAudio);
	} else if (_pListener && name == "receiveVideo") {
		message.readBoolean(_pListener->receiveVideo);

	} else if (_pListener && name == "pause") {
		bool paused(true);
		message.readBoolean(paused);
		// TODO support pause for VOD
		
		if (paused) {
			// useless, client knows it when it calls NetStream::pause method
			// writer.writeAMFStatus("NetStream.Pause.Notify", _pListener->publication.name() + " paused");
		} else {
			double position;
			if (message.readNumber(position))
				_pListener->seek((UInt32)position);
			OnStart::raise(id, writer); // stream begin
			// useless, client knows it when it calls NetStream::resume method
			//	writer.writeAMFStatus("NetStream.Unpause.Notify", _pListener->publication.name() + " resumed");
		}

	} else if (_pListener && name == "seek") {
		double position;
		if (message.readNumber(position)) {
			_pListener->seek((UInt32)position);
			 // TODO support seek for VOD
			OnStart::raise(id, writer); // stream begin
			// useless, client knows it when it calls NetStream::seek method, and wait "NetStream.Seek.Complete" rather (raised by client side)
			// writer.writeAMFStatus("NetStream.Seek.Notify", _pListener->publication.name() + " seek operation");
		} else
			writer.writeAMFStatus("NetStream.Seek.InvalidTime", _pListener->publication.name() + " seek operation must pass in argument a milliseconds position time");
	} else if (_pPublication && name == "@setDataFrame") {
		// metadata
		_pPublication->writeProperties(message);
	} else if (_pPublication && name == "@clearDataFrame") {
		_pPublication->clearProperties();
	} else
		ERROR("Message '",name,"' unknown on stream ",id);
}
Beispiel #13
0
void WSSession::packetHandler(PacketReader& packet) {
	UInt8 type = 0;
	Exception ex;
	if(peer.connected) {
		type = packet.read8();	
		
		switch(type) {
			case WS::TYPE_BINARY: {
				RawReader reader(packet);
				peer.onMessage(ex, "onMessage",reader,WS::TYPE_BINARY);
				break;
			}
			case WS::TYPE_TEXT: {
				if(!JSONReader::IsValid(packet)) {
					RawReader reader(packet);
					peer.onMessage(ex, "onMessage",reader);
					break;
				}
				JSONReader reader(packet);
				if(reader.followingType()!=JSONReader::STRING) {
					peer.onMessage(ex, "onMessage",reader);
					break;
				}
				string name;
				reader.readString(name);
				if(name=="__publish") {
					if(reader.followingType()!=JSONReader::STRING) {
						ex.set(Exception::PROTOCOL, "__publish method takes a stream name in first parameter",WS::CODE_MALFORMED_PAYLOAD);
						break;
					}
					reader.readString(name);
					if(_pPublication)
						invoker.unpublish(peer,_pPublication->name());
					_pPublication = invoker.publish(ex, peer,name);
				} else if(name=="__play") {
					if(reader.followingType()!=JSONReader::STRING) {
						ex.set(Exception::PROTOCOL, "__play method takes a stream name in first parameter",WS::CODE_MALFORMED_PAYLOAD);
						break;
					}
					reader.readString(name);
					
					closeSusbcription();
				} else if(name=="__closePublish") {
					closePublication();
				} else if(name=="__closePlay") {
					closeSusbcription();
				} else if (name == "__close") {
					closePublication();
					closeSusbcription();
					
				} else if(_pPublication) {
					reader.reset();
					_pPublication->pushData(reader);
				} else
					peer.onMessage(ex, name,reader);
				break;
			}
			case WS::TYPE_CLOSE:
				_writer.close(packet.available() ? packet.read16() : 0);
				break;
			case WS::TYPE_PING:
				_writer.writePong(packet.current(),packet.available());
				break;
			case WS::TYPE_PONG:
				peer.setPing(_writer.ping = (UInt16)(_time.elapsed()/1000));
				break;
			default:
				ex.set(Exception::PROTOCOL, Format<UInt8>("Type %#x unknown", type), WS::CODE_MALFORMED_PAYLOAD);
				break;
		}
		
		if (ex) {
			ERROR(ex.error());
			_writer.close((ex.code()==Exception::APPLICATION || ex.code() == Exception::SOFTWARE) ? WS::CODE_PROTOCOL_ERROR : ex.code());	
		}
		
	}

	if(!peer.connected || type==WS::TYPE_CLOSE)
		kill();
	else
		_writer.flush();
}
Beispiel #14
0
UInt8 RTMFPHandshake::handshakeHandler(UInt8 id,const SocketAddress& address, BinaryReader& request,BinaryWriter& response) {

	switch(id){
		case 0x30: {
			
			request.read7BitValue(); // = epdLen + 2 (useless)
			UInt16 epdLen = request.read7BitValue()-1;
			UInt8 type = request.read8();
			string epd;
			request.read(epdLen,epd);

			string tag;
			request.read(16,tag);
			response.write7BitValue(tag.size()).write(tag);
		
			if(type == 0x0f) {

				const UInt8* peerId((const UInt8*)epd.c_str());
				
				RTMFPSession* pSessionWanted = _sessions.findByPeer<RTMFPSession>(peerId);
	
				if(pSessionWanted) {
					if(pSessionWanted->failed())
						return 0x00; // TODO no way in RTMFP to tell "died!"
					/// Udp hole punching
					UInt32 times = attempt(tag);
		
					RTMFPSession* pSession(NULL);
					if(times > 0 || address.host() == pSessionWanted->peer.address.host()) // try in first just with public address (excepting if the both peer are on the same machine)
						pSession = _sessions.findByAddress<RTMFPSession>(address,Socket::DATAGRAM);
					
					bool hasAnExteriorPeer(pSessionWanted->p2pHandshake(tag,address,times,pSession));
					
					// public address
					RTMFP::WriteAddress(response,pSessionWanted->peer.address, RTMFP::ADDRESS_PUBLIC);
					DEBUG("P2P address initiator exchange, ",pSessionWanted->peer.address.toString());

					if (hasAnExteriorPeer && pSession->peer.serverAddress.host()!=pSessionWanted->peer.address.host()) {
						// the both peer see the server in a different way (and serverAddress.host()!= public address host written above),
						// Means an exterior peer, but we can't know which one is the exterior peer
						// so add an interiorAddress build with how see eachone the server on the both side
						SocketAddress interiorAddress(pSession->peer.serverAddress.host(), pSessionWanted->peer.address.port());
						RTMFP::WriteAddress(response,interiorAddress, RTMFP::ADDRESS_PUBLIC);
						DEBUG("P2P address initiator exchange, ",interiorAddress.toString());
					}	

					// local address
					for(const SocketAddress& address : pSessionWanted->peer.localAddresses) {
						RTMFP::WriteAddress(response,address, RTMFP::ADDRESS_LOCAL);
						DEBUG("P2P address initiator exchange, ",address.toString());
					}

					// add the turn address (RelayServer) if possible and required
					if (pSession && times>0) {
						UInt8 timesBeforeTurn(0);
						if(pSession->peer.parameters().getNumber("timesBeforeTurn",timesBeforeTurn) && timesBeforeTurn>=times) {
							UInt16 port = invoker.relayer.relay(pSession->peer.address,pSessionWanted->peer.address,20); // 20 sec de timeout is enough for RTMFP!
							if (port > 0) {
								SocketAddress address(pSession->peer.serverAddress.host(), port);
								RTMFP::WriteAddress(response, address, RTMFP::ADDRESS_REDIRECTION);
							} // else ERROR already display by RelayServer class
						}
					}
					return 0x71;
				}


				DEBUG("UDP Hole punching, session ", Util::FormatHex(peerId, ID_SIZE, LOG_BUFFER), " wanted not found")
				set<SocketAddress> addresses;
				peer.onRendezVousUnknown(peerId,addresses);
				set<SocketAddress>::const_iterator it;
				for(it=addresses.begin();it!=addresses.end();++it) {
					if(it->host().isWildcard())
						continue;
					if(address == *it)
						WARN("A client tries to connect to himself (same ", address.toString()," address)");
					RTMFP::WriteAddress(response,*it,RTMFP::ADDRESS_REDIRECTION);
					DEBUG("P2P address initiator exchange, ",it->toString());
				}
				return addresses.empty() ? 0 : 0x71;
			}

			if(type == 0x0a){
				/// RTMFPHandshake
				HelloAttempt& attempt = AttemptCounter::attempt<HelloAttempt>(tag);

				Peer& peer(*_pPeer);

				// Fill peer infos
				peer.properties().clear();
				string serverAddress;
				Util::UnpackUrl(epd, serverAddress, (string&)peer.path,(string&)peer.query);
				peer.setServerAddress(serverAddress);
				Util::UnpackQuery(peer.query, peer.properties());

				Exception ex;

				set<SocketAddress> addresses;
				peer.onHandshake(attempt.count+1,addresses);
				if(!addresses.empty()) {
					set<SocketAddress>::iterator it;
					for(it=addresses.begin();it!=addresses.end();++it) {
						if (it->host().isWildcard())
							RTMFP::WriteAddress(response, peer.serverAddress, RTMFP::ADDRESS_REDIRECTION);
						else
							RTMFP::WriteAddress(response, *it, RTMFP::ADDRESS_REDIRECTION);
					}
					return 0x71;
				}


				// New RTMFPCookie
				RTMFPCookie* pCookie = attempt.pCookie;
				if (!pCookie) {
					pCookie = new RTMFPCookie(*this, invoker, tag, _pPeer);
					if (!pCookie->run(ex)) {
						delete pCookie;
						ERROR("RTMFPCookie creation, ", ex.error())
						return 0;
					}
					_pPeer.reset(new Peer((Handler&)invoker)); // reset peer
					_cookies.emplace(pCookie->value(), pCookie);
					attempt.pCookie = pCookie;
				}

				// response
				response.write8(COOKIE_SIZE);
				response.write(pCookie->value(),COOKIE_SIZE);
				// instance id (certificat in the middle)
				response.write(_certificat,sizeof(_certificat));
				return 0x70;

			} else
Beispiel #15
0
bool SDP::build(Exception& ex, const string& text) {

	SDPMedia* pMedia = NULL;

	vector<string> lines;
	String::Split(text, "\r\n", lines, String::SPLIT_IGNORE_EMPTY | String::SPLIT_TRIM);
	for(string& line : lines) {

		if(line.empty() || line[1] != '=') {
			ex.set(Exception::FORMATTING, "SDP line ",line," malformed, second byte isn't an equal sign");
			return false;
		}
	
		UInt8 type = line[0];
		line.erase(0,2);
		String::Trim(line, String::TRIM_LEFT);

		// RFC 4566
		switch(type) {
			case 'v': // v=  (protocol version)
				version = String::ToNumber<UInt32>(ex, line);
				break;
			case 'o': { // o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
				vector<string> fields;
				String::Split(line, " ", fields, String::SPLIT_IGNORE_EMPTY | String::SPLIT_TRIM);
				if (fields.size()!=6) {
					ex.set(Exception::PROTOCOL, "fields.size()!=6");
					break;
				}
				user = fields[0];
				sessionId = String::ToNumber<UInt32>(ex, fields[1]);
				if (ex)
					break;
				sessionVersion = String::ToNumber<UInt32>(ex, fields[2]);
				if (ex)
					break;

				unicastAddress.set(ex,fields[5],fields[4]=="IP6" ? IPAddress::IPv6 : IPAddress::IPv4);
				break;
			}
			case 's': // s=  (session name)
				sessionName = line;
				break;

			/// Optional lines
			case 'i': // i=* (session information)
				sessionInfos = line;
				break;
			case 'u': // u=* (URI of description)
				uri = line;
				break;
			case 'e': // e=* (email address)
				email = line;
				break;
			case 'p': // p=* (phone number)
				phone = line;
				break;
			case 'c': { // c=* (connection information -- not required if included in all media)
				vector<string> fields;
				String::Split(line, " ", fields, String::SPLIT_IGNORE_EMPTY | String::SPLIT_TRIM);
				if(fields.size()!=3) {
					ex.set(Exception::PROTOCOL, "fields.size()!=3");
					break;
				}

				IPAddress defaultAddress;  // TODO defaultAddress is useless for what?
				defaultAddress.set(ex,fields[2], fields[1] == "IP6" ? IPAddress::IPv6 : IPAddress::IPv4);
				break;
			}
			case 'b': // b=* (zero or more bandwidth information lines)
				// TODO useless?
				break;
				 
				// One or more time descriptions ("t=" and "r=" lines; see below)
				// t=  (time the session is active)
				// r=* (zero or more repeat times)
			case 't':
				// TODO useless?
				break;
			case 'r':
				// TODO useless?
				break;
			case 'z': // z=* (time zone adjustments)
				// TODO useless?
				break;

			case 'k': // k=* (encryption key)
				encryptKey = line;
				break;
			case 'm': { // m=<name> <port> <proto> <fmt>
				vector<string> values;
				String::Split(line, " ", values, String::SPLIT_IGNORE_EMPTY | String::SPLIT_TRIM);
				if (values.size()<4) {
					ex.set(Exception::PROTOCOL , "values.size()<4");
					break;
				}

				UInt16 val = String::ToNumber<UInt16>(ex, values[1]);
				if (ex)
					break;

				pMedia = addMedia(values[0], val, values[2]);
				for(const string& st : values) {
					pMedia->formats.emplace_back(String::ToNumber<UInt8>(ex, st));
					if (ex)
						break;
				}
				break;
			}
			case 'a': { // a=* (zero or more session attribute lines)

				vector<string>* pFields = NULL;
				bool	isMsId = false;
				string   fingerHash;

				// TODO SDPSource* pSource = NULL;
				// TODO list<UInt32>* pSourceGroupe = NULL;

				vector<string> fields;
				String::Split(line, " ", fields, String::SPLIT_IGNORE_EMPTY | String::SPLIT_TRIM);
				for(const string& st : fields) {
					size_t pos = st.find(':');
					string key,value;
					if(pos!=string::npos) {
						key = st.substr(0,pos);
						value = st.substr(pos+1);
					} else 
						key = st;

					// RFC 5576
					if(pMedia) {
						/* TODO
						if(pSourceGroupe)
							pSourceGroupe->push_back(NumberParser::parseUnsigned(field));
						else if(pSource) {
							if(isMsId)
								pSource->msData = field;
							else if(key=="cname") // cname:<value>
								pSource->cname = value;
							else if(key=="msid") {// draft-alvestrand-mmusic-msid-00
								isMsId = true;
								pSource->msId = value;
							} else if(key=="mslabel") // draft-alvestrand-rtcweb-mid-01
								pSource->msLabel = value;
							else if(key=="label") // draft-alvestrand-rtcweb-mid-01
								pSource->label = value;
							else
								WARN("Media source attribute ",key," unknown");
						} else if(key=="ssrc") // a=ssrc:<ssrc-id>
							pSource = &sources[NumberParser::parseUnsigned(key)];// a=ssrc:<ssrc-id> <attribute>
						else if(key=="rtcp-mux")
							pMedia->rtcpMux = true;
						else if(key=="mid")  // RFC 3388, a=mid:<token>
							pMedia->mid = value;
						else if(key=="ssrc-group")  // a=ssrc-group:<semantics>
							pSourceGroupe = &sourceGroupes[value];
						else if(key="crypto") { // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
							StringTokenizer values(value," ",StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
							poco_assert(values.size()>2)
							pMedia->cryptoTag = 
						} else */
							WARN("Media attribute ",key," unknown");
					} else if(pFields)
						pFields->emplace_back(st);
					else if(!fingerHash.empty())
						int i=0; // TODO finger = talk_base::SSLFingerprint::CreateFromRfc4572(fingerHash, field);
					else if(key=="group") // RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00
						pFields = &groups[value];
					else if(key=="ice-ufrag")
						iceUFrag = value;
					else if(key=="ice-pwd")
						icePwd = value;
					else if(key=="ice-options") {
						pFields = &iceOptions;
						iceOptions.emplace_back(value);
					} else if(key=="fingerprint") // fingerprint:<hash> <algo>
						fingerHash = value; // fingerprint:<hash>
					else if(key=="msid-semantic")
						supportMsId = value=="VMS";
					else if(key=="extmap") // RFC 5285 a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
						pFields = &extensions[value];
					else 
						WARN("SDP attribute ",key," unknown");
				}
			} // case 'a':
			break;
		} // switch(type)

		if (ex) {
			ex.set(Exception::PROTOCOL, "SDP '",type,"' value ",line," malformed, ",ex.error());
			return false;
		}
	} // for(string& line : lines)

	return true;
}