/// <summary> /// A CoAP token created from string value /// </summary> /// <param name="tokenValue">The string value that represents the CoAP token</param> public CoAPToken(string tokenValue) { if (tokenValue == null || tokenValue.Trim().Length == 0) { throw new ArgumentNullException("Token value cannot be NULL or empty string"); } this.Value = AbstractByteUtils.StringToByteUTF8(tokenValue); this.Length = (byte)this.Value.Length; }
/// <summary> /// Creates a payload with string data /// </summary> /// <param name="payloadData">Payload data as string</param> public CoAPPayload(string payloadData) { if (payloadData == null || payloadData.Trim().Length == 0) { throw new ArgumentNullException("Payload data cannot be NULL or empty string"); } if (payloadData.Trim().Length > AbstractNetworkUtils.GetMaxMessageSize() / 2) { throw new ArgumentException("Payload size cannot be more than " + AbstractNetworkUtils.GetMaxMessageSize() / 2); } this.Value = AbstractByteUtils.StringToByteUTF8(payloadData.Trim()); }
/// <summary> /// Add the token value. If the value is larger than 8 bytes, then it is truncated to 8 bytes /// The string is treated as UTF-8 string /// </summary> /// <param name="tokenValue">The token value (maximum 8 bytes)</param> public void AddTokenValue(string tokenValue) { if (this.Token == null) { this.Token = new CoAPToken(); } if (tokenValue != null) { byte[] asBytes = AbstractByteUtils.StringToByteUTF8(tokenValue); int len = (asBytes.Length > 8) ? 8 : asBytes.Length; this.Token.Value = new byte[len]; Array.Copy(asBytes, 0, this.Token.Value, 0, len); this.Token.Length = (byte)this.Token.Value.Length; } }
/// <summary> /// Add a new option to the list of options /// </summary> /// <param name="optionNumber">The option number</param> /// <param name="optionValue">The associated option value as UTF-8string. This will be converted to bytes internally</param> public virtual void AddOption(UInt16 optionNumber, string optionValue) { CoAPHeaderOption headerOption = new CoAPHeaderOption(optionNumber, AbstractByteUtils.StringToByteUTF8(optionValue)); if (!headerOption.IsRepeatable() && this.HasOption(optionNumber)) { //Specs say that if an option is not repeatable and it still appears multiple times, //each subsequent option must be treated as un-recognized.... //In this implementation, we do not allow non-repetable options to be added to the list throw new CoAPFormatException("Non-repeatable option already present in collection. Cannot add more."); } else { this.Add(headerOption); } }
/// <summary> /// Add the payload...limited to 1024 bytes...we assume the string is in UTF-8 format /// </summary> /// <param name="payload">A unicode string not longer than 1024 bytes</param> public void AddPayload(string payload) { if (payload == null || payload.Trim().Length == 0) { return; } this.Payload = new CoAPPayload(); byte[] payloadAsUtf8 = AbstractByteUtils.StringToByteUTF8(payload); int length = payloadAsUtf8.Length; //if (length > 1024) length = 1024; this.Payload.Value = new byte[length]; Array.Copy(payloadAsUtf8, 0, this.Payload.Value, 0, length); }
/// <summary> /// Set the location URL. This is set by the response to indicate "Created" result if the /// request is POST (to create a new resource) /// </summary> /// <param name="locationURL">The location URL relative to the URI that got created</param> public void SetLocation(string locationURL) { if (locationURL == null || locationURL.Trim().Length == 0) { throw new ArgumentException("Invalid CoAP location URL"); } locationURL = locationURL.Trim().ToLower(); if (locationURL.IndexOf("#") >= 0) { throw new ArgumentException("Fragments not allowed in CoAP location URL"); } //Add these items as option //Path components string[] segments = AbstractURIUtils.GetUriSegments(locationURL); if (segments != null && segments.Length > 0) { foreach (string segment in segments) { if (segment.Trim().Length == 0) { continue; } this.Options.AddOption(CoAPHeaderOption.LOCATION_PATH, AbstractByteUtils.StringToByteUTF8(AbstractURIUtils.UrlDecode(segment))); } } //Query string[] qParams = AbstractURIUtils.GetQueryParameters(locationURL); if (qParams != null && qParams.Length > 0) { foreach (string queryComponent in qParams) { if (queryComponent.Trim().Length == 0) { continue; } this.Options.AddOption(CoAPHeaderOption.LOCATION_QUERY, AbstractByteUtils.StringToByteUTF8(AbstractURIUtils.UrlDecode(queryComponent))); } } }
/// <summary> /// Handle request received from remote clients /// </summary> /// <param name="msgStream">The byte stream that represents a request message</param> /// <param name="sender">The remote sender endpoint</param> protected virtual void ProcessRequestMessageReceived(byte[] msgStream, ref EndPoint sender) { CoAPRequest coapReq = new CoAPRequest(); IPEndPoint remoteSender = (IPEndPoint)sender; try { coapReq.FromByteStream(msgStream); coapReq.RemoteSender = new IPEndPoint(remoteSender.Address, remoteSender.Port); //Setup who sent this message //setup the default values of host and port if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_HOST)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_HOST, AbstractByteUtils.StringToByteUTF8(this._host)); } if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_PORT)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_PORT, AbstractByteUtils.GetBytes((UInt16)this._port)); } if (coapReq.MessageType.Value == CoAPMessageType.CON && coapReq.Code.Value == CoAPMessageCode.EMPTY) { //This is a PING..send a RST this.RespondToPing(coapReq); } else { this.HandleRequestReceived(coapReq);//Other messages, let program handle it } } catch { ;//TOCHECK::Do nothing, we do not want to crash the server just because we //could not process one received message...will check later how to improve this } }
/// <summary> /// This thread continuously looks for messages on the socket /// Once available, it will post them for handling downstream /// </summary> protected void ProcessReceivedMessages() { byte[] buffer = null; int maxSize = AbstractNetworkUtils.GetMaxMessageSize(); while (!this._isDone) { Thread.Sleep(1000); try { if (this._clientSocket.Available >= 4 /*Min size of CoAP block*/) { buffer = new byte[maxSize * 2]; int bytesRead = this._clientSocket.Receive(buffer); byte[] udpMsg = new byte[bytesRead]; Array.Copy(buffer, udpMsg, bytesRead); byte mType = AbstractCoAPMessage.PeekMessageType(udpMsg); if ((mType == CoAPMessageType.CON || mType == CoAPMessageType.NON) && AbstractCoAPMessage.PeekIfMessageCodeIsRequestCode(udpMsg)) { //This is a request CoAPRequest coapReq = new CoAPRequest(); coapReq.FromByteStream(udpMsg); coapReq.RemoteSender = this._remoteEP;//Setup who sent this message string uriHost = ((IPEndPoint)this._remoteEP).Address.ToString(); UInt16 uriPort = (UInt16)((IPEndPoint)this._remoteEP).Port; //setup the default values of host and port //setup the default values of host and port if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_HOST)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_HOST, AbstractByteUtils.StringToByteUTF8(uriHost)); } if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_PORT)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_PORT, AbstractByteUtils.GetBytes(uriPort)); } this.HandleRequestReceived(coapReq); } else { //This is a response CoAPResponse coapResp = new CoAPResponse(); coapResp.FromByteStream(udpMsg); coapResp.RemoteSender = this._remoteEP;//Setup who sent this message //Remove the waiting confirmable message from the timeout queue if (coapResp.MessageType.Value == CoAPMessageType.ACK || coapResp.MessageType.Value == CoAPMessageType.RST) { this._msgPendingAckQ.RemoveFromWaitQ(coapResp.ID.Value); } this.HandleResponseReceived(coapResp); } } } catch (SocketException se) { //Close this client connection this._isDone = true; this.HandleError(se, null); } catch (ArgumentNullException argEx) { this.HandleError(argEx, null); } catch (ArgumentException argEx) { this.HandleError(argEx, null); } catch (CoAPFormatException fEx) { //Invalid message.. this.HandleError(fEx, null); } } }
/// <summary> /// Set the request URL /// </summary> /// <param name="coapURL">The coap URL associated with the request</param> public void SetURL(string coapURL) { if (coapURL == null || coapURL.Trim().Length == 0) { throw new ArgumentNullException("Invalid CoAP URL"); } coapURL = coapURL.Trim(); /* * The URI object provided by NETMF does not work if the scheme is not http/https * Therefore, after checking for the scheme, we replace it with http * and then use the Uri class for other items */ string scheme = AbstractURIUtils.GetUriScheme(coapURL); if (scheme == null || (scheme.Trim().ToLower() != "coap" && scheme.Trim().ToLower() != "coaps")) { throw new ArgumentException("Invalid CoAP URL"); } if (scheme.Trim().ToLower() == "coaps") { this.IsSecure = true; } if (coapURL.IndexOf("#") >= 0) { throw new ArgumentException("Fragments not allowed in CoAP URL"); } //Add these items as option //The host this.Options.AddOption(CoAPHeaderOption.URI_HOST, AbstractByteUtils.StringToByteUTF8(AbstractURIUtils.GetUriHost(coapURL))); //The port this.Options.AddOption(CoAPHeaderOption.URI_PORT, AbstractByteUtils.GetBytes((UInt16)AbstractURIUtils.GetUriPort(coapURL))); //Path components string[] segments = AbstractURIUtils.GetUriSegments(coapURL); if (segments != null && segments.Length > 0) { foreach (string segment in segments) { if (segment.Trim().Length == 0) { continue; } this.Options.AddOption(CoAPHeaderOption.URI_PATH, AbstractByteUtils.StringToByteUTF8(AbstractURIUtils.UrlDecode(segment))); } } //Query string[] qParams = AbstractURIUtils.GetQueryParameters(coapURL); if (qParams != null && qParams.Length > 0) { foreach (string queryComponent in qParams) { if (queryComponent.Trim().Length == 0) { continue; } this.Options.AddOption(CoAPHeaderOption.URI_QUERY, AbstractByteUtils.StringToByteUTF8(AbstractURIUtils.UrlDecode(queryComponent))); } } }
/// <summary> /// Receive a message from the server. This will block if there /// are no messages. Please note, you must handle all errors (except timeout) /// and no error is raised. /// </summary> /// <param name="rxTimeoutMillis"> /// The timeout value in milliseconds.The default value is 0, which indicates an infinite time-out period. /// Specifying -1 also indicates an infinite time-out period /// </param> /// <param name="timedOut">Is set to true on timeout</param> /// <returns>An instance of AbstractCoAPMessage on success, else null on error/timeout</returns> public AbstractCoAPMessage ReceiveMessage(int rxTimeoutMillis, ref bool timedOut) { byte[] buffer = null; int maxSize = AbstractNetworkUtils.GetMaxMessageSize(); CoAPRequest coapReq = null; CoAPResponse coapResp = null; try { this._clientSocket.ReceiveTimeout = rxTimeoutMillis; buffer = new byte[maxSize * 2]; int bytesRead = this._clientSocket.Receive(buffer); byte[] udpMsg = new byte[bytesRead]; Array.Copy(buffer, udpMsg, bytesRead); byte mType = AbstractCoAPMessage.PeekMessageType(udpMsg); if ((mType == CoAPMessageType.CON || mType == CoAPMessageType.NON) && AbstractCoAPMessage.PeekIfMessageCodeIsRequestCode(udpMsg)) { //This is a request coapReq = new CoAPRequest(); coapReq.FromByteStream(udpMsg); coapReq.RemoteSender = this._remoteEP;//Setup who sent this message string uriHost = ((IPEndPoint)this._remoteEP).Address.ToString(); UInt16 uriPort = (UInt16)((IPEndPoint)this._remoteEP).Port; //setup the default values of host and port //setup the default values of host and port if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_HOST)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_HOST, AbstractByteUtils.StringToByteUTF8(uriHost)); } if (!coapReq.Options.HasOption(CoAPHeaderOption.URI_PORT)) { coapReq.Options.AddOption(CoAPHeaderOption.URI_PORT, AbstractByteUtils.GetBytes(uriPort)); } return(coapReq); } else { //This is a response coapResp = new CoAPResponse(); coapResp.FromByteStream(udpMsg); coapResp.RemoteSender = this._remoteEP;//Setup who sent this message return(coapResp); } } catch (SocketException se) { if (se.ErrorCode == (int)SocketError.TimedOut) { timedOut = true; } else { throw se; } } return(null); }