1

AM trying to prepare a multipart/related message which involve JSON data n1n2Request.JsonData and a binary data binaryData to be sent in HTTP request, however, am having issue abou how to set the boundries in the function below. The server is logging "Failed to parse boundary. missing/invalide boundary". I need help to set the boundaries correctly.




func CreateMultipartRelatedN1N2MessageTransferRequest(inputData models.InputData) (*models.N1N2MessageTransferRequest, string, error) {
    // Create a buffer to hold the multipart data
    var buf bytes.Buffer

    // Create a multipart writer with "multipart/related" type
    writer := multipart.NewWriter(&buf)

    // Define the boundary for the multipart/related content
    boundary := "----n1n2msg" // Explicitly set a boundary
    writer.SetBoundary(boundary)             // Apply the boundary to the writer

    // Create the N1N2MessageTransferRequest object
    n1n2Request := models.N1N2MessageTransferRequest{}

    // Get LMF instanceID (example placeholder, replace with actual logic)
    _, _, nfId, _, _ := config.Cfg.GetServerInfo()

    // Populate the N1N2MessageTransferReqData
    n1n2Request.JsonData = &models.N1N2MessageTransferReqData{
        N1MessageContainer: &models.N1MessageContainer{
            N1MessageClass: models.N1MessageClass_LPP,
            N1MessageContent: &models.RefToBinaryData{
                ContentId: "n1msg",
            },
            NfId: nfId,
        },
        LcsCorrelationId:       inputData.CorrelationID,
        N1n2FailureTxfNotifURI: "",
    }

    // Add the JSON part as the root body part
    jsonPartHeader := textproto.MIMEHeader{}
    jsonPartHeader.Set("Content-Type", "application/json; charset=UTF-8")
    //jsonPartHeader.Set("Content-ID", "<n1msg>")
    jsonPart, err := writer.CreatePart(jsonPartHeader)
    if err != nil {
        return nil, "", fmt.Errorf("error creating JSON part: %v", err)
    }

    jsonBytes, err := json.Marshal(n1n2Request.JsonData)
    if err != nil {
        return nil, "", fmt.Errorf("error marshaling JSON data: %v", err)
    }
    _, err = jsonPart.Write(jsonBytes)
    if err != nil {
        return nil, "", fmt.Errorf("error writing JSON data: %v", err)
    }

    // Create the LPP message
    lppMessage := models.LPPMessage{
        TransactionID: &models.LPPTransactionID{
            TransactionNumber: common.GenerateNumber(),
            Initiator:         int(models.LocationServer),
        },
        SequenceNumber: common.GenerateNumber(),
        LPPMessageBody: &models.LPPMessageBody{
            C1: &models.C1{
                RequestLocationInformation: &models.RequestLocationInformation{
                    CriticalExtensions: &models.CriticalExtensions{
                        C1: &models.CriticalExtensionsC1{
                            RequestLocationInformationR9: &models.RequestLocationInformationR9IEs{
                                CommonIEsRequestLocationInformation: &models.CommonIEsRequestLocationInformation{
                                    LocationInformationType: int(models.LocationEstimateRequired),
                                    PeriodicalReporting: &models.PeriodicalReportingCriteria{
                                        ReportingAmount:   int(models.RA1),
                                        ReportingInterval: int(models.RI32),
                                    },
                                    LocationCoordinateTypes: &models.LocationCoordinateTypes{
                                        EllipsoidPoint: true,
                                    },
                                    VelocityTypes: &models.VelocityTypes{
                                        HorizontalVelocity: true,
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
        EndTransaction: true,
    }

    // Encode the LPP message using ASN.1 PER encoding
    binaryData, err := aper.Marshal(lppMessage)
    if err != nil {
        logger.Log.Error().Msgf("Error encoding, aper.Marshal error: %v", err)
        return nil, "", err
    }

    // Add the binary part with a Content-ID header
    binaryPartHeader := textproto.MIMEHeader{}
    binaryPartHeader.Set("Content-ID", "<n1msg>")
    binaryPartHeader.Set("Content-Type", "application/vnd.3gpp.5gnas")
    binaryPart, err := writer.CreatePart(binaryPartHeader)
    if err != nil {
        return nil, "", fmt.Errorf("error creating binary part: %v", err)
    }

    // Write the encoded binary data to the binary part
    _, err = binaryPart.Write(binaryData)
    if err != nil {
        return nil, "", fmt.Errorf("error writing binary data: %v", err)
    }

    // Close the multipart writer to finalize the form data
    err = writer.Close()
    if err != nil {
        return nil, "", fmt.Errorf("error closing multipart writer: %v", err)
    }

    // Attach the buffer's content as the binary data of the N1N2MessageTransferRequest
    n1n2Request.BinaryDataN1Message = buf.Bytes()

    contentType := fmt.Sprintf("multipart/related; boundary=%s", boundary)

    return &n1n2Request, contentType, nil
}


Here is example in the specification

------Boundary
Content-Type: application/json
{
 "n2InfoContainer": {
 "n2InformationClass": "SM",
 "smInfo": {
 "pduSessionId": 5,
 "n2InfoContent": {
 "ngapIeType": "PDU_RES_SETUP_REQ",
 "ngapData": {
 "contentId": "n2msg"
 }
 }
 }
 },
 "pduSessionId": 5
}
------Boundary
Content-Type: application/vnd.3gpp.ngap
Content-Id: n2msg
{ … N2 Information binary data …}
------Boundary

Usage

n1n2MessageTransferRespData, _, err := consumer.NewApiClient.N1N2MessageCollectionCollectionApi.N1N2MessageTransfer(context.Background(), *n1n2MessageRequest, inputData.Supi, inputData.AmfId, contentType)

...

...

In 
func (c *N1N2MessageCollectionCollectionApiService) N1N2MessageTransfer(ctx context.Context, n1N2MessageRequest models.N1N2MessageTransferRequest, ueContextId,
    amfInstanceId, contentType string) (models.N1N2MessageTransferRspData, *http.Response, error) {

...
...

req, err := c.client.prepareRequest(ctx, varPath, varHttpMethod, varPostBody, varHeaderParams, varQueryParams, varFormParams, varFileName, varFileBytes)
    if err != nil {
        return varReturnValue, nil, err
    }
    // Set the Content-Type header
    req.Header.Set("Content-Type", contentType)
...
...

}


6
  • Well, so, if you rip this code out of your piece of software and merely dump what ends up in the buf into a file—do you see a correctly-formulated MIME message with the boundary in place? A multipart MIME message is a textual format after all, it can be inspected using a bare eye. Commented Jun 17, 2024 at 14:59
  • While we're at it, are you sure the server expects truly binary content in the second part? The thing is, MIME has been invented to facilitate data transfers using SMTP and so its messages are 7-bit ASCII streams, and hence binary data is usually (but not necessarily) encoded using base64 before sending. What the spec on that "N1N2"—whatever it is—says on the format of the binary data sent in messages it expects? Commented Jun 17, 2024 at 15:02
  • The code in the question returns a content type with boundary attribute. Does the caller set request content type? Commented Jun 17, 2024 at 15:45
  • Yes, the server is expecting JSON and binary data. I have seen an example of HTTP multipart message in Annex B, B.1.2 in 3gpp specification ETSI TS 129 518 V17.13.0 and have updated the function. Commented Jun 17, 2024 at 15:50
  • Yes, Ceirse, I set the content-type after the request. Commented Jun 17, 2024 at 15:53

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.