[嵌入式開發模塊]Coap開源庫libnyoci 使用詳解

這些天花了老長時間研究libnyoci這個庫了,主要是文檔不全,就給了幾個簡單的示例。只能依靠接口文件裏的那點註釋結合着示例程序翻着源碼一點點學習。這裏把學習的結果發出來給大家參考,希望能幫助大家節省些時間。

注:學習這篇之前如果能先學完CoAP協議定義(RFC7252)的話會很有幫助。

TODO:
學習並補全observe相關接口的使用
學習並補全noderouter插件相關知識


1 平臺移植

爲了使用libnyoci,必須先根據自己的平臺和網絡棧進行相應的移植和配置。
詳情請移步:
https://blog.csdn.net/lin_strong/article/details/96321759

2 libnyoci實例

libnyoci支持多實例,當然你其實可以通過設置將其設爲單例模式,當爲單例模式時,self參數失效,內部會自動設置爲唯一的那個單例。

所有帶有 nyoci_t self 參數的函數,就是讓你在self參數傳入你要操作的對應實例。

2.1 實例創建

不管是不是單例模式,使用libnyoci的第一步都是調用以下函數創建並初始化一個實例:

nyoci_t _CoapInstance;
_CoapInstance= nyoci_create();
if(!_CoapInstance){
  // error
}

如果是多例模式,則需保留引用,在每次調用有self參數的函數時傳入。
tip:建議單例模式時也這麼處理,保持程序一致性。

2.2 實例釋放

使用下面函數釋放一個實例,一般用不到:

nyoci_release(_CoapInstance);

3 網絡相關設置

當創建好實例後。在初始化好各項配置和回調函數之後(或之前,這無所謂),實際讓libnyoci開始幹活之前。你需要先保證網絡的暢通,並設置網絡相關參數,具體調用的設置函數和平臺有關,在我的環境中我只調用了這兩句:

nyoci_plat_set_socketnumber(_CoapInstance, COAP_SOCKET);
nyoci_plat_bind_to_port(_CoapInstance, NYOCI_SESSION_TYPE_UDP, COAP_DEFAULT_PORT);

4 驅動運行

爲了libnyoci能不斷運作,需要有進程不斷地調用nyoci_plat_process,在這個函數中
以下是在多Task環境中,單獨使用一個Task專門驅動libnyoci運行的主要語句:

  while (1) {
    MyOS_DlyHMSM(0, 0, 0, 30);
    nyoci_plat_process(_CoapInstance);
  }

裸奔環境中自己找機會不斷調用它。

注意,libnyoci雖然是異步的,但它的方法都是線程不安全的。

5 基本概念

會話分爲inbound和outbound部分,顧名思義,inbound部分對應外面傳進來的包,一般是請求包;outbound部分則是要發出去的包,可能是對請求的答覆,也可能是你要請求別的資源而主動使用的。每個實例對應inbound和outbound各有一個緩衝區。

在libnyoci.h中分別給了inbound和outbound各自的接口

6 處理進入的請求

6.1 設置回調handler

libnyoci服務器的處理請求的最基本模式就是通過設置handler函數,每當收到一個需要用戶處理的請求時,libnyoci就會回調這個handler。用戶在handler中進行必要的處理,可能會進行答覆,也可能直接丟棄包。

static nyoci_status_t request_handler(void* context){
  // 處理inbound包
}
……
nyoci_set_default_request_handler(_CoapInstance, &request_handler, NULL);

我們知道CoAP協議的應答有很多必要的步驟:設置一致的消息ID、Token;根據是CON還是NON請求設置對應的答覆類型等。這些在調用回調函數前,libnyoci就已經幫我們處理好了,不需要我們操心,我們要做的就是執行請求的內容,決定答覆碼,添加需要的選項以及payload,或者直接選擇不答覆。

如果不在handler中顯式的發送答覆(或者選擇直接放棄答覆)的話,libnyoci會根據返回的值自動生成對應的答覆,規則好像是對於CON則生成對應ACK答覆,對於NON則直接不答覆。所以,如果想要對NON也進行答覆的話,則需要顯式的發送答覆,對NON的答覆默認是NON類型。

接下來我們來講下request_handler中的主要處理步驟。

6.2 inbound處理

6.2.1 檢查URI路徑

每個資源都有自己的URI,首先我們重組出資源的URI,如果沒有對應資源的話就答覆經典的404。

按理來說應該是一個個Uri-Path選項去檢查,跟查找文件一樣去搜索。但是libnyoci提供了一個很方便的函數nyoci_inbound_get_path,於是我們可以寫成這樣:

  char buf[30];
  ……
  if(nyoci_inbound_get_path(buf, sizeof(buf), 0) != buf){
    // 路徑轉換失敗
    return NYOCI_STATUS_NOT_FOUND;
  }
  // 假設我們有個資源的路徑爲data,也可以是多層的如data/abc
  if(strcmp(buf, "data") != 0){
    // 沒有給定的資源
    return NYOCI_STATUS_NOT_FOUND;
  }

注意,那兩句

    return NYOCI_STATUS_NOT_FOUND;

也可以改成

    // 發送答覆包的簡易接口,第一個參數爲答覆碼,第二個參數爲內容,沒有的話就傳NULL
    return nyoci_outbound_quick_response(COAP_RESULT_404_NOT_FOUND, NULL);

這樣的效果是,對於CON消息類型的請求,兩句沒有差別。但對於NON消息類型,上面那句會不進行任何答覆,效果上就是忽略了這個請求;而顯式的答覆的話就會發送一個NON消息類型的獨立答覆。

像這樣:
後面對於答覆錯誤的寫法同理,不再贅述。

6.2.2 檢查是否支持請求的方法

請求的方法就是GET、POST、PUT或DELETE。這個是要根據資源的用途自己設計的。
比如如果只支持GET方法的話,可以這麼寫:

	if(nyoci_inbound_get_code() != COAP_METHOD_GET) {
		return NYOCI_STATUS_NOT_ALLOWED;
	}

通過返回NYOCI_STATUS_NOT_ALLOWED,如果是CON消息,libnyoci會自動爲你生成答覆碼爲4.05(Method Not Allowed)的ACK包進行答覆。

6.2.3 檢查選項

CoAP包中可能會帶有各種各樣的選項。實際上前面的路徑值就是從Uri-Path選項中恢復出來的
選項一定是按照編號從低到高排列的,有些選項可以出現多次(Repeatable),所以同一選項出現多次時一定是相鄰的。有些選項是關鍵選項,也就是說你要是不理解這個選項而他又存在的話應該要拒絕請求。當然實際怎麼處理各個選項是你代碼來決定的,你甚至可以忽略所有的選項。

下圖用於一覽幾乎所有的選項。
不同的資源對不同的請求方法可能有不同的選項處理要求。因此可能你需要做一個表,爲不同的 資源+方法 組合跳轉不同的handler。

可以使用nyoci_inbound_next_option和/或nyoci_inbound_peek_option來遍歷選項,它們的第一個參數(示例中爲value)返回指向選項值的指針,第二個參數(示例中爲value_len)返回選項值的長度;對於字符串類型的選項,value_len就是字符串的長度,value指向字符串的頭;對於無符號整型值的選項,我們可以通過coap_decode_uint32來獲得其值。

代碼示例:

  // 因爲前面轉換路徑時移動了選項指針,所以現在重置下
  nyoci_inbound_reset_next_option();
  
  { coap_option_key_t key;
		const uint8_t* value;
		coap_size_t value_len;
		uint32_t ct;
		// 掃描所有選項
		while ((key = nyoci_inbound_next_option(&value, &value_len)) != COAP_OPTION_INVALID) {
		  switch(key){
		    case COAP_OPTION_URI_PATH:   // 已經處理過了,跳過
		    // 暫時跳過,因爲我們不使用虛擬主機
		    case COAP_OPTION_URI_HOST:
		    case COAP_OPTION_URI_PORT:
		    break;
		    case COAP_OPTION_CONTENT_TYPE:
		      // 如果比如POST或PUT支持多種格式的話,請求者可以通過這個選項告知內容的格式
			    printf("Content type: %s\r\n", coap_content_type_to_cstr(coap_decode_uint32(value, value_len)));
		    break;
		    case COAP_OPTION_URI_QUERY:
		      // 即URL中 ?XXXXX&XXXX 的XXXX部分內容,可以攜帶各種參數,按需自己解析
		      printf("Query: %.*s\r\n", value_len, value);
		    break;
		    case COAP_OPTION_ACCEPT:
		      ct = coap_decode_uint32(value, value_len);
		      // 如果無法支持要求的格式,答覆4.06
		      if(dontSupport(ct))
		        return nyoci_outbound_quick_response(COAP_RESULT_406_NOT_ACCEPTABLE);
		    break;
		    default:
		      // 如果不識得的選項是關鍵選項,Bad option錯誤。
			  if(COAP_OPTION_IS_CRITICAL(key)) {
			    return NYOCI_STATUS_BAD_OPTION;
			  }
			break;
      }
    }
  }

6.2.4 檢查payload

可能GET請求不會攜帶payload。但PUT和POST請求一般會隨帶一個payload,用於提交或更新。這時我們就需要提取其中的內容進行處理,示例如下:

  {
		const char* content_ptr;
		coap_size_t content_len;
		content_ptr = nyoci_inbound_get_content_ptr();
		content_len = nyoci_inbound_get_content_len();
		
		printf("Payload: %.*s\r\n", content_len, content_ptr);
  }

而獲得payload格式的方法已經在上一節說明了。具體怎麼處理那是應用自己定義的事情。

6.3 outbound答覆

檢查完請求包後,如果沒有發現什麼問題,且需要進行響應的話那就要考慮怎麼構建答覆包的問題了,我們通過outbound的各種接口來構建答覆。

對於最簡單的只答復一個答覆碼的答覆包,就調用之前提到的那個nyoci_outbound_quick_response接口就行了。

對於稍微複雜點的答覆,我們就需要按照標準步驟構建答覆包。

6.3.1 開始答覆及設置答覆碼

首先,爲了開始一個答覆,先要調用nyoci_outbound_begin_response表明開始答覆,接口中直接傳入答覆碼:

比如要答覆一個帶有內容的答覆的話,就會像這樣:

  nyoci_outbound_begin_response(COAP_RESULT_205_CONTENT);

如果需要獨立設置/修改答覆碼,可以調用

nyoci_status_t nyoci_outbound_set_code(coap_code_t code);

6.3.2 添加選項

設置好答覆碼後,一般就要添加選項了,libnyoci提供了以下兩個接口來給outbound包添加選項

//!	往outbound包中增加給定的選項
/*! 如果`value`是一個C字符串,簡單的傳遞NYOCI_CSTR_LEN爲`len`的值以避免調用`strlen()`
**  注意: 按數字順序添加選項比隨機順序添加選項快的多。只要做得到,就按遞增的順序添加選項。*/
nyoci_status_t nyoci_outbound_add_option(
	coap_option_key_t key,
	const char* value,
	coap_size_t len
);

//!	往outbound包中增加給定的一個值爲無符號整型的選項
nyoci_status_t nyoci_outbound_add_option_uint(coap_option_key_t key, uint32_t value);

至於添加什麼選項就看自己的需要了,比如我們一般要添加後面payload的文本類型:

  nyoci_outbound_add_option_uint(
    COAP_OPTION_CONTENT_TYPE,
    COAP_CONTENT_TYPE_TEXT_PLAIN
  );

6.3.3 添加內容

在添加完選項後(必須要添加完後),如果還需要添加payload的話,可以按如下操作(假設要答覆的內容在content_ptr指向的長度爲content_len的緩衝區中):

  {
    char *p;
    coap_size_t max_len;
    p = nyoci_outbound_get_content_ptr(&max_len);
    
    if(content_len > max_len){
      // 放不下時,對應的處理
    }
    
    // 往payload中寫入內容
    memcpy(p, content_ptr, content_len);
    // 表明payload長度
    nyoci_outbound_set_content_len(content_len);
  }

或者用如下接口在尾部添加內容,這時不需要set_content_len,其會自動更新:

  nyoci_outbound_append_content(content_ptr, content_len);

6.3.4 發送答覆

很簡單,一般也就是最後一句,然後就可以結束回調函數了,所以return它的結果:

  return nyoci_outbound_send();

我簡單的把POST過來的內容複製了一遍進行答覆,於是就得到了如下結果:

6.4 資源發現接口

CoAP協議規定.well-known/core爲衆知的資源發現接口。客戶端可以通過GET這個URI的資源,獲得服務器擁有的資源的列表。

列表的格式由RFC6690定義,這是我的翻譯:
https://blog.csdn.net/lin_strong/article/details/103407291

一般會專門實現這個接口,使得客戶端能夠知道交互接口,比如我可以在handle中這樣子簡單實現它:


  if(nyoci_inbound_get_path(buf, sizeof(buf), 0) != buf){
    // 路徑轉換失敗
    return NYOCI_STATUS_NOT_FOUND;
  }
  
  if(strcmp(buf, ".well-known/core") == 0){
    if (nyoci_inbound_get_code() != COAP_METHOD_GET)
      return NYOCI_STATUS_NOT_ALLOWED;
    nyoci_outbound_begin_response(COAP_RESULT_205_CONTENT);
    nyoci_outbound_add_option_uint(COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_LINK_FORMAT);
    nyoci_outbound_append_cstr("</data/temp>;rt=\"temperature-c\";if=\"sensor\", </data/light>;rt=\"light-lux\";if=\"sensor\"");
    return nyoci_outbound_send();
  }else if……
  else{
    // 沒有給定的資源
    return NYOCI_STATUS_NOT_FOUND;
  }

這樣上位機就能通過返回的CoRE資源自動生成資源列表,選取需要的資源進行交互等。

7 請求別處的資源

我們以Get請求爲例,示例如何使用libnyoci請求別處的資源。

7.1 會話

請求別處的資源時,libnyoci使用會話的概念,主要接口在nyoci-transaction.h中。

7.1.1 創建會話

創建會話的接口如下:

//!	初始化給定的transaction對象。
/* transaction      如果爲NULL,則會自動分配一個對象給你,否則,直接初始化你給的這個
** flags            見NYOCI_TRANSACTION_XXXX,位模式
** requestResend    在這個回調函數中進行outbound發送請求
** responseHandler  在這個回調函數中答覆
** context          會傳遞給兩個回調函數的參數
*/
NYOCI_API_EXTERN nyoci_transaction_t nyoci_transaction_init(
	nyoci_transaction_t transaction,
	int	flags,
	nyoci_inbound_resend_func requestResend,
	nyoci_response_handler_func responseHandler,
	void* context
);

我們知道,一個完整的請求需要構造請求包並進行發送,可能還需要多次重試,然後得到答覆後還需要處理答覆。由於CoAP的包構造十分複雜,libnyoci將一個會話的這兩個步驟抽象到兩個回調函數中。在requestResend中你需要構造outbound包並進行發送,libnyoci每次想要重發請求時都會調用這個回調函數,因此你可以在其中比如做個重試計數啥的功能;而收到答覆或者發生一些事件時,nyoci實例會回調responseHandler以要求你進行處理。

代碼示例:


static struct nyoci_transaction_s transaction;

static nyoci_status_t resend_get_request(void* context);
static nyoci_status_t response_handler(int statuscode, void* context);
……

  nyoci_transaction_init(
    &transaction,
    NYOCI_TRANSACTION_ALWAYS_INVALIDATE,
    resend_request,
    response_handler,
    NULL
  );

7.1.2 啓動會話

調用nyoci_transaction_begin成功後,這個會話就會受到nyoci實例的調度,開始工作:

  nyoci_status_t status = 0;
  ……
  status = nyoci_transaction_begin(_CoapInstance, &transaction, _timeout);
  if(status != NYOCI_STATUS_OK) {
    // error
    printf("nyoci_begin_transaction_old() returned %d(%s).\n",status,nyoci_status_to_cstr(status));
  }

_timeout指定了會話的超時時間,按需要設置。

7.1.3 終止會話

如果要終止進行中的會話,可以調用以下接口:

NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_end(
	nyoci_t self,
	nyoci_transaction_t transaction
);

7.2 重傳請求-回調函數

剛剛說了,在requestResend中你需要構造outbound包並進行發送。現在我們來具體說說整個步驟。有很多步驟和前一章中outbound處理的內容相似,畢竟它就是一個outbound。

我們的重傳請求-回調函數的基本框架長這個亞子:

static nyoci_status_t resend_request(void* context) {
  nyoci_status_t status = NYOCI_STATUS_OK;
  ……
bail:
  return status;
}

context參數傳進來的值就是你init中的最後一個參數。

7.2.1 開始構造請求

這裏我們用的接口和答覆請求時的略有不同,使用的是nyoci_outbound_begin,而不是nyoci_outbound_begin_response,BTW,後者其實只是在前者外面再加了層殼:

  // 這裏示例設置outbound包爲GET請求,CON消息;實際按自己需求設置
  status = nyoci_outbound_begin(nyoci_get_current_instance(),COAP_METHOD_GET, COAP_TRANS_TYPE_CONFIRMABLE);
  require_noerr(status,bail);  // nyoci自帶的語法糖,相當於 if(status != NYOCI_STATUS_OK) goto bail;

7.2.2 設置目標URI

首先我們需要調用如下接口:

  status = nyoci_outbound_set_uri(URI, NYOCI_MSG_SKIP_AUTHORITY);
  require_noerr(status,bail);

這個接口會根據URI中解析出來的信息自動設置採用的通訊方案、目標主機和端口、資源路徑、query參數

這個是UDP上coap的URI格式:

這個是DTLS的coap的URI格式

以及
coap+tcp://……是使用TCP
coaps+tcp://……是使用TLS

這個前綴(不含://)叫scheme,指定了通訊的協議。如果不含它的話,默認使用UDP。
當然,使用的通訊協議需要平臺的支持纔行,需要你在移植的時候在nyoci_plat_set_session_type中將其實現。

一般要指定主機部分,port部分如果不指定的話則會使用協議默認端口。
如果flag沒設置NYOCI_MSG_SKIP_AUTHORITY的話,接口會把解析出來的主機和端口號設置爲option,我覺得這個flag可以傳。
host和port會使用nyoci_plat_set_remote_hostname_and_port進行設置,而host又會依賴於nyoci_plat_lookup_hostname進行解析,這些都是平臺移植文件中的函數,需要你在移植時進行實現。

如果不想要nyoci_outbound_set_uri調用nyoci_plat_set_remote_hostname_and_port進行設置目標IP和端口的話,可以傳遞flag—NYOCI_MSG_SKIP_DESTADDR給它,就能跳過這一步。
但是隨後一定要記得自己設置目標端口和IP。

設下的path和query則一定會按照正常的轉換規則轉換爲option。雖然定義了NYOCI_MSG_SKIP_PATH和NYOCI_MSG_SKIP_QUERY這兩個flag。但是看代碼,實際上並沒有使用它們,當然如果你有需要也可以自己加進去。

7.2.3 添加其他選項

然而有一些東西是無法用URI來標識的,因此還需要自己再根據需要添加其他option,比如一般會加上content-type option。

  status = nyoci_outbound_add_option_uint(COAP_OPTION_ACCEPT, COAP_CONTENT_TYPE_TEXT_PLAIN);
  require_noerr(status,bail);

要注意的是,當然也可以不使用上述set_uri接口,完全手動構造好整個包並設置好目標IP之類的東西。

7.2.4 發送請求包

最後調用一下send就好。

  status = nyoci_outbound_send();
  
  switch (status) {
    case NYOCI_STATUS_OK:
    case NYOCI_STATUS_WAIT_FOR_SESSION:
    case NYOCI_STATUS_WAIT_FOR_DNS:
      break;
    default:
      check_noerr(status);
      printf("nyoci_outbound_send() returned error %d(%s).\n",
        status,
        nyoci_status_to_cstr(status));
      break;
  }

7.3 答覆-回調函數

當收到發出的請求的對應答覆包時,或者在異步處理中發生任何錯誤時,responsehandler會被調用,你需要在其中進行對應處理,提取你需要的信息或處理故障。答覆包是個inbound,所以當收到答覆包時,和前面的inbound處理部分幾乎一樣,因此我們不再贅述具體的inbound處理過程。

答覆-回調函數的框架是這樣子的:

static nyoci_status_t response_handler(int statuscode, void* context) {
  const char* content = (const char*)nyoci_inbound_get_content_ptr();
  coap_size_t content_length = nyoci_inbound_get_content_len();
  ……
  return NYOCI_STATUS_OK;
}

context自然傳遞的就是init會話時傳遞的那個參數,而返回值其實並沒有什麼意義。我們需要理解下statuscode的處理方式。

對於有答覆的請求,如果收到答覆了,libnyoci就會使用那個答覆的答覆碼來調用statuscode,值自然是大於0的,比如我要是GET一個資源成功的話,答覆碼就是COAP_RESULT_205_CONTENT。有些時候會有多次答覆,比如觀測一個資源的時候,這個時候這個答覆回調函數就可能會被多次調用。

如果發生錯誤了,則會用對應的狀態碼(小於0)調用回調函數,這裏整理幾個常見的:
NYOCI_STATUS_TIMEOUT 當超過了你給定的超時時間還沒有收到答覆時(針對CON)
在內部調用你nyoci_plat_outbound_finish進行發送時,如返回的不是NYOCI_STATUS_OK,則會用返回的那個值調用回調函數
NYOCI_STATUS_TRANSACTION_INVALIDATED 一個會話將要失效。
……

注意:除非你在初始化會話時設置了NYOCI_TRANSACTION_ALWAYS_INVALIDATE,不然,對於那種一次性的會話(一個普通的請求,或者發生了什麼異常而導致結束),答覆-回調函數只會被只用對應的答覆碼或者錯誤碼調用一次,然後就直接結束了。並不會再使用NYOCI_STATUS_TRANSACTION_INVALIDATED調用答覆-回調函數一次。對於那種長期的會話,比如觀測一個資源的會話,不設置這個flag可能會導致難以得知會話在什麼時候終止了。 所以建議乾脆就都設置這個flag,然後就通過 答覆-回調函數被使用NYOCI_STATUS_TRANSACTION_INVALIDATED調用,作爲會話結束的標誌就行了。

GET請求的一個答覆-回調函數簡單示例如下:

static nyoci_status_t response_handler(int statuscode, void* context) {
  const char* content = (const char*)nyoci_inbound_get_content_ptr();
  coap_size_t content_length = nyoci_inbound_get_content_len();
  const char* desc = "";
  if (statuscode == NYOCI_STATUS_TRANSACTION_INVALIDATED) {
    printf("Trans Invalidated\r\n");
    _tranStatus = TRANSACTION_UNREADY;
  } else if (statuscode == COAP_RESULT_205_CONTENT) {
    printf("Got content: %.*s\r\n", content_length, content);
  } else if (statuscode == NYOCI_STATUS_TIMEOUT){
    printf("request timeout\r\n");
  }else {
    desc = (statuscode > 0)?coap_code_to_cstr(statuscode): nyoci_status_to_cstr(statuscode);
    printf("ERROR: Got unexpected status code %d (%s)\r\n", statuscode, desc);
  }
  return NYOCI_STATUS_OK;
}

我們使用這個示例來請求上一章中寫的CaAP服務器,成功時結果如下:

得不到答覆而超時的結果如下:

好的,主要內容到這就講解完了,還有很多其他東西可能後面會慢慢補全。
有什麼建議或意見歡迎留言!

附錄:常用接口速查

只給出自己認爲常用的,而且自己理解了的接口

libnyoci.h

libnyoci.h裏頭定義了libnyoci最宏觀的相關接口,包括實例的創建和釋放、回調函數設置、inbound和outbound緩衝區的相關接口。

// 創建並分配一個libnyoci實例
nyoci_t nyoci_create(void);
// 釋放一個libnyoci實例,關閉所有端口並結束所有的會話
void nyoci_release(nyoci_t self);

// 獲取及設置對當前實例的引用,內部使用
nyoci_t nyoci_get_current_instance(void);
void nyoci_set_current_instance(nyoci_t x);

// 設置默認的請求handler
// 只要實例收到了一個非代理相關的請求,這個handler就會被回調。
// 如果收到的是CON消息並且你在回調函數中沒有發出答覆消息,就會自動基於返回值生成併發出答覆包。
// context會在回調時被作爲參數傳遞
void nyoci_set_default_request_handler(
	nyoci_t self,
	nyoci_request_handler_func request_handler,
	void* context
);

// 得知在下次調用nyoci_plat_process()前最多你還有多久時間
nyoci_cms_t nyoci_get_timeout(nyoci_t self);

// 以下函數只可以用於回調函數中來檢查當前inbound包,在回調函數外用它們會導致運行時錯誤

//!	返回指向當前inbound CoAP包的開頭的指針
NYOCI_API_EXTERN const struct coap_header_s* nyoci_inbound_get_packet(void);

//!	返回inbound包的長度
NYOCI_API_EXTERN coap_size_t nyoci_inbound_get_packet_length(void);

//! 返回inbound包的碼
#define nyoci_inbound_get_code()		(nyoci_inbound_get_packet()->code)

//! 返回inbound包的消息ID
#define nyoci_inbound_get_msg_id()	(nyoci_inbound_get_packet()->msg_id)

#define NYOCI_INBOUND_FLAG_DUPE           (1<<0)
#define NYOCI_INBOUND_FLAG_MULTICAST      (1<<1)
#define NYOCI_INBOUND_FLAG_FAKE           (1<<2)
#define NYOCI_INBOUND_FLAG_HAS_OBSERVE    (1<<3)
#define NYOCI_INBOUND_FLAG_LOCAL          (1<<4)

//! 返回一個flags字節,標識了inbound包的狀態,位模式,見(NYOCI_INBOUND_FLAG_XXXXX)
NYOCI_API_EXTERN uint16_t nyoci_inbound_get_flags(void);

//! 返回是否LibNyoci認爲當前inbound包是一個欺騙包
#define nyoci_inbound_is_dupe() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_DUPE)==NYOCI_INBOUND_FLAG_DUPE)

//! 返回是否LibNyoci認爲當前inbound包是假的(用來觸發對訂閱者的消息推送) 
#define nyoci_inbound_is_fake() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_FAKE)==NYOCI_INBOUND_FLAG_FAKE)

//! 返回是否當前inbound包是一個多播包
#define nyoci_inbound_is_multicast() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_MULTICAST)==NYOCI_INBOUND_FLAG_MULTICAST)

//! 返回是否當前inbound包有一個observe選項
#define nyoci_inbound_has_observe() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_HAS_OBSERVE)==NYOCI_INBOUND_FLAG_HAS_OBSERVE)

//! 返回是否LibNyoci認爲當前inbound包是本地的
#define nyoci_inbound_is_local() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_LOCAL)==NYOCI_INBOUND_FLAG_LOCAL)

//!	返回指向當前inbound包的內容開頭的指針,保證是以NULL終止的
NYOCI_API_EXTERN const char* nyoci_inbound_get_content_ptr(void);

//!	返回當前inbound包的內容的長度
NYOCI_API_EXTERN coap_size_t nyoci_inbound_get_content_len(void);

//!	返回當前inbound包observe頭部的值
NYOCI_API_EXTERN uint32_t nyoci_inbound_get_observe(void);

//!	返回當前inbound包的內容類型
NYOCI_API_EXTERN coap_content_type_t nyoci_inbound_get_content_type(void);

//! 提取頭部中下一個(或說當前指向的那個)選項的值和類型,並移動到下一個
//! ptr傳遞選項指針和返回新的選項指針,len返回選項的長度
NYOCI_API_EXTERN coap_option_key_t nyoci_inbound_next_option(const uint8_t** ptr, coap_size_t* len);

//! 提取頭部中下一個(或說當前指向的那個)選項的值和類型,但不移動到下一個
//! ptr傳遞選項指針和返回新的選項指針,len返回選項的長度
NYOCI_API_EXTERN coap_option_key_t nyoci_inbound_peek_option(const uint8_t** ptr, coap_size_t* len);

//!	重置選項指針爲最開始的那個
NYOCI_API_EXTERN void nyoci_inbound_reset_next_option(void);

//!	將當前選項的值與指定的key和C字符串表示的值進行對比,相等則返回true,否則返回false
NYOCI_API_EXTERN bool nyoci_inbound_option_strequal(coap_option_key_t key, const char* str);

#define nyoci_inbound_option_strequal_const(key,const_str)	\
	nyoci_inbound_option_strequal(key,const_str)

#define NYOCI_GET_PATH_REMAINING			(1<<0)     // 翻譯當前餘下部分(因爲選項指針可能不在頭部),否則重新開始
#define NYOCI_GET_PATH_LEADING_SLASH		(1<<1)     // 在最開頭加上'/'
#define NYOCI_GET_PATH_INCLUDE_QUERY		(1<<2)     // 包含Query部分

//!	獲得inbound包的目標路徑的字符串表示(即請求URI)
//! where是緩衝區,maxlen爲緩衝區大小,flags見上
NYOCI_API_EXTERN char* nyoci_inbound_get_path(char* where, coap_size_t maxlen, uint8_t flags);

// 以下是拼湊outbound包的API,他們用於在回調函數中構建outbound CoAP消息
// 從回調函數外調用這些函數是運行時錯誤

//!	將outbound包設置爲請求
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_begin(
	nyoci_t self,
	coap_code_t code,
	coap_transaction_type_t tt
);

//! 設置outbound包爲對當前inbound包的答覆
//!	這個函數會自動地確保目標地址,消息ID和Token合理地設置
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_begin_response(coap_code_t code);

//!	修改當前outbound包的碼
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_code(coap_code_t code);

NYOCI_API_EXTERN nyoci_status_t nyoci_set_remote_sockaddr_from_host_and_port(const char* addr_str, uint16_t toport);


//!	往outbound包中增加給定的選項
/*! 如果`value`是一個C字符串,簡單的傳遞NYOCI_CSTR_LEN爲`len`的值以避免調用`strlen()`
**  注意: 按數字順序添加選項比隨機順序添加選項快的多。只要做得到,就按遞增的順序添加選項。*/
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_add_option(
	coap_option_key_t key,
	const char* value,
	coap_size_t len
);

//!	往outbound包中增加給定的一個值爲無符號整型的選項
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_add_option_uint(coap_option_key_t key, uint32_t value);

// 這4個flags用於nyoci_outbound_set_uri()
#define NYOCI_MSG_SKIP_DESTADDR		(1<<0)         // 不通過URI設置目標地址和端口
#define NYOCI_MSG_SKIP_AUTHORITY		(1<<1)     // 不設置Uri-Host和Uri-Port選項
#define NYOCI_MSG_SKIP_PATH			(1<<2)         // 不設置Uri-Path選項(暫時無用)
#define NYOCI_MSG_SKIP_QUERY			(1<<3)     // 不設置Uri-Query選項(暫時無用)

//!	設置outbound包的目標URI
//!	如果目標URL不能直接可達,並且定義了代理URL,這個函數會自動使用代理
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_uri(const char* uri, char flags);

//!	返回指向outbound包內容部分的指針
/*!	如果需要添加內容到outbound消息中,調用這個函數然後寫你的內容到返回的指針指向的位置。
**  不要寫比返回的`max_len`更多的字節。
**
**	在寫完你的數據後(或之前,這無關緊要),使用nyoci_outbound_set_content_len()來表明
**	內容的長度。
**
**	警告:在調用以下函數後絕對不能再添加任何選項,否則內容就沒有了。先添加完所有選項!*/
NYOCI_API_EXTERN char* nyoci_outbound_get_content_ptr(
	coap_size_t* max_len //^< [出參] 最大的內容長度
);

// 返回outbound緩衝區還剩多少字節空間
NYOCI_API_EXTERN coap_size_t nyoci_outbound_get_space_remaining(void);

//!	設置內容的實際長度。在nyoci_outbound_get_content_ptr()後調用它。
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_content_len(coap_size_t len);

//!	附加給定數據到包的末尾(也就是內容的末尾),這個函數會自動更新內容的長度
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_append_content(const char* value, coap_size_t len);

//!	附加給定C字符串到包的末尾,這個函數會自動更新內容的長度
#define nyoci_outbound_append_cstr(cstr)   nyoci_outbound_append_content(cstr, NYOCI_CSTR_LEN)

#if !NYOCI_AVOID_PRINTF
//!	按printf風格寫outbound消息的內容
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_append_content_formatted(const char* fmt, ...);

#define nyoci_outbound_append_content_formatted_const	\
	nyoci_outbound_append_content_formatted
#endif

//!	發送outbound包
/*!	在調用這個函數後,你這個回調函數的事情就做完了。後面就別調其他nyoci_outbound_*函數了。
**	每個回調函數中應該只發送一個outbound包 */
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_send(void);

//!	丟棄outbound包(不進行答覆)
NYOCI_API_EXTERN void nyoci_outbound_drop(void);

//!	重置outbound包
NYOCI_API_EXTERN void nyoci_outbound_reset(void);

//!	設置outbound包的消息id
//!	注意:大部分情況下消息ID是自動處理的,一般你用不到這個函數
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_msg_id(coap_msg_id_t tid);

//!	設置outbound包的token
//!	注意:大部分情況下token是自動處理的,一般你用不到這個函數
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_token(const uint8_t *token, uint8_t token_length);

//!	封裝了只設置答覆碼的答覆過程,通知錯誤時很有用。
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_quick_response(coap_code_t code, const char* body);

coap.h

coap.h中定義了CoAP協議相關的常量定義,包解析/編碼的相關函數以及一些常量轉換函數。它是很底層的函數,爲更高層的函數提供服務接口。


// COAP_RESULT_XXXX 與 HTTP_RESULT_CODE_XXXX間相互轉換用函數
NYOCI_INTERNAL_EXTERN uint16_t coap_to_http_code(uint8_t x);
NYOCI_INTERNAL_EXTERN uint8_t http_to_coap_code(uint16_t x);

// 解析CoAP包的一個選項
// buffer:指向當前要解析的選項的指針
// key   :返回解析出的選項編號
// value :返回指向解析出的選項的值的指針
// lenP  : 返回選項的值的長度
// 返回  :指向下一個選項的指針
NYOCI_API_EXTERN uint8_t* coap_decode_option(
	const uint8_t* buffer,
	coap_option_key_t* key,
	const uint8_t** value,
	coap_size_t* lenP
);

NYOCI_API_EXTERN uint8_t* coap_encode_option(
	uint8_t* buffer,
	coap_option_key_t prev_key,
	coap_option_key_t key,
	const uint8_t* value,
	coap_size_t len
);

//!	按正確的順序插入一個選項
/*!	返回: 插入選項的字節數
 */
NYOCI_API_EXTERN coap_size_t coap_insert_option(
	uint8_t* start_of_options,
	uint8_t* end_of_options,
	coap_option_key_t key,
	const uint8_t* value,
	coap_size_t len
);

// 返回選項值的格式是否是字符串
NYOCI_API_EXTERN bool coap_option_value_is_string(coap_option_key_t key);

NYOCI_API_EXTERN bool coap_option_strequal(const char* optionptr,const char* cstr);

#define coap_option_strequal_const(item,cstr)	coap_option_strequal(item,cstr)

// The following functions are not recommended on embedded platforms.

// 返回內容類型對應的常量字符串
NYOCI_API_EXTERN const char* coap_content_type_to_cstr(coap_content_type_t ct);
// 返回選項對應的常量字符串,不同的for_response只會使COAP_OPTION_AUTHENTICATE的對應字符串稍微有點不同
NYOCI_API_EXTERN const char* coap_option_key_to_cstr(coap_option_key_t key, bool for_response);
// coap_content_type_to_cstr的反向轉換函數
NYOCI_INTERNAL_EXTERN coap_content_type_t coap_content_type_from_cstr(const char* x);
// coap_option_key_to_cstr的反向轉換函數
NYOCI_INTERNAL_EXTERN coap_option_key_t coap_option_key_from_cstr(const char* key);
// 返回HTTP_RESULT_CODE_XXXX對應的字符串
NYOCI_INTERNAL_EXTERN const char* http_code_to_cstr(int x);
// 返回COAP_RESULT_XXXX對應的字符串
NYOCI_API_EXTERN const char* coap_code_to_cstr(int x);

// 檢查CoAP包的格式是否正確
NYOCI_INTERNAL_EXTERN bool coap_verify_packet(const char* packet,coap_size_t packet_size);
// CoAP包選項的值的格式爲無符號整型時,用於返回無符號整型的值
// value和value_len由coap_decode_option或nyoci_inbound_XXXX_option獲得
NYOCI_API_EXTERN uint32_t coap_decode_uint32(const uint8_t* value, uint8_t value_len);

nyoci-transaction.h

nyoci-transaction.h中定義了建立會話的相關接口,會話這個概念在libnyoci中用於請求別處CoAP服務器的服務,在發送請求前需要先初始化一個會話,會話可以是一次性的,也可以是持續觀測的。


//! 會話答覆的handler的定義,返回除NYOCI_STATUS_OK之外的幾乎所有值都將導致handler失效
typedef nyoci_status_t (*nyoci_response_handler_func)(
	int statuscode,
	void* context
);

struct nyoci_transaction_s {
#if NYOCI_TRANSACTIONS_USE_BTREE
	struct bt_item_s			bt_item;
#else
	struct ll_item_s			ll_item;
#endif

	nyoci_inbound_resend_func	resendCallback;
	nyoci_response_handler_func	callback;
	void*						context;

	// 這個expiration(有效期)字段在會話是或不是可觀測會話的時候有不同的含義。
	// 如果是不可觀測的,有效期就是值會話多久後將超時的時間。
	// 如果是可觀測的,就是超過了最大到期時間,需要重啓觀測。
	nyoci_timestamp_t			expiration;
	struct nyoci_timer_s			timer;

	coap_msg_id_t				token;
	coap_msg_id_t				msg_id;
	nyoci_sockaddr_t				sockaddr_remote;

#if NYOCI_CONF_TRANS_ENABLE_OBSERVING
	uint32_t					last_observe;
#endif

#if NYOCI_CONF_TRANS_ENABLE_BLOCK2
	uint32_t					next_block2;
#endif

	coap_code_t					sent_code;

	uint8_t						flags;
	uint8_t						attemptCount:4, maxAttempts:4,
								waiting_for_async_response:1,
								should_dealloc:1,
								active:1,
								needs_to_close_observe:1,
								multicast:1;
};

typedef struct nyoci_transaction_s* nyoci_transaction_t;

enum {
	//! 當要失效會話時,一定要用NYOCI_STATUS_INVALIDATE調用回調函數
	/*! 在一般的單播會話中,回調函數總是隻會被調用一次,所以並不存在
	 *  不清楚會話是否已經被失效了的情況。然而,有一些特定的會話會多次
	 *  調用回調函數,所以有可能並不清楚會話是否已經被失效了。
	 *  如果用了這個flag,當會話要失效時,會話回調函數總是會被使用
	 *  `NYOCI_STATUS_INVALIDATE`調用。*/
	NYOCI_TRANSACTION_ALWAYS_INVALIDATE = (1 << 0),

	NYOCI_TRANSACTION_OBSERVE = (1 << 1),

	NYOCI_TRANSACTION_KEEPALIVE = (1 << 2),		//!< 在觀測間發送keep-alive包

	NYOCI_TRANSACTION_NO_AUTO_END = (1 << 3),

	NYOCI_TRANSACTION_BURST_UNICAST = (1 << 4), //!< 在每次重傳時突然發送一堆單播包
	NYOCI_TRANSACTION_BURST_MULTICAST = (1 << 5), //!< 在每次重傳時突然發送一堆多播包

	NYOCI_TRANSACTION_BURST = NYOCI_TRANSACTION_BURST_UNICAST|NYOCI_TRANSACTION_BURST_MULTICAST, //!< 在每次重傳時突然發送一堆包

	NYOCI_TRANSACTION_DELAY_START = (1 << 8),
};

//!	初始化給定的transaction對象。
/* transaction      如果爲NULL,則會自動分配一個對象給你,否則,直接初始化你給的這個
** flags            見NYOCI_TRANSACTION_XXXX,位模式
** requestResend    在這個回調函數中進行outbound發送請求
** responseHandler  在這個回調函數中答覆
** context          會傳遞給兩個回調函數的參數
*/
NYOCI_API_EXTERN nyoci_transaction_t nyoci_transaction_init(
	nyoci_transaction_t transaction,
	int	flags,
	nyoci_inbound_resend_func requestResend,
	nyoci_response_handler_func responseHandler,
	void* context
);

NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_begin(
	nyoci_t self,
	nyoci_transaction_t transaction,
	nyoci_cms_t expiration
);

//!	結束會話
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_end(
	nyoci_t self,
	nyoci_transaction_t transaction
);

//!	強制會話重試/重傳
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_tickle(
	nyoci_t self,
	nyoci_transaction_t transaction
);

//!	修改制定會話的消息ID
/*!	不要修改token。這是用於當你想要使用同個會話對象來處理一系列
**	消息請求和答覆時。
**	比如,用於觀測以及分塊傳輸 */
NYOCI_API_EXTERN void nyoci_transaction_new_msg_id(
	nyoci_t			self,
	nyoci_transaction_t handler,
	coap_msg_id_t msg_id
);

nyoci-observable.h

創建和維護可觀測資源的相關接口




//! 可觀測上下文。
//!	這個結構體用於追蹤-誰在觀測這個資源。你想要多少就能有多少。
struct nyoci_observable_s {
#if !NYOCI_SINGLETON
	nyoci_t interface;
#endif

	// 應把以下成員當做私有的。

	int8_t first_observer; //!^ always +1, zero is end of list
	int8_t last_observer;  //!^ always +1, zero is end of list
};

//! 使用給定的可觀測內容觸發所有的觀測者的KEY
#define NYOCI_OBSERVABLE_BROADCAST_KEY		(0xFF)

typedef struct nyoci_observable_s *nyoci_observable_t;

//!	使一個資源可觀測的Hook
/*!	它必須在你“開始”構造outbound答覆消息後,而還沒有填充內容前被調用。
**	更準確地說就是:
**
**	 *在nyoci_outbound_begin()或nyoci_outbound_begin_response()後
**	 *在nyoci_outbound_get_content_ptr()、nyoci_outbound_append_content()、
**	   nyoci_outbound_send()等函數前。
**
**	你可以爲`key`選擇任何值,只要與你傳遞給nyoci_observable_trigger()來觸發更新的那個一致就行。
*/
NYOCI_API_EXTERN nyoci_status_t nyoci_observable_update(
	nyoci_observable_t context, //!< [入參] 指向可觀測上下文的指針
	uint8_t key		//!< [入參] 這個資源的Key(比如與在trigger中使用的一致)
);

#define NYOCI_OBS_TRIGGER_FLAG_NO_INCREMENT    (1<<0)
#define NYOCI_OBS_TRIGGER_FLAG_FORCE_CON       (1<<1)

//!	觸發一個可觀測資源發送更新給它的觀測者們。
/*!
**	你可以使用NYOCI_OBSERVABLE_BROADCAST_KEY以觸發與這個可觀測上下文相關的所有資源的更新。
*/
NYOCI_API_EXTERN nyoci_status_t nyoci_observable_trigger(
	nyoci_observable_t context, //!< [入參] 指向可觀測上下文的指針
	uint8_t key,	//!< [入參] 這個資源的Key(比如與在update中使用的一致)
	uint8_t flags	//!< [入參] 標誌位
);

//!	讓所有的可觀測資源發送一個CON更新給它們的所有觀測者。
/*!
**	偶爾使用它來清理下線的觀測者非常有用,特別是當你的可觀測資源更新地很不頻繁時。
**	這個功能被拆分成獨立地函數,而不是搞成自動地,這樣你就可以在適當的時機調用它,
**	比如在定時喚醒時。
*/
NYOCI_API_EXTERN void nyoci_refresh_observers(nyoci_t interface, uint8_t flags);

//! 返回活躍的觀測者的數量
NYOCI_API_EXTERN int nyoci_count_observers(nyoci_t interface);

//!	返回指定資源和key的觀測值個數
/*!
**	你可以給參數key傳遞NYOCI_OBSERVABLE_BROADCAST_KEY來獲得與這個上下文相關的觀測者的個數。
*/
NYOCI_API_EXTERN int nyoci_observable_observer_count(
	nyoci_observable_t context, //!< [入參] 指向可觀測上下文的指針
	uint8_t key	//!< [入參] 這個資源的Key(比如與在update中使用的一致)
);

//!	移除指定資源和key的所有觀測者
/*!
**	你可以給參數key傳遞NYOCI_OBSERVABLE_BROADCAST_KEY來清除與這個上下文相關的所有觀測者。
*/
NYOCI_API_EXTERN int nyoci_observable_clear(
	nyoci_observable_t context, //!< [入參] 指向可觀測上下文的指針
	uint8_t key	//!< [入參] 這個資源的Key(比如與在update中使用的一致)
);

url-helpers.h

url輔助函數主要負責url字符串的編碼和解碼,url在傳輸時需要把那些非ASCII字符進行百分比編碼,收到後可能顯示前會進行解碼,這個模塊主要就負責這個事。


#define URL_HELPERS_MAX_URL_COMPONENTS      (15)
#define MAX_URL_SIZE        (256)

/*!	對給定字符串執行URL編碼
**	返回:被編碼的字符串的字節數
*/
NYOCI_INTERNAL_EXTERN size_t url_encode_cstr(
	char *dest,				//!< [入參] 指向目標C字符串的緩衝區的指針
	const char* src,		//!< [入參] 源字符串,必須是NULL結尾的。
	size_t dest_max_size    //!< [入參] 目標緩衝區的大小
);

//! 解碼字符串
NYOCI_INTERNAL_EXTERN size_t url_decode_str(
	char *dest,
	size_t dest_max_size,
	const char* src,		//!< 長度由 `src_len`確定。
	size_t src_len
);

/*!	對給定字符串執行URL解碼
**	返回:被解碼的字符串的字節數
*/
NYOCI_INTERNAL_EXTERN size_t url_decode_cstr(
	char *dest,
	const char* src,		//!< 源字符串,必須是NULL結尾的。
	size_t dest_max_size
);

// 原地解碼字符串(所以你得保證這個字符串是可修改的)
NYOCI_INTERNAL_EXTERN void url_decode_cstr_inplace(char *str);

// 將字符串用雙引號引起來
NYOCI_INTERNAL_EXTERN size_t quoted_cstr(
	char *dest,
	const char* src,		//!< 源字符串,必須是NULL結尾的。
	size_t dest_max_size
);

NYOCI_INTERNAL_EXTERN bool url_is_absolute(const char* url);

NYOCI_INTERNAL_EXTERN bool url_is_root(const char* url);
// 返回字符串中是否包含':'
NYOCI_INTERNAL_EXTERN bool string_contains_colons(const char* str);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章