diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java index 5a8db178..90b5291e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory; import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties; import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver; import gov.nist.javax.sip.SipProviderImpl; @@ -63,6 +64,7 @@ public class SipLayer implements CommandLineRunner { SipStackImpl sipStack; try { sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog())); + sipStack.setMessageParserFactory(new GbStringMsgParserFactory()); } catch (PeerUnavailableException e) { logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); return; @@ -75,7 +77,6 @@ public class SipLayer implements CommandLineRunner { tcpSipProvider.setDialogErrorsAutomaticallyHandled(); tcpSipProvider.addSipListener(sipProcessorObserver); tcpSipProviderMap.put(monitorIp, tcpSipProvider); - logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port); } catch (TransportNotSupportedException | TooManyListenersException diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java new file mode 100644 index 00000000..2abc3569 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java @@ -0,0 +1,457 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import gov.nist.core.CommonLogger; +import gov.nist.core.Host; +import gov.nist.core.HostNameParser; +import gov.nist.core.StackLogger; +import gov.nist.javax.sip.SIPConstants; +import gov.nist.javax.sip.address.AddressImpl; +import gov.nist.javax.sip.address.GenericURI; +import gov.nist.javax.sip.address.SipUri; +import gov.nist.javax.sip.address.TelephoneNumber; +import gov.nist.javax.sip.header.*; +import gov.nist.javax.sip.message.SIPMessage; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; +import gov.nist.javax.sip.parser.*; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; + +public class GBStringMsgParser implements MessageParser { + + protected static boolean computeContentLengthFromMessage = false; + + private static StackLogger logger = CommonLogger.getLogger(StringMsgParser.class); + + /** + * @since v0.9 + */ + public GBStringMsgParser() { + super(); + } + + /** + * Parse a buffer containing a single SIP Message where the body is an array + * of un-interpreted bytes. This is intended for parsing the message from a + * memory buffer when the buffer. Incorporates a bug fix for a bug that was + * noted by Will Sullin of Callcast + * + * @param msgBuffer + * a byte buffer containing the messages to be parsed. This can + * consist of multiple SIP Messages concatenated together. + * @return a SIPMessage[] structure (request or response) containing the + * parsed SIP message. + * @exception ParseException + * is thrown when an illegal message has been encountered + * (and the rest of the buffer is discarded). + * @see ParseExceptionListener + */ + public SIPMessage parseSIPMessage(byte[] msgBuffer, boolean readBody, boolean strict, ParseExceptionListener parseExceptionListener) throws ParseException { + if (msgBuffer == null || msgBuffer.length == 0) + return null; + + int i = 0; + + // Squeeze out any leading control character. + try { + while (msgBuffer[i] < 0x20) + i++; + } + catch (ArrayIndexOutOfBoundsException e) { + // Array contains only control char, return null. + if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) { + logger.logDebug("handled only control char so returning null"); + } + return null; + } + + // Iterate thru the request/status line and headers. + String currentLine = null; + String currentHeader = null; + boolean isFirstLine = true; + SIPMessage message = null; + do + { + int lineStart = i; + + // Find the length of the line. + try { + while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n') + i++; + } + catch (ArrayIndexOutOfBoundsException e) { + // End of the message. + break; + } + int lineLength = i - lineStart; + + // Make it a String. + try { + currentLine = new String(msgBuffer, lineStart, lineLength, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ParseException("Bad message encoding!", 0); + } + + currentLine = trimEndOfLine(currentLine); + + if (currentLine.length() == 0) { + // Last header line, process the previous buffered header. + if (currentHeader != null && message != null) { + processHeader(currentHeader, message, parseExceptionListener, msgBuffer); + } + + } + else { + if (isFirstLine) { + message = processFirstLine(currentLine, parseExceptionListener, msgBuffer); + } else { + char firstChar = currentLine.charAt(0); + if (firstChar == '\t' || firstChar == ' ') { + if (currentHeader == null) + throw new ParseException("Bad header continuation.", 0); + + // This is a continuation, append it to the previous line. + currentHeader += currentLine.substring(1); + } + else { + if (currentHeader != null && message != null) { + processHeader(currentHeader, message, parseExceptionListener, msgBuffer); + } + currentHeader = currentLine; + } + } + } + + if (msgBuffer[i] == '\r' && msgBuffer.length > i+1 && msgBuffer[i+1] == '\n') + i++; + + i++; + + isFirstLine = false; + } while (currentLine.length() > 0); // End do - while + + if (message == null) throw new ParseException("Bad message", 0); + message.setSize(i); + + // Check for content legth header + if (readBody && message.getContentLength() != null ) { + if ( message.getContentLength().getContentLength() != 0) { + int bodyLength = msgBuffer.length - i; + + byte[] body = new byte[bodyLength]; + System.arraycopy(msgBuffer, i, body, 0, bodyLength); + message.setMessageContent(body,!strict,computeContentLengthFromMessage,message.getContentLength().getContentLength()); + } else if (message.getCSeqHeader().getMethod().equalsIgnoreCase("MESSAGE")) { + int bodyLength = msgBuffer.length - i; + + byte[] body = new byte[bodyLength]; + System.arraycopy(msgBuffer, i, body, 0, bodyLength); + message.setMessageContent(body,!strict,computeContentLengthFromMessage,bodyLength); + }else if (!computeContentLengthFromMessage && strict) { + String last4Chars = new String(msgBuffer, msgBuffer.length - 4, 4); + if(!"\r\n\r\n".equals(last4Chars)) { + throw new ParseException("Extraneous characters at the end of the message ",i); + } + } + } + + return message; + } + + protected static String trimEndOfLine(String line) { + if (line == null) + return line; + + int i = line.length() - 1; + while (i >= 0 && line.charAt(i) <= 0x20) + i--; + + if (i == line.length() - 1) + return line; + + if (i == -1) + return ""; + + return line.substring(0, i+1); + } + + protected SIPMessage processFirstLine(String firstLine, ParseExceptionListener parseExceptionListener, byte[] msgBuffer) throws ParseException { + SIPMessage message; + if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) { + message = new SIPRequest(); + try { + RequestLine requestLine = new RequestLineParser(firstLine + "\n") + .parse(); + ((SIPRequest) message).setRequestLine(requestLine); + } catch (ParseException ex) { + if (parseExceptionListener != null) + try { + parseExceptionListener.handleException(ex, message, + RequestLine.class, firstLine, new String(msgBuffer, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + else + throw ex; + + } + } else { + message = new SIPResponse(); + try { + StatusLine sl = new StatusLineParser(firstLine + "\n").parse(); + ((SIPResponse) message).setStatusLine(sl); + } catch (ParseException ex) { + if (parseExceptionListener != null) { + try { + parseExceptionListener.handleException(ex, message, + StatusLine.class, firstLine, new String(msgBuffer, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } else + throw ex; + + } + } + return message; + } + + protected void processHeader(String header, SIPMessage message, ParseExceptionListener parseExceptionListener, byte[] rawMessage) throws ParseException { + if (header == null || header.length() == 0) + return; + + HeaderParser headerParser = null; + try { + headerParser = ParserFactory.createParser(header + "\n"); + } catch (ParseException ex) { + // https://java.net/jira/browse/JSIP-456 + if (parseExceptionListener != null) { + parseExceptionListener.handleException(ex, message, null, + header, null); + return; + } else { + throw ex; + } + } + + try { + SIPHeader sipHeader = headerParser.parse(); + message.attachHeader(sipHeader, false); + } catch (ParseException ex) { + if (parseExceptionListener != null) { + String headerName = Lexer.getHeaderName(header); + Class headerClass = NameMap.getClassFromName(headerName); + if (headerClass == null) { + headerClass = ExtensionHeaderImpl.class; + + } + try { + parseExceptionListener.handleException(ex, message, + headerClass, header, new String(rawMessage, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + } + } + } + + /** + * Parse an address (nameaddr or address spec) and return and address + * structure. + * + * @param address + * is a String containing the address to be parsed. + * @return a parsed address structure. + * @since v1.0 + * @exception ParseException + * when the address is badly formatted. + */ + public AddressImpl parseAddress(String address) throws ParseException { + AddressParser addressParser = new AddressParser(address); + return addressParser.address(true); + } + + /** + * Parse a host:port and return a parsed structure. + * + * @param hostport + * is a String containing the host:port to be parsed + * @return a parsed address structure. + * @since v1.0 + * @exception throws + * a ParseException when the address is badly formatted. + * + public HostPort parseHostPort(String hostport) throws ParseException { + Lexer lexer = new Lexer("charLexer", hostport); + return new HostNameParser(lexer).hostPort(); + + } + */ + + /** + * Parse a host name and return a parsed structure. + * + * @param host + * is a String containing the host name to be parsed + * @return a parsed address structure. + * @since v1.0 + * @exception ParseException + * a ParseException when the hostname is badly formatted. + */ + public Host parseHost(String host) throws ParseException { + Lexer lexer = new Lexer("charLexer", host); + return new HostNameParser(lexer).host(); + + } + + /** + * Parse a telephone number return a parsed structure. + * + * @param telephone_number + * is a String containing the telephone # to be parsed + * @return a parsed address structure. + * @since v1.0 + * @exception ParseException + * a ParseException when the address is badly formatted. + */ + public TelephoneNumber parseTelephoneNumber(String telephone_number) + throws ParseException { + // Bug fix contributed by Will Scullin + return new URLParser(telephone_number).parseTelephoneNumber(true); + + } + + /** + * Parse a SIP url from a string and return a URI structure for it. + * + * @param url + * a String containing the URI structure to be parsed. + * @return A parsed URI structure + * @exception ParseException + * if there was an error parsing the message. + */ + + public SipUri parseSIPUrl(String url) throws ParseException { + try { + return new URLParser(url).sipURL(true); + } catch (ClassCastException ex) { + throw new ParseException(url + " Not a SIP URL ", 0); + } + } + + /** + * Parse a uri from a string and return a URI structure for it. + * + * @param url + * a String containing the URI structure to be parsed. + * @return A parsed URI structure + * @exception ParseException + * if there was an error parsing the message. + */ + + public GenericURI parseUrl(String url) throws ParseException { + return new URLParser(url).parse(); + } + + /** + * Parse an individual SIP message header from a string. + * + * @param header + * String containing the SIP header. + * @return a SIPHeader structure. + * @exception ParseException + * if there was an error parsing the message. + */ + public static SIPHeader parseSIPHeader(String header) throws ParseException { + int start = 0; + int end = header.length() - 1; + try { + // Squeeze out any leading control character. + while (header.charAt(start) <= 0x20) + start++; + + // Squeeze out any trailing control character. + while (header.charAt(end) <= 0x20) + end--; + } + catch (ArrayIndexOutOfBoundsException e) { + // Array contains only control char. + throw new ParseException("Empty header.", 0); + } + + StringBuilder buffer = new StringBuilder(end + 1); + int i = start; + int lineStart = start; + boolean endOfLine = false; + while (i <= end) { + char c = header.charAt(i); + if (c == '\r' || c == '\n') { + if (!endOfLine) { + buffer.append(header.substring(lineStart, i)); + endOfLine = true; + } + } + else { + if (endOfLine) { + endOfLine = false; + if (c == ' ' || c == '\t') { + buffer.append(' '); + lineStart = i + 1; + } + else { + lineStart = i; + } + } + } + + i++; + } + buffer.append(header.substring(lineStart, i)); + buffer.append('\n'); + + HeaderParser hp = ParserFactory.createParser(buffer.toString()); + if (hp == null) + throw new ParseException("could not create parser", 0); + return hp.parse(); + } + + /** + * Parse the SIP Request Line + * + * @param requestLine + * a String containing the request line to be parsed. + * @return a RequestLine structure that has the parsed RequestLine + * @exception ParseException + * if there was an error parsing the requestLine. + */ + + public RequestLine parseSIPRequestLine(String requestLine) + throws ParseException { + requestLine += "\n"; + return new RequestLineParser(requestLine).parse(); + } + + /** + * Parse the SIP Response message status line + * + * @param statusLine + * a String containing the Status line to be parsed. + * @return StatusLine class corresponding to message + * @exception ParseException + * if there was an error parsing + * @see StatusLine + */ + + public StatusLine parseSIPStatusLine(String statusLine) + throws ParseException { + statusLine += "\n"; + return new StatusLineParser(statusLine).parse(); + } + + public static void setComputeContentLengthFromMessage( + boolean computeContentLengthFromMessage) { + GBStringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSipDate.java similarity index 97% rename from src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java rename to src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSipDate.java index f2a256b9..0631c5d0 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSipDate.java @@ -8,7 +8,7 @@ import java.util.*; /** * 重写jain sip的SIPDate解决与国标时间格式不一致的问题 */ -public class WvpSipDate extends SIPDate { +public class GbSipDate extends SIPDate { /** * @@ -17,7 +17,7 @@ public class WvpSipDate extends SIPDate { private Calendar javaCal; - public WvpSipDate(long timeMillis) { + public GbSipDate(long timeMillis) { this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault()); Date date = new Date(timeMillis); this.javaCal.setTime(date); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java new file mode 100644 index 00000000..3a9a1d19 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java @@ -0,0 +1,21 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import gov.nist.javax.sip.parser.MessageParser; +import gov.nist.javax.sip.parser.MessageParserFactory; +import gov.nist.javax.sip.stack.SIPTransactionStack; + +public class GbStringMsgParserFactory implements MessageParserFactory { + + /** + * msg parser is completely stateless, reuse isntance for the whole stack + * fixes https://github.com/RestComm/jain-sip/issues/92 + */ + private static GBStringMsgParser msgParser = new GBStringMsgParser(); + /* + * (non-Javadoc) + * @see gov.nist.javax.sip.parser.MessageParserFactory#createMessageParser(gov.nist.javax.sip.stack.SIPTransactionStack) + */ + public MessageParser createMessageParser(SIPTransactionStack stack) { + return msgParser; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java index e084f352..ceeb3b17 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java @@ -6,7 +6,7 @@ import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; -import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate; +import com.genersoft.iot.vmp.gb28181.bean.GbSipDate; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; @@ -145,8 +145,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen // 添加date头 SIPDateHeader dateHeader = new SIPDateHeader(); // 使用自己修改的 - WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); - dateHeader.setDate(wvpSipDate); + GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); + dateHeader.setDate(gbSipDate); response.addHeader(dateHeader); if (request.getExpires() == null) { @@ -218,8 +218,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen // 添加date头 SIPDateHeader dateHeader = new SIPDateHeader(); // 使用自己修改的 - WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); - dateHeader.setDate(wvpSipDate); + GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); + dateHeader.setDate(gbSipDate); response.addHeader(dateHeader); // 添加Contact头 diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java index 06dfb006..b182b265 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java @@ -470,7 +470,6 @@ public class DeviceQuery { public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId, @RequestParam(required = false) String mark) { try { - final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + (mark == null? ".jpg": ("_" + mark + ".jpg"))).toPath()); resp.setContentType(MediaType.IMAGE_PNG_VALUE); IOUtils.copy(in, resp.getOutputStream());