msgbuf: Add pldm_msgbuf_span_string_ascii()

Add pldm_msgbuf_span_string_ascii() API to find the start of the ascii
string in the message buffer.

```
pldm_msgbuf_span_string_ascii(struct pldm_msgbuf *ctx, void **cursor,
			      size_t *length)
```

The API returns the start pointer of ascii string in the message buffer
and length of that ascii string includes Terminator.
The `cursor` and `length` are optional. Input NULL to `cursor` and
`length` will cause the message buffer cursor points to remaining data.
The caller can ignore `length` option by input NULL if they don't care
about the size of ascii string.

Change-Id: I4a73b7425ee1e4e5621eb16de6e16189efdf202b
Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/src/msgbuf.h b/src/msgbuf.h
index e5e91ed..ef65be2 100644
--- a/src/msgbuf.h
+++ b/src/msgbuf.h
@@ -1054,6 +1054,61 @@
 }
 
 __attribute__((always_inline)) static inline int
+pldm_msgbuf_span_string_ascii(struct pldm_msgbuf *ctx, void **cursor,
+			      size_t *length)
+{
+	intmax_t measured;
+
+	assert(ctx);
+
+	if (!ctx->cursor || (cursor && *cursor)) {
+		return pldm_msgbuf_status(ctx, EINVAL);
+	}
+
+	if (ctx->remaining < 0) {
+		/* Tracking the amount of overflow gets disturbed here */
+		return pldm_msgbuf_status(ctx, EOVERFLOW);
+	}
+
+	measured = (intmax_t)strnlen((const char *)ctx->cursor, ctx->remaining);
+	if (measured == ctx->remaining) {
+		/*
+		 * We have hit the end of the buffer prior to the NUL terminator.
+		 * Optimistically, the NUL terminator was one-beyond-the-end. Setting
+		 * ctx->remaining negative ensures the `pldm_msgbuf_destroy*()` APIs also
+		 * return an error.
+		 */
+		ctx->remaining = -1;
+		return pldm_msgbuf_status(ctx, EOVERFLOW);
+	}
+
+	/* Include the NUL terminator in the span length, as spans are opaque */
+	measured++;
+
+	if (ctx->remaining < INTMAX_MIN + measured) {
+		return pldm_msgbuf_status(ctx, EOVERFLOW);
+	}
+
+	ctx->remaining -= measured;
+	assert(ctx->remaining >= 0);
+	if (ctx->remaining < 0) {
+		return pldm_msgbuf_status(ctx, EOVERFLOW);
+	}
+
+	if (cursor) {
+		*cursor = ctx->cursor;
+	}
+
+	ctx->cursor += measured;
+
+	if (length) {
+		*length = measured;
+	}
+
+	return 0;
+}
+
+__attribute__((always_inline)) static inline int
 pldm_msgbuf_span_remaining(struct pldm_msgbuf *ctx, void **cursor, size_t *len)
 {
 	assert(ctx);
diff --git a/tests/msgbuf.cpp b/tests/msgbuf.cpp
index 52d47ae..714243f 100644
--- a/tests/msgbuf.cpp
+++ b/tests/msgbuf.cpp
@@ -936,6 +936,103 @@
     EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
 }
 
+TEST(msgbuf, pldm_msgbuf_span_string_ascii_good)
+{
+    struct pldm_msgbuf _ctxExtract;
+    struct pldm_msgbuf* ctxExtract = &_ctxExtract;
+    uint8_t src[9] = {0x11, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x77};
+    constexpr size_t required = 6;
+    const char expectData[required] = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00};
+    uint16_t testVal;
+    uint8_t testVal1;
+    char* retBuff = NULL;
+
+    ASSERT_EQ(pldm_msgbuf_init_errno(ctxExtract, 0, src, sizeof(src)), 0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint16(ctxExtract, &testVal), 0);
+    EXPECT_EQ(0x2211, testVal);
+    EXPECT_EQ(pldm_msgbuf_span_string_ascii(ctxExtract, (void**)&retBuff, NULL),
+              0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint8(ctxExtract, &testVal1), 0);
+    EXPECT_EQ(0x77, testVal1);
+
+    EXPECT_EQ(required, strlen(retBuff) + 1);
+    EXPECT_EQ(strncmp(expectData, retBuff, strlen(retBuff) + 1), 0);
+    EXPECT_EQ(pldm_msgbuf_destroy(ctxExtract), 0);
+}
+
+TEST(msgbuf, pldm_msgbuf_span_string_ascii_good_with_length)
+{
+    struct pldm_msgbuf _ctxExtract;
+    struct pldm_msgbuf* ctxExtract = &_ctxExtract;
+    uint8_t src[9] = {0x11, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x77};
+    constexpr size_t required = 6;
+    const char expectData[required] = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00};
+    uint16_t testVal;
+    uint8_t testVal1;
+    char* retBuff = NULL;
+    size_t length;
+
+    ASSERT_EQ(pldm_msgbuf_init_errno(ctxExtract, 0, src, sizeof(src)), 0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint16(ctxExtract, &testVal), 0);
+    EXPECT_EQ(0x2211, testVal);
+    EXPECT_EQ(
+        pldm_msgbuf_span_string_ascii(ctxExtract, (void**)&retBuff, &length),
+        0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint8(ctxExtract, &testVal1), 0);
+    EXPECT_EQ(0x77, testVal1);
+
+    EXPECT_EQ(required, strlen(retBuff) + 1);
+    EXPECT_EQ(length, strlen(retBuff) + 1);
+    EXPECT_EQ(required, length);
+    EXPECT_EQ(strncmp(expectData, retBuff, strlen(retBuff) + 1), 0);
+    EXPECT_EQ(pldm_msgbuf_destroy(ctxExtract), 0);
+}
+
+TEST(msgbuf, pldm_msgbuf_span_string_ascii_allow_null_args)
+{
+    struct pldm_msgbuf _ctxExtract;
+    struct pldm_msgbuf* ctxExtract = &_ctxExtract;
+    uint8_t src[8] = {0x11, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00};
+    uint16_t testVal;
+
+    ASSERT_EQ(pldm_msgbuf_init_errno(ctxExtract, 0, src, sizeof(src)), 0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint16(ctxExtract, &testVal), 0);
+    EXPECT_EQ(0x2211, testVal);
+    EXPECT_EQ(pldm_msgbuf_span_string_ascii(ctxExtract, NULL, NULL), 0);
+    EXPECT_EQ(pldm_msgbuf_destroy(ctxExtract), 0);
+}
+
+TEST(msgbuf, pldm_msgbuf_span_string_ascii_bad_no_terminator)
+{
+    struct pldm_msgbuf _ctxExtract;
+    struct pldm_msgbuf* ctxExtract = &_ctxExtract;
+    uint8_t src[8] = {0x11, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77};
+    uint16_t testVal;
+    char* retBuff = NULL;
+
+    ASSERT_EQ(pldm_msgbuf_init_errno(ctxExtract, 0, src, sizeof(src)), 0);
+    EXPECT_EQ(pldm_msgbuf_extract_uint16(ctxExtract, &testVal), 0);
+    EXPECT_EQ(0x2211, testVal);
+    EXPECT_EQ(pldm_msgbuf_span_string_ascii(ctxExtract, (void**)&retBuff, NULL),
+              -EOVERFLOW);
+    EXPECT_EQ(pldm_msgbuf_destroy(ctxExtract), -EOVERFLOW);
+}
+
+TEST(msgbuf, pldm_msgbuf_span_string_ascii_under)
+{
+    struct pldm_msgbuf _ctxExtract;
+    struct pldm_msgbuf* ctxExtract = &_ctxExtract;
+
+    uint8_t src[1] = {};
+    char* retBuff = NULL;
+
+    ASSERT_EQ(pldm_msgbuf_init_errno(ctxExtract, 0, src, 0), 0);
+    ctxExtract->remaining = INTMAX_MIN;
+    EXPECT_NE(pldm_msgbuf_span_string_ascii(ctxExtract, (void**)&retBuff, NULL),
+              0);
+    EXPECT_EQ(pldm_msgbuf_destroy(ctxExtract), -EOVERFLOW);
+}
+
 TEST(msgbuf, pldm_msgbuf_span_remaining_good)
 {
     struct pldm_msgbuf _ctx;