@@ -93,6 +93,7 @@ struct coap_request {
9393 * each subsequent block request to the host's pull rate.
9494 */
9595 bool manual_rx ; /* true: manual pull mode enabled */
96+ bool hex_rx ; /* true: deliver response payload as ASCII hex string */
9697 uint8_t * rx_buf ; /* Block-sized buffer (CONFIG_COAP_CLIENT_BLOCK_SIZE) */
9798 size_t rx_buf_filled ; /* Bytes currently in rx_buf[] */
9899 struct k_sem rx_consumed ; /* Given by AT#XCOAPCDATA; taken by coap_callback */
@@ -144,6 +145,21 @@ static void coap_send_status(struct coap_request *req)
144145 (int )req -> bytes_sent );
145146}
146147
148+ static void coap_data_send_hex (struct modem_pipe * pipe , const uint8_t * buf , size_t len )
149+ {
150+ char hex_buf [257 ];
151+ size_t chunk = (sizeof (hex_buf ) - 1 ) / 2 ;
152+ size_t done = 0 ;
153+
154+ while (done < len ) {
155+ size_t n = MIN (chunk , len - done );
156+ size_t sz = bin2hex (buf + done , n , hex_buf , sizeof (hex_buf ));
157+
158+ data_send (pipe , hex_buf , sz );
159+ done += n ;
160+ }
161+ }
162+
147163static void coap_send_data (struct coap_request * req , const uint8_t * payload , size_t payload_len )
148164{
149165 if (!payload || payload_len == 0 ) {
@@ -153,7 +169,11 @@ static void coap_send_data(struct coap_request *req, const uint8_t *payload, siz
153169 urc_send_to (req -> pipe , "\r\n#XCOAPCDATA: %d,%d,%d\r\n" , req -> fd ,
154170 (int )req -> bytes_sent , (int )payload_len );
155171 req -> bytes_sent += payload_len ;
156- data_send (req -> pipe , payload , payload_len );
172+ if (req -> hex_rx ) {
173+ coap_data_send_hex (req -> pipe , payload , payload_len );
174+ } else {
175+ data_send (req -> pipe , payload , payload_len );
176+ }
157177}
158178
159179/* Notify host that a response block is ready to pull in manual receive mode. */
@@ -537,16 +557,16 @@ static int coap_datamode_callback(uint8_t op, const uint8_t *data, int len, uint
537557}
538558
539559/**
540- * AT#XCOAPCREQ=<handle>,<path>,<method>[,<auto_reception>[,<confirmable >
541- * [,<content_format>[,<payload_len>[,<opt_num>,<opt_val>...]]]]]
560+ * AT#XCOAPCREQ=<handle>,<path>,<method>[,<auto_reception>[,<hex_format >
561+ * [,<confirmable>[,< content_format>[,<payload_len>[,<opt_num>,<opt_val>...] ]]]]]
542562 * AT#XCOAPCREQ=?
543563 *
544564 * Send a CoAP request over the AT socket identified by <handle>.
545565 * The socket must already be created with AT#XSOCKET and connected with AT#XCONNECT.
546566 * Returns OK immediately; the response is delivered asynchronously.
547567 *
548568 * Auto receive mode (auto_reception=1, default):
549- * #XCOAPCDATA: <handle>,<received_bytes>,<len>\r\n<raw bytes>
569+ * #XCOAPCDATA: <handle>,<received_bytes>,<len>\r\n<raw bytes or hex string >
550570 * #XCOAPCSTAT: <handle>,<status>,<total_bytes>
551571 *
552572 * Manual receive mode (auto_reception=0):
@@ -558,20 +578,21 @@ static int coap_datamode_callback(uint8_t op, const uint8_t *data, int len, uint
558578 * <path>: URI path string (quoted)
559579 * <method>: 1=GET, 2=POST, 3=PUT, 4=DELETE, 5=FETCH, 6=PATCH, 7=iPATCH
560580 * <auto_reception>: 1=automatic (default), 0=manual; host pulls body via AT#XCOAPCDATA.
581+ * <hex_format>: 0=binary (default), 1=hex string (each byte as two ASCII hex chars).
561582 * <confirmable>: 0=NON-confirmable (default), 1=CON-confirmable
562583 * <content_format>: CoAP content format integer (default 0 = text/plain)
563584 * <payload_len>: Optional payload length in bytes; if > 0, the command returns OK
564585 * and the device enters data mode to receive the raw payload bytes.
565586 * <opt_num>: CoAP option number (decimal integer, e.g. 35 for Proxy-Uri).
566587 * Must be followed by <opt_val>. Zero or more pairs may be appended.
567- * <opt_val>: Option value (quoted string). Decoded as hex bytes when the string
568- * is non-empty, even-length, and all hex digits (e.g. "3c " -> 0x3c);
588+ * <opt_val>: Option value (quoted string). A value prefixed with "0x" or "0X"
589+ * is decoded as raw hex bytes (e.g. "0x3c " -> 0x3c);
569590 * otherwise used verbatim as a UTF-8 string.
570591 *
571592 * Examples:
572593 * AT#XCOAPCREQ=0,"/path",1
573- * AT#XCOAPCREQ=0,"proxy",1,1,1,0,0,35,"https://host/path"
574- * AT#XCOAPCREQ=0,"/obs",1,1,1,0,0,6,"00 ",17,"3c "
594+ * AT#XCOAPCREQ=0,"proxy",1,1,0, 1,0,0,35,"https://host/path"
595+ * AT#XCOAPCREQ=0,"/obs",1,1,0, 1,0,0,6,"0x00 ",17,"0x3c "
575596 */
576597SM_AT_CMD_CUSTOM (xcoapreq , "AT#XCOAPCREQ" , handle_at_coap_req );
577598STATIC int handle_at_coap_req (enum at_parser_cmd_type cmd_type , struct at_parser * parser ,
@@ -586,6 +607,7 @@ STATIC int handle_at_coap_req(enum at_parser_cmd_type cmd_type, struct at_parser
586607 const char * path ;
587608 size_t path_len ;
588609 int auto_reception = 1 ;
610+ int hex_format = 0 ;
589611 int confirmable = 0 ;
590612 int content_format = COAP_CONTENT_FORMAT_TEXT_PLAIN ;
591613 int payload_len = 0 ;
@@ -635,25 +657,36 @@ STATIC int handle_at_coap_req(enum at_parser_cmd_type cmd_type, struct at_parser
635657 }
636658
637659 if (param_count > 5 ) {
638- ret = at_parser_num_get (parser , 5 , & confirmable );
660+ ret = at_parser_num_get (parser , 5 , & hex_format );
639661 if (ret ) {
640662 return ret ;
641663 }
642- if (confirmable != 0 && confirmable != 1 ) {
643- LOG_ERR ("Invalid confirmable value: %d" , confirmable );
664+ if (hex_format != 0 && hex_format != 1 ) {
665+ LOG_ERR ("Invalid hex_format value: %d" , hex_format );
644666 return - EINVAL ;
645667 }
646668 }
647669
648670 if (param_count > 6 ) {
649- ret = at_parser_num_get (parser , 6 , & content_format );
671+ ret = at_parser_num_get (parser , 6 , & confirmable );
650672 if (ret ) {
651673 return ret ;
652674 }
675+ if (confirmable != 0 && confirmable != 1 ) {
676+ LOG_ERR ("Invalid confirmable value: %d" , confirmable );
677+ return - EINVAL ;
678+ }
653679 }
654680
655681 if (param_count > 7 ) {
656- ret = at_parser_num_get (parser , 7 , & payload_len );
682+ ret = at_parser_num_get (parser , 7 , & content_format );
683+ if (ret ) {
684+ return ret ;
685+ }
686+ }
687+
688+ if (param_count > 8 ) {
689+ ret = at_parser_num_get (parser , 8 , & payload_len );
657690 if (ret ) {
658691 return ret ;
659692 }
@@ -677,6 +710,7 @@ STATIC int handle_at_coap_req(enum at_parser_cmd_type cmd_type, struct at_parser
677710 memcpy (req -> path , path , path_len );
678711 req -> path [path_len ] = '\0' ;
679712 req -> manual_rx = !auto_reception ;
713+ req -> hex_rx = (bool )hex_format ;
680714
681715 if (req -> manual_rx ) {
682716 req -> rx_buf = malloc (CONFIG_COAP_CLIENT_BLOCK_SIZE );
@@ -686,21 +720,25 @@ STATIC int handle_at_coap_req(enum at_parser_cmd_type cmd_type, struct at_parser
686720 }
687721 }
688722
689- /* Parse option pairs (<opt_num>, <opt_val>) starting at param 8.
690- * <opt_val> is decoded as hex bytes when it is a non-empty, even-length
691- * all-hex-digits string; otherwise it is used verbatim as a UTF-8 string.
723+ /* Parse option pairs (<opt_num>, <opt_val>) starting at param 9.
724+ * Param 8 is <payload_len>; it must be present (explicitly 0) when options
725+ * follow. Omitting <payload_len> shifts every option one position left, which
726+ * always produces an odd option-param count and is caught below.
727+ *
728+ * <opt_val> is decoded as raw bytes when prefixed with "0x"/"0X"
729+ * (e.g. "0x3c" → 1 byte 0x3c); otherwise used verbatim as a UTF-8 string.
692730 * Examples:
693- * ...,35,"https ://host/path" → Proxy-Uri (option 35, text value )
694- * ...,17,"3c " → Accept: application/cbor (1 byte 0x3c)
695- * ...,6,"00 ",17,"3c " → Observe + Accept
731+ * ...,0,0, 35,"coap ://host/path" payload_len=0, Proxy-Uri (text)
732+ * ...,0,0, 17,"0x3c " payload_len=0, Accept: application/cbor
733+ * ...,0,0, 6,"0x00 ",17,"0x3c " payload_len=0, Observe + Accept
696734 */
697- if (param_count > 8 && ((param_count - 8 ) & 1U )) {
698- LOG_ERR ("Odd number of option params; each opt_num must be paired "
699- "with an opt_val" );
735+ if (param_count > 9 && ((param_count - 9 ) & 1U )) {
736+ LOG_ERR ("Odd number of option params after payload_len "
737+ "(missing payload_len=0 before options, or unpaired opt_num/ opt_val) " );
700738 ret = - EINVAL ;
701739 goto cleanup_req ;
702740 }
703- for (uint32_t i = 8 ; (i + 1 ) < param_count ; i += 2 ) {
741+ for (uint32_t i = 9 ; (i + 1 ) < param_count ; i += 2 ) {
704742 const char * val_str ;
705743 size_t val_len ;
706744 size_t decoded_len ;
@@ -773,8 +811,8 @@ STATIC int handle_at_coap_req(enum at_parser_cmd_type cmd_type, struct at_parser
773811
774812 case AT_PARSER_CMD_TYPE_TEST :
775813 rsp_send ("\r\n#XCOAPCREQ: <handle>,<path>,<method>"
776- "[,<auto_reception>[,<confirmable>[,<content_format>"
777- "[,<payload_len>[,<opt_num>,<opt_val>...]]]]]\r\n" );
814+ "[,<auto_reception>[,<hex_format>[,< confirmable>[,<content_format>"
815+ "[,<payload_len>[,<opt_num>,<opt_val>...]]]]]]] \r\n" );
778816 ret = 0 ;
779817 break ;
780818
@@ -923,7 +961,11 @@ STATIC int handle_at_coap_data(enum at_parser_cmd_type cmd_type, struct at_parse
923961 rsp_send ("\r\n#XCOAPCDATA: %d,%d,%d\r\n" , handle , (int )req -> bytes_sent ,
924962 (int )send_len );
925963 req -> bytes_sent += send_len ;
926- data_send (req -> pipe , req -> rx_buf , send_len );
964+ if (req -> hex_rx ) {
965+ coap_data_send_hex (req -> pipe , req -> rx_buf , send_len );
966+ } else {
967+ data_send (req -> pipe , req -> rx_buf , send_len );
968+ }
927969 req -> rx_buf_filled -= send_len ;
928970
929971 if (req -> rx_buf_filled > 0 ) {
0 commit comments