| /* ------------------------------------------------------------------ |
| * Copyright (C) 1998-2009 PacketVideo |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
| * express or implied. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * ------------------------------------------------------------------- |
| */ |
| #include "base_media_info_parser.h" |
| #include "oscl_string_utils.h" |
| #include "oscl_string_containers.h" |
| #include "rtsp_range_utils.h" |
| |
| |
| /* Function to allocate temporary buffer, OSCL_TRY() put here to avoid */ |
| /* compiler warnings */ |
| static void newTmpBuf(uint32 len, char** buf) |
| { |
| int32 err; |
| *buf = NULL; |
| |
| OSCL_TRY(err, *buf = OSCL_ARRAY_NEW(char, len)); |
| |
| if (err != OsclErrNone) |
| { |
| *buf = NULL; |
| } |
| } |
| |
| SDP_ERROR_CODE |
| SDPBaseMediaInfoParser::baseMediaInfoParser(const char* buff, |
| mediaInfo* mediaStr, |
| const int index, |
| const int alt_id, |
| bool alt_def_id, |
| bool isSipSdp) |
| { |
| const char *current_start = buff; //Pointer to the beginning of the media text |
| const char *end = buff + index; //Pointer to the end of the media text |
| const char *line_start_ptr, *line_end_ptr; |
| |
| |
| bool a_range_found = false; |
| |
| bool a_rtpmap_found = false; |
| bool a_control_found = false; |
| bool a_control_set = false; |
| |
| OsclMemoryFragment memFrag; |
| |
| while (get_next_line(current_start, end, |
| line_start_ptr, line_end_ptr)) |
| { |
| if ((!alt_def_id && !alt_id) || (alt_def_id) || |
| ((!alt_def_id) && !oscl_strncmp(line_start_ptr, "a=alt:", oscl_strlen("a=alt:")))) |
| { |
| if (!alt_def_id && !oscl_strncmp(line_start_ptr, "a=alt:", oscl_strlen("a=alt:"))) |
| { |
| line_start_ptr += oscl_strlen("a=alt:"); |
| const char *end1 = line_start_ptr; |
| for (; *end1 != ':'; end1++); |
| uint32 id; |
| if (!PV_atoi(line_start_ptr, 'd' , end1 - line_start_ptr, id)) |
| return SDP_BAD_MEDIA_ALT_ID; |
| if ((int)id != alt_id) |
| { |
| //check if id is already present |
| Oscl_Vector<int, SDPParserAlloc> alt_track = mediaStr->getalternateTrackId(); |
| bool found = false; |
| |
| for (int ss = 0; ss < (int)alt_track.size(); ss++) |
| { |
| if (alt_track[ss] == (int)id) |
| found = true; |
| } |
| |
| if (!found) |
| mediaStr->setalternateTrackId(id); |
| |
| current_start = line_end_ptr; |
| continue; |
| } |
| |
| line_start_ptr = end1 + 1; |
| line_start_ptr = skip_whitespace(line_start_ptr, line_end_ptr); |
| |
| } |
| |
| switch (*line_start_ptr) |
| { |
| case 'm': |
| { |
| if (*(line_start_ptr + 1) != '=') |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| // parse through each field |
| const char *sptr, *eptr; |
| |
| //line_start_ptr+2 since we need to start looking beyond the '=' sign |
| |
| //get the media type (audio, video, application) |
| sptr = skip_whitespace(line_start_ptr + 2, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for media type")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| memFrag.ptr = (void*)sptr; |
| memFrag.len = (eptr - sptr); |
| |
| mediaStr->setType(memFrag); |
| |
| //get the suggested port number |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for suggested port")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 suggestedPort; |
| |
| OSCL_HeapString<SDPParserAlloc> restOfLine(sptr, eptr - sptr); |
| const char *slash = oscl_strstr(restOfLine.get_cstr(), "/"); |
| if (slash) |
| { |
| if (PV_atoi(restOfLine.get_cstr(), 'd', (slash - restOfLine.get_cstr()), suggestedPort) == true) |
| { |
| mediaStr->setSuggestedPort(suggestedPort); |
| // There must be number of ports info after the slash |
| uint32 numOfPorts; |
| const char *ports = oscl_strstr(sptr, "/"); |
| ports++; |
| if (ports == NULL) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for ports info")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| if (PV_atoi(ports, 'd', (eptr - ports), numOfPorts) == true) |
| { |
| mediaStr->setNumOfPorts(numOfPorts); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for ports info")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for ports info")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| else |
| { |
| if (PV_atoi(sptr, 'd', (eptr - sptr), suggestedPort) == true) |
| { |
| mediaStr->setSuggestedPort(suggestedPort); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for ports info")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| //get the transport profile |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for tranport profile")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for tranport profile")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| memFrag.ptr = (void*)sptr; |
| memFrag.len = (eptr - sptr); |
| |
| if (oscl_strncmp(sptr, "RTP/AVP", (eptr - sptr)) && oscl_strncmp(sptr, "RTP/AVPF", (eptr - sptr)) && oscl_strncmp(sptr, "RTP/SAVP", (eptr - sptr))) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for tranport profile")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| else if ((suggestedPort % 2)) // port number should be even |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format - port number is not even")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| mediaStr->setTransportProfile(memFrag); |
| |
| //get the payload number |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for payload number")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| while (eptr < line_end_ptr) |
| { |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for payload number")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 payloadNumber; ; |
| if (PV_atoi(sptr, 'd', (eptr - sptr), payloadNumber) == true) |
| { |
| // Parse the payload number info only and see if there |
| // is any payload number in static range if yes rtpmap |
| // field may not be present for this |
| for (uint32 ii = 0; ii < mediaStr->getPayloadSpecificInfoVector().size(); ii++) |
| { |
| if (payloadNumber == mediaStr->getPayloadSpecificInfoVector()[ii]->getPayloadNumber()) |
| { |
| // check if (FIRST_STATIC_PAYLOAD <= payloadNumber <= LAST_STATIC_PAYLOAD) |
| // since payloadNumber is unsigned and FIRST_STATIC_PAYLOAD == 0, only the upper |
| // boundary needs to be checked. Adding the lower boundary causes compiler warning. |
| if (payloadNumber <= LAST_STATIC_PAYLOAD) |
| a_rtpmap_found = true; |
| } |
| } |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad m= line format for payload number")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| eptr = sptr; |
| } |
| // No rtpmap will come if port is 0 in sip sdp |
| if (isSipSdp && suggestedPort == 0) |
| { |
| a_rtpmap_found = true; |
| } |
| |
| } |
| break; |
| case 'a': |
| { |
| if (*(line_start_ptr + 1) != '=') |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a= line format - '=' missing")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| // parse through each field |
| const char *sptr1, *eptr1; |
| if (!oscl_strncmp(line_start_ptr, "a=rtpmap:", oscl_strlen("a=rtpmap:"))) |
| { |
| //get the payload number |
| sptr1 = line_start_ptr + oscl_strlen("a=rtpmap:"); |
| sptr1 = skip_whitespace(sptr1, line_end_ptr); |
| |
| a_rtpmap_found = true; |
| |
| if (sptr1 >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| eptr1 = skip_to_whitespace(sptr1, line_end_ptr); |
| if (eptr1 <= sptr1) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| uint32 payloadNumber; |
| if (PV_atoi(sptr1, 'd', (eptr1 - sptr1), payloadNumber) == true) |
| { |
| int p; |
| if (!mediaStr->lookupPayloadNumber(payloadNumber, p)) |
| { |
| break; |
| } |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format for payload number")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| |
| // payloadNumber is present in the mediaInfo. get the payload |
| // Specific pointer corresponding to this payload |
| PayloadSpecificInfoTypeBase* payloadPtr = |
| mediaStr->getPayloadSpecificInfoTypePtr(payloadNumber); |
| if (payloadPtr == NULL) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Unable to get payload pointer for the payload")); |
| return SDP_PAYLOAD_MISMATCH; |
| } |
| PVMF_SDP_PARSER_LOGINFO((0, "SDPBaseMediaInfoParser::parseMediaInfo - processing payload number : %d", payloadNumber)); |
| |
| //get the MIME type and sample rate |
| sptr1 = skip_whitespace(eptr1, line_end_ptr); |
| if (sptr1 >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format for MIME Type & Sample rate")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| |
| eptr1 = skip_to_whitespace(sptr1, line_end_ptr); |
| if (eptr1 <= sptr1) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| //int ii = 0; |
| const char *tmp_end_ptr = NULL; |
| /* |
| for( ii = 0; ii < (eptr1-sptr1); ii++ ) |
| { |
| if(sptr1[ii] == '/') |
| { |
| tmp_end_ptr = sptr1 + ii; |
| break; |
| } |
| } |
| */ |
| const char SDP_FWD_SLASH[] = "/"; |
| tmp_end_ptr = oscl_strstr(sptr1, SDP_FWD_SLASH); |
| if (tmp_end_ptr == NULL) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format - nothing after '/' ")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| //The below mentioned code converts the non standard MIME type to standard MIME type. |
| //For eg. earlier we have MIME type "AMR" and according to standard it should be |
| //like "audio/AMR". so tho whole logic implements the same. |
| uint32 tempBufLen = 0; |
| char *tmpBuf = NULL; |
| const char SDP_NULL[] = "\0"; |
| |
| tempBufLen = oscl_strlen(mediaStr->getType()) + (tmp_end_ptr - sptr1) + 2; |
| |
| // "OSCL_TRY(err, OSCL_ARRAY_NEW(char, tempBufLen)" is in separate function to avoid warnings |
| newTmpBuf(tempBufLen, &tmpBuf); |
| if (NULL == tmpBuf) |
| { |
| return SDP_NO_MEMORY; |
| } |
| |
| oscl_strncpy(tmpBuf, mediaStr->getType(), (oscl_strlen(mediaStr->getType()) + 1)); |
| oscl_strcat(tmpBuf, SDP_FWD_SLASH); |
| oscl_strncat(tmpBuf, sptr1, (tmp_end_ptr - sptr1)); |
| oscl_strcat(tmpBuf, SDP_NULL); |
| |
| |
| memFrag.ptr = (void*)tmpBuf; |
| memFrag.len = oscl_strlen(tmpBuf); |
| |
| mediaStr->setMIMEType(memFrag); |
| OSCL_ARRAY_DELETE(tmpBuf); |
| tmpBuf = NULL; |
| //Till here |
| tmp_end_ptr++; |
| if (tmp_end_ptr >= eptr1) |
| { |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| tmp_end_ptr = skip_whitespace(tmp_end_ptr, eptr1); |
| if (tmp_end_ptr >= eptr1) |
| { |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| |
| OSCL_HeapString<SDPParserAlloc> restOfLine(tmp_end_ptr, eptr1 - tmp_end_ptr); |
| const char *another_slash = oscl_strstr(restOfLine.get_cstr(), SDP_FWD_SLASH); |
| |
| uint32 sampleRate; |
| if (another_slash) |
| { |
| if (PV_atoi(restOfLine.get_cstr(), 'd', (another_slash - restOfLine.get_cstr()), sampleRate) == true) |
| { |
| payloadPtr->setSampleRate(sampleRate); |
| // There must be channel numbers after the 2nd forward slash |
| uint32 channels; |
| tmp_end_ptr = oscl_strstr(tmp_end_ptr, SDP_FWD_SLASH); |
| if (tmp_end_ptr == NULL) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format for channel info")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| tmp_end_ptr++; |
| if (PV_atoi(tmp_end_ptr, 'd', (eptr1 - tmp_end_ptr), channels) == true) |
| { |
| payloadPtr->setNoOfChannels(channels); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format for channel info")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtpmap line format for channel info")); |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| } |
| else |
| { |
| if (PV_atoi(tmp_end_ptr, 'd', (eptr1 - tmp_end_ptr), sampleRate) == true) |
| { |
| payloadPtr->setSampleRate(sampleRate); |
| } |
| else |
| { |
| return SDP_BAD_MEDIA_RTP_MAP; |
| } |
| } |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=control:", oscl_strlen("a=control:"))) |
| { |
| sptr1 = line_start_ptr + oscl_strlen("a=control:"); |
| sptr1 = skip_whitespace(sptr1, line_end_ptr); |
| a_control_found = true; |
| if (sptr1 >= line_end_ptr) |
| { |
| return SDP_BAD_MEDIA_CONTROL_FIELD; |
| } |
| |
| memFrag.ptr = (void*)sptr1; |
| memFrag.len = (line_end_ptr - sptr1); |
| mediaStr->setControlURL(memFrag); |
| |
| for (int ii = 0; ii < (line_end_ptr - sptr1); ii++) |
| { |
| if (sptr1[ii] == '=') |
| { |
| uint32 trackID; |
| sptr1 = skip_whitespace((sptr1 + ii + 1), line_end_ptr); |
| if (sptr1 >= line_end_ptr) |
| { |
| break; |
| } |
| |
| if ((PV_atoi(sptr1, 'd', 1, trackID) == true)) |
| { |
| mediaStr->setControlTrackID(trackID); |
| } |
| break; |
| } |
| } |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=range:", oscl_strlen("a=range:"))) |
| { |
| sptr1 = line_start_ptr + oscl_strlen("a=range:"); |
| sptr1 = skip_whitespace(sptr1, line_end_ptr); |
| |
| a_range_found = true; |
| |
| if (sptr1 >= line_end_ptr) |
| { |
| return SDP_BAD_MEDIA_RANGE_FIELD; |
| } |
| parseRtspRange(sptr1, line_end_ptr - sptr1, *(mediaStr->getRtspRange())); |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=depends_on:", oscl_strlen("a=depends_on:"))) |
| { |
| sptr1 = line_start_ptr + oscl_strlen("a=depends_on:"); |
| memFrag.ptr = (void*)sptr1; |
| memFrag.len = (line_end_ptr - sptr1); |
| mediaStr->setDependsonURL(memFrag); |
| |
| for (int ii = 0; ii < (line_end_ptr - sptr1); ii++) |
| { |
| if (sptr1[ii] == '=') |
| { |
| uint32 trackID; |
| sptr1 = skip_whitespace((sptr1 + ii + 1), line_end_ptr); |
| if (sptr1 >= line_end_ptr) |
| { |
| break; |
| } |
| |
| if ((PV_atoi(sptr1, 'd', 1, trackID) == true)) |
| { |
| mediaStr->setDependsOnTrackID(trackID); |
| } |
| break; |
| } |
| } |
| } |
| |
| //Random access denied added for 3rd party content random positioning - 01/08/02 |
| StrPtrLen random_access("a=random_access_denied"); |
| if (!oscl_strncmp(line_start_ptr, random_access.c_str(), random_access.length())) |
| { |
| mediaStr->setRandomAccessDenied(true); |
| } |
| |
| StrPtrLen qoe_metrics("a=3GPP-QoE-Metrics:"); |
| if (!oscl_strncmp(line_start_ptr, qoe_metrics.c_str(), qoe_metrics.length())) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + qoe_metrics.length(); |
| QoEMetricsType qMetrics; |
| oscl_memset(qMetrics.name, 0, 7); |
| qMetrics.rateFmt = QoEMetricsType::VAL; |
| qMetrics.rateVal = 0; |
| qMetrics.paramFmt = QoEMetricsType::IDIGIT; |
| qMetrics.paramExtIdigit = 0; |
| |
| if (!parseQoEMetrics(sptr, line_end_ptr, qMetrics)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-QoE-Metrics: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setQoEMetrics(qMetrics); |
| |
| |
| } |
| StrPtrLen predec("a=X-predecbufsize:"); |
| if (!oscl_strncmp(line_start_ptr, predec.c_str(), predec.length())) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + predec.length(); |
| uint32 size; |
| if (!PV_atoi(sptr, 'd', (line_end_ptr - sptr), size)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=X-predecbufsize: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setPreDecBuffSize(size); |
| } |
| |
| StrPtrLen initpredec("a=X-initpredecbufperiod:"); |
| if (!oscl_strncmp(line_start_ptr, initpredec.c_str(), initpredec.length())) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + initpredec.length(); |
| uint32 period; |
| if (!PV_atoi(sptr, 'd', (line_end_ptr - sptr), period)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=X-initpredecbufperiod: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setInitPreDecBuffPeriod(period); |
| } |
| |
| StrPtrLen initpostdec("a=X-initpostdecbufperiod:"); |
| if (!oscl_strncmp(line_start_ptr, initpostdec.c_str(), initpostdec.length())) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + initpostdec.length(); |
| uint32 period; |
| if (!PV_atoi(sptr, 'd', (line_end_ptr - sptr), period)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=X-initpostdecbufperiod: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setInitPostDecBuffPeriod(period); |
| } |
| |
| StrPtrLen decbyterate("a=X-decbyterate:"); |
| if (!oscl_strncmp(line_start_ptr, decbyterate.c_str(), |
| decbyterate.length())) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + decbyterate.length(); |
| uint32 rate; |
| if (!PV_atoi(sptr, 'd', (line_end_ptr - sptr), rate)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=X-decbyterate: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setDecByteRate(rate); |
| } |
| |
| StrPtrLen adapt_supp("a=3GPP-Adaptation-Support:"); |
| if (!oscl_strncmp(line_start_ptr, adapt_supp.c_str(), |
| adapt_supp.length())) |
| { |
| const char *sptr = line_start_ptr + adapt_supp.length(); |
| sptr = skip_whitespace_and_line_term(sptr, line_end_ptr); |
| uint32 frequency; |
| if (!PV_atoi(sptr, 'd', line_end_ptr - sptr, frequency)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-Adaptation-Support: line format - frequency not correct")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setReportFrequency(frequency); |
| } |
| |
| StrPtrLen asset_info("a=3GPP-Asset-Information:"); |
| if (!oscl_strncmp(line_start_ptr, asset_info.c_str(), |
| asset_info.length())) |
| { |
| const char *sptr = line_start_ptr + asset_info.length(); |
| AssetInfoType assetInfo; |
| if (!parseAssetInfo(sptr, line_end_ptr, assetInfo)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-Asset-Information: line format")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| mediaStr->setAssetInfo(assetInfo); |
| |
| } |
| StrPtrLen srtp("a=3GPP-SRTP-Config:"); |
| if (!oscl_strncmp(line_start_ptr, srtp.c_str(), |
| srtp.length())) |
| { |
| const char *sptr = line_start_ptr + srtp.length(); |
| const char *eptr; |
| sptr = skip_whitespace(sptr, line_end_ptr); |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = eptr - sptr; |
| |
| mediaStr->setSRTPintg_nonce(memFrag); |
| |
| eptr = eptr + 1; |
| sptr = eptr; |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-SRTP-Config: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| eptr = skip_to_whitespace(eptr, line_end_ptr); |
| if (eptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-SRTP-Config: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = eptr - sptr; |
| mediaStr->setSRTPkey_salt(memFrag); |
| |
| eptr = eptr + 1; |
| sptr = eptr; |
| |
| if (!oscl_strncmp(sptr, "auth-tag-len=", oscl_strlen("auth-tag-len="))) |
| { |
| sptr = sptr + oscl_strlen("auth-tag-len="); |
| uint32 length; |
| if (!PV_atoi(sptr, 'd', 2, length)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-SRTP-Config: line format for auth-tag-len= field")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| if ((length != 32) && (length != 80)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=3GPP-SRTP-Config: line format for auth-tag-len= field")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| else |
| mediaStr->setSRTPauth_tag_len(length); |
| |
| } |
| else |
| { |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = line_end_ptr - sptr; |
| |
| mediaStr->setSRTPparam_ext(memFrag); |
| |
| |
| } |
| |
| |
| } |
| StrPtrLen rtcp_fb("a=rtcp-fb:"); |
| if (!oscl_strncmp(line_start_ptr, rtcp_fb.c_str(), rtcp_fb.length())) |
| { |
| const char *sptr = line_start_ptr + rtcp_fb.length(); |
| const char *eptr = skip_to_whitespace(sptr, line_end_ptr); |
| |
| if (eptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtcp-fb: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = eptr - sptr; |
| mediaStr->setrtcp_fb_pt(memFrag); |
| |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = eptr - sptr; |
| mediaStr->setrtcp_fb_val(memFrag); |
| |
| if (eptr >= line_end_ptr) |
| break; |
| |
| if (!oscl_strncmp(sptr, "trr-int", eptr - sptr)) |
| { |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| eptr = skip_to_line_term(sptr, line_end_ptr); |
| uint32 trr; |
| if (!PV_atoi(sptr, 'd', eptr - sptr, trr)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=rtcp-fb: line format for trr-int field")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| mediaStr->setrtcp_fb_trr_val(trr); |
| } |
| else |
| { |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| eptr = skip_to_line_term(sptr, line_end_ptr); |
| memFrag.ptr = (void *)sptr; |
| memFrag.len = eptr - sptr; |
| mediaStr->setrtcp_fb_val_param(memFrag); |
| |
| } |
| |
| |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=alt:", oscl_strlen("a=alt:"))) |
| { |
| line_start_ptr += oscl_strlen("a=alt:"); |
| const char *end1 = line_start_ptr; |
| for (; *end1 != ':'; end1++); |
| uint32 id; |
| if (!PV_atoi(line_start_ptr, 'd' , end1 - line_start_ptr, id)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad ID in a=alt: line format")); |
| return SDP_BAD_MEDIA_ALT_ID; |
| } |
| |
| //check if id is already present |
| Oscl_Vector<int, SDPParserAlloc> alt_track = mediaStr->getalternateTrackId(); |
| bool found = false; |
| for (int ss = 0; ss < (int)alt_track.size(); ss++) |
| { |
| if (alt_track[ss] == (int)id) |
| found = true; |
| } |
| if (!found) |
| mediaStr->setalternateTrackId(id); |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=maxprate:", oscl_strlen("a=maxprate:"))) |
| { |
| line_start_ptr += oscl_strlen("a=maxprate:"); |
| OsclFloat rate; |
| if (!PV_atof(line_start_ptr, line_end_ptr - line_start_ptr, rate)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad a=maxprate: line format for rate field")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| mediaStr->setMaxprate(rate); |
| } |
| if (!oscl_strncmp(line_start_ptr, "a=X-allowrecord", oscl_strlen("a=X-allowrecord"))) |
| { |
| mediaStr->setAllowRecord(true); |
| } |
| |
| } |
| break; |
| case 'b': |
| { |
| if (!oscl_strncmp(line_start_ptr, "b=AS:", oscl_strlen("b=AS:"))) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + oscl_strlen("b=AS:"); |
| sptr = skip_whitespace(sptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=AS: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 bitRate; |
| if (PV_atoi(sptr, 'd', (line_end_ptr - sptr), bitRate) == true) |
| { |
| mediaStr->setBitrate(1000*bitRate); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=AS: line format - bitrate incorrect")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| else if (!oscl_strncmp(line_start_ptr, "b=RS:", oscl_strlen("b=RS:"))) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + oscl_strlen("b=AS:"); |
| sptr = skip_whitespace(sptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=RS: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 rtcpBWSender; |
| if (PV_atoi(sptr, 'd', (line_end_ptr - sptr), rtcpBWSender) == true) |
| { |
| mediaStr->setRTCPSenderBitRate(rtcpBWSender); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=RS: line format - Sender Bitrate incorrect")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| else if (!oscl_strncmp(line_start_ptr, "b=RR:", oscl_strlen("b=RR:"))) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + oscl_strlen("b=AS:"); |
| sptr = skip_whitespace(sptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=RR: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 rtcpBWReceiver; |
| if (PV_atoi(sptr, 'd', (line_end_ptr - sptr), rtcpBWReceiver) == true) |
| { |
| mediaStr->setRTCPReceiverBitRate(rtcpBWReceiver); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=RR: line format - Receiver Bit rate incorrect")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| else if (!oscl_strncmp(line_start_ptr, "b=TIAS:", oscl_strlen("b=TIAS:"))) |
| { |
| const char *sptr; |
| sptr = line_start_ptr + oscl_strlen("b=TIAS:"); |
| sptr = skip_whitespace(sptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=T1AS: line format")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| |
| uint32 bMod; |
| if (PV_atoi(sptr, 'd', (line_end_ptr - sptr), bMod) == true) |
| { |
| mediaStr->setBWtias(1000 * bMod); |
| } |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad b=T1AS: line format - bMod incorrect")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| } |
| break; |
| case 'u': |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - u field not supported")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| case 'c': |
| { |
| if (*(line_start_ptr + 1) != '=') |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - '=' missing after c")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| mediaStr->setCFieldStatus(true); |
| |
| // parse through each field |
| const char *sptr, *eptr; |
| |
| // get the connection network type |
| sptr = skip_whitespace(line_start_ptr + 2, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - connection network type missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - part after connection network type missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| memFrag.ptr = (void*)sptr; |
| memFrag.len = (eptr - sptr); |
| mediaStr->setCNetworkType(memFrag); |
| |
| // get the address type |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - address type missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr <= sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - part after address type missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| memFrag.ptr = (void*)sptr; |
| memFrag.len = (eptr - sptr); |
| mediaStr->setCAddressType(memFrag); |
| |
| // get the address |
| sptr = skip_whitespace(eptr, line_end_ptr); |
| if (sptr >= line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - address missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| eptr = skip_to_whitespace(sptr, line_end_ptr); |
| if (eptr < sptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format - part after address missing")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| memFrag.ptr = (void*)sptr; |
| memFrag.len = (eptr - sptr); |
| mediaStr->setCAddress(memFrag); |
| uint32 len = OSCL_MIN((uint32)(eptr - sptr), oscl_strlen("IP4")); |
| if (oscl_strncmp(sptr, "IP4", len) == 0) |
| { |
| uint32 address; |
| const char *addrend = sptr; |
| for (; *addrend != '.'; ++addrend); |
| |
| if (!PV_atoi(sptr, 'd', addrend - sptr, address)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| |
| if (address >= 224 && address <= 239) //multicast address look for TTL |
| { |
| for (; (*sptr != '/') && (sptr < eptr); ++sptr); |
| if (sptr == eptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; // no TTL found in multicast address. |
| } |
| else |
| { |
| uint32 ttl; |
| sptr = sptr + 1; |
| if (!PV_atoi(sptr, 'd', eptr - sptr, ttl)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| if (!(ttl <= 255)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; // ttl out of range. |
| } |
| |
| } |
| |
| } |
| else // unicast address |
| { |
| for (; (*sptr != '/') && (sptr < eptr); ++sptr); |
| if (!oscl_strncmp(sptr, "/", 1)) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; //unicast address can not have TTL. |
| } |
| } |
| |
| if (eptr < line_end_ptr) |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad c= line format")); |
| return SDP_BAD_SESSION_FORMAT; |
| } |
| } |
| //use "len" here since "IP4" and "IP6" have same lengths |
| else if (oscl_strncmp(sptr, "IP6", len) == 0) |
| { |
| //TBD |
| } |
| break; |
| } |
| |
| default: |
| { |
| //skip a line we don't understand |
| } |
| break; |
| } |
| } |
| current_start = line_end_ptr; |
| } |
| |
| mediaStr->setmediaTrackId(alt_id); |
| |
| if (!alt_def_id && alt_id) |
| { |
| uint32 defaultId; |
| getAltDefaultId(buff, buff + index, defaultId); |
| if (defaultId != 0) |
| mediaStr->setalternateTrackId(defaultId); |
| else |
| return SDP_BAD_MEDIA_ALT_ID; |
| } |
| |
| if (!a_control_found) |
| { |
| uint32 addr; |
| connectionInfo ci; |
| mediaStr->getConnectionInformation(&ci); |
| PV_atoi(ci.connectionAddress.get_cstr(), 'd', addr); |
| //224.0.0.0 through 239.255.255.255 represent class D network addresses |
| //reserved for multicasting, which indicate a DVB connection |
| if (addr >= 224 && addr <= 239) |
| { |
| uint32 id = mediaStr->getMediaInfoID(); |
| mediaStr->setControlTrackID(id); |
| a_control_set = true; |
| } |
| } |
| |
| /* |
| * cannot assume that range is always going to be set at media level |
| */ |
| if ((isSipSdp && a_rtpmap_found) || (!alt_def_id && alt_id)) |
| return SDP_SUCCESS; |
| else if ((a_rtpmap_found && a_control_found) || (!alt_def_id && alt_id) || (a_control_set)) |
| return SDP_SUCCESS; |
| else |
| { |
| PVMF_SDP_PARSER_LOGERROR((0, "SDPBaseMediaInfoParser::parseMediaInfo - Bad Media - no rtpmap and control present")); |
| return SDP_BAD_MEDIA_FORMAT; |
| } |
| } |
| |
| |
| SDP_ERROR_CODE SDPBaseMediaInfoParser::getAltDefaultId(const char* start, const char *end, uint32 &defaultId) |
| { |
| const char *current_start = start; |
| const char *line_start_ptr, *line_end_ptr; |
| defaultId = 0; |
| while (get_next_line(current_start, end, |
| line_start_ptr, line_end_ptr)) |
| { |
| switch (*line_start_ptr) |
| { |
| case 'a': |
| { |
| if (!oscl_strncmp(line_start_ptr, "a=alt-default-id:", oscl_strlen("a=alt-default-id:"))) |
| { |
| line_start_ptr += oscl_strlen("a=alt-default-id:"); |
| |
| if (!PV_atoi(line_start_ptr, 'd', line_end_ptr - line_start_ptr, defaultId)) |
| return SDP_BAD_MEDIA_ALT_ID; |
| else |
| return SDP_SUCCESS; |
| |
| } |
| } |
| break; |
| default: |
| break; |
| |
| } |
| |
| current_start = line_end_ptr; |
| } |
| |
| return SDP_SUCCESS; |
| } |
| |
| SDP_ERROR_CODE SDPBaseMediaInfoParser::setDependentMediaId(const char *start, int length, mediaInfo *mediaPtr, int mediaId) |
| { |
| const char *startPtr = start; |
| const char *endLine = start + length; |
| |
| while (startPtr < endLine) |
| { |
| for (; *startPtr != '='; ++startPtr); |
| startPtr = startPtr + 1; |
| if (startPtr > endLine) |
| return SDP_BAD_MEDIA_ALT_ID; |
| const char *endPtr = startPtr; |
| for (; (*endPtr != ';') && (endPtr != endLine); ++endPtr); |
| |
| if (endPtr > endLine) |
| return SDP_BAD_MEDIA_ALT_ID; |
| |
| if (lookForMediaId(startPtr, endPtr, mediaId)) |
| { |
| while (startPtr < endPtr) |
| { |
| const char *end = startPtr; |
| for (; (*end != ',') && (end < endPtr) ; ++end); |
| uint32 id; |
| if (!PV_atoi(startPtr, 'd', end - startPtr, id)) |
| return SDP_BAD_MEDIA_ALT_ID; |
| if ((int)id != mediaId) |
| mediaPtr->setdependentTrackId(id); |
| startPtr = end + 1; |
| } |
| } |
| else |
| startPtr = endPtr + 1; |
| } |
| |
| return SDP_SUCCESS; |
| |
| } |
| |
| bool SDPBaseMediaInfoParser::lookForMediaId(const char *startPtr, const char* endPtr, int mediaId) |
| { |
| const char *end = startPtr; |
| |
| while (startPtr < endPtr) |
| { |
| for (; (*end != ',') && (end < endPtr); ++end); |
| uint32 id; |
| PV_atoi(startPtr, 'd' , end - startPtr, id); |
| if ((int)id == mediaId) |
| return true; |
| end = end + 1; |
| startPtr = end; |
| } |
| |
| return false; |
| } |
| |