diff --git a/sql/初始化.sql b/sql/初始化.sql index f29a9242..ac8cbe5b 100644 --- a/sql/初始化.sql +++ b/sql/初始化.sql @@ -279,8 +279,50 @@ create table wvp_user_role ( update_time character varying(50) ); +create table wvp_sip_server ( + id serial primary key , + local_ip character varying(50) not null , + local_port integer, + server_ip character varying(50) not null , + server_port integer, + create_time character varying(50), + update_time character varying(50), + transport character varying(50), + status bool default false, + constraint uk_sip_server_server_ip_server_port unique (server_ip, server_port) +); + +create table wvp_sip_server_account ( + id serial primary key , + sip_server_id integer, + username character varying(50) not null , + password character varying(50) not null , + device_channel_id integer , + push_stream_id integer, + proxy_stream_id integer, + create_time character varying(50), + update_time character varying(50), + status bool default false, + constraint uk_sip_server_account_username unique (username) +); + +create table wvp_sip_video ( + id serial primary key , + sip_server_id integer, + sip_account_id integer, + media_server_id character varying(50) not null , + request_no character varying(50) not null , + create_time character varying(50), + update_time character varying(50), + auto_reconnect_on_reboot bool default false, + status bool default false, + constraint uk_sip_server_account_username unique (request_no) +); + + /*初始数据*/ INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3'); + INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57'); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java b/src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java index eb408ab8..3209eee1 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/VersionInfo.java @@ -11,7 +11,7 @@ public class VersionInfo { @Autowired GitUtil gitUtil; - public VersionPo getVersion() { + public VersionPo getVersion () { VersionPo versionPo = new VersionPo(); versionPo.setGIT_Revision(gitUtil.getGitCommitId()); versionPo.setGIT_BRANCH(gitUtil.getBranch()); 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 78238462..5c84b797 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -64,7 +64,7 @@ public class SipLayer implements CommandLineRunner { try { sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog())); } catch (PeerUnavailableException e) { - logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); + logger.error("[国标28181服务] 启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); return; } @@ -76,12 +76,12 @@ public class SipLayer implements CommandLineRunner { tcpSipProvider.addSipListener(sipProcessorObserver); tcpSipProviderMap.put(monitorIp, tcpSipProvider); - logger.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port); + logger.info("[国标28181服务] tcp://{}:{} 启动成功", monitorIp, port); } catch (TransportNotSupportedException | TooManyListenersException | ObjectInUseException | InvalidArgumentException e) { - logger.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" + logger.error("[国标28181服务] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" , monitorIp, port); } @@ -93,12 +93,12 @@ public class SipLayer implements CommandLineRunner { udpSipProviderMap.put(monitorIp, udpSipProvider); - logger.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port); + logger.info("[国标28181服务] udp://{}:{} 启动成功", monitorIp, port); } catch (TransportNotSupportedException | TooManyListenersException | ObjectInUseException | InvalidArgumentException e) { - logger.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" + logger.error("[国标28181服务] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" , monitorIp, port); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java index c04a6959..bd16f161 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java @@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.gb28181.bean; -import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; public class SipTransactionInfo { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java index a7ce8c0b..40b4c27f 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java @@ -50,10 +50,6 @@ public class SipUtils { return uri.getUser(); } - public static String getNewViaTag() { - return "z9hG4bK" + System.currentTimeMillis(); - } - public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException { List agentParam = new ArrayList<>(); agentParam.add("WVP-Pro "); @@ -69,6 +65,10 @@ public class SipUtils { return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam); } + public static String getNewViaTag() { + return "z9hG4bK" + System.currentTimeMillis(); + } + public static String getNewFromTag(){ return UUID.randomUUID().toString().replace("-", ""); diff --git a/src/main/java/com/genersoft/iot/vmp/sip/SipCommander.java b/src/main/java/com/genersoft/iot/vmp/sip/SipCommander.java new file mode 100644 index 00000000..373b6698 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/SipCommander.java @@ -0,0 +1,42 @@ +package com.genersoft.iot.vmp.sip; + +import com.genersoft.iot.vmp.sip.bean.SipEvent; +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import com.genersoft.iot.vmp.sip.utils.SIPRequestFactory; +import com.genersoft.iot.vmp.sip.utils.SipUtils; +import gov.nist.javax.sip.SipProviderImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import javax.sip.header.WWWAuthenticateHeader; +import javax.sip.message.Request; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; + +@Component +public class SipCommander { + + @Autowired + private SipSender sipSender; + + public void register(SipServer server, SipServerAccount account, SipProviderImpl sipProvider, boolean isRegister, + SipEvent okEvent, SipEvent errorEvent) + throws InvalidArgumentException, SipException, ParseException, NoSuchAlgorithmException { + register(server, account, sipProvider, null, isRegister, okEvent, errorEvent); + } + + public void register(SipServer server, SipServerAccount account, SipProviderImpl sipProvider, + WWWAuthenticateHeader wwwAuthenticateHeader, boolean isRegister, + SipEvent okEvent, SipEvent errorEvent) + throws InvalidArgumentException, SipException, ParseException, NoSuchAlgorithmException { + if (server == null || account == null || sipProvider == null) { + return; + } + String callId = SipUtils.getNewCallId(sipProvider); + Request request = SIPRequestFactory.createRegisterRequest(server, account, callId, isRegister, wwwAuthenticateHeader); + sipSender.transmitRequest(sipProvider, request, okEvent, errorEvent); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/SipSender.java b/src/main/java/com/genersoft/iot/vmp/sip/SipSender.java new file mode 100755 index 00000000..349ed747 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/SipSender.java @@ -0,0 +1,73 @@ +package com.genersoft.iot.vmp.sip; + +import com.genersoft.iot.vmp.sip.bean.SipEvent; +import com.genersoft.iot.vmp.sip.service.SipSubscribe; +import com.genersoft.iot.vmp.sip.utils.SipUtils; +import com.genersoft.iot.vmp.utils.GitUtil; +import gov.nist.javax.sip.SipProviderImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.SipException; +import javax.sip.header.CallIdHeader; +import javax.sip.header.UserAgentHeader; +import javax.sip.message.Message; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.text.ParseException; + +/** + * 消息发送器 + * @author lin + */ +@Component +public class SipSender { + + private Logger logger = LoggerFactory.getLogger(SipSender.class); + + @Autowired + private GitUtil gitUtil; + + public void transmitRequest(SipProviderImpl sipProvider, Message message) throws SipException, ParseException { + transmitRequest(sipProvider, message, null, null); + } + + public void transmitRequest(SipProviderImpl sipProvider, String ip, Message message, SipEvent errorEvent) throws SipException, ParseException { + transmitRequest(sipProvider, message, null, errorEvent ); + } + + public void transmitRequest(SipProviderImpl sipProvider, Message message, SipEvent okEvent, SipEvent errorEvent) throws SipException, ParseException { + if (message.getHeader(UserAgentHeader.NAME) == null) { + try { + message.addHeader(SipUtils.createUserAgentHeader(gitUtil)); + } catch (ParseException e) { + logger.error("添加UserAgentHeader失败", e); + } + } + + CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); + // 添加错误订阅 + if (errorEvent != null) { + SipSubscribe.getInstance().addErrorSubscribe(callIdHeader.getCallId(), (code, msg, data) -> { + errorEvent.response(code, msg, data); + SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId()); + SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId()); + }); + } + // 添加订阅 + if (okEvent != null) { + SipSubscribe.getInstance().addOkSubscribe(callIdHeader.getCallId(), (code, msg, data) -> { + okEvent.response(code, msg, data); + SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId()); + SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId()); + }); + } + if (message instanceof Request) { + sipProvider.sendRequest((Request)message); + }else if (message instanceof Response) { + sipProvider.sendResponse((Response)message); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/SipSessionManager.java b/src/main/java/com/genersoft/iot/vmp/sip/SipSessionManager.java new file mode 100644 index 00000000..92b37a0c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/SipSessionManager.java @@ -0,0 +1,9 @@ +package com.genersoft.iot.vmp.sip; + +import org.springframework.stereotype.Component; + +@Component +public class SipSessionManager { + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/ResultCallback.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/ResultCallback.java new file mode 100644 index 00000000..3495f098 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/ResultCallback.java @@ -0,0 +1,6 @@ +package com.genersoft.iot.vmp.sip.bean; + +public interface ResultCallback { + + void run(int code, String msg, T data); +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/SipEvent.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipEvent.java new file mode 100644 index 00000000..4096da84 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipEvent.java @@ -0,0 +1,6 @@ +package com.genersoft.iot.vmp.sip.bean; + +public interface SipEvent { + + void response(int code, String msg, Object data); +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/SipResult.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipResult.java new file mode 100644 index 00000000..e2a4ccbf --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipResult.java @@ -0,0 +1,4 @@ +package com.genersoft.iot.vmp.sip.bean; + +public class SipResult { +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServer.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServer.java new file mode 100644 index 00000000..ab5bf366 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServer.java @@ -0,0 +1,132 @@ +package com.genersoft.iot.vmp.sip.bean; + +/** + * sip系统的帐号信息 + */ +public class SipServer { + + private int id; + + /** + * 本机IP + */ + private String localIp; + + /** + * 本机端口 + */ + private Integer localPort; + + /** + * 服务器IP + */ + private String serverIp; + + /** + * 服务器端口 + */ + private Integer serverPort; + + /** + * 信令传输模式, 默认UDP, 可选UDP/TCP + */ + private String transport; + + /** + * 状态 + */ + private boolean status; + + /** + * 创建时间 + */ + private String createTime; + + /** + * 更新时间 + */ + private String updateTime; + + + public static SipServer getInstance(String serverIp, Integer serverPort, String localIp, Integer localPort, String transport) { + SipServer sipServer = new SipServer(); + sipServer.setTransport(transport); + sipServer.setServerIp(serverIp); + sipServer.setServerPort(serverPort); + sipServer.setLocalIp(localIp); + sipServer.setLocalPort(localPort); + return sipServer; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getLocalIp() { + return localIp; + } + + public void setLocalIp(String localIp) { + this.localIp = localIp; + } + + public Integer getLocalPort() { + return localPort; + } + + public void setLocalPort(Integer localPort) { + this.localPort = localPort; + } + + public String getServerIp() { + return serverIp; + } + + public void setServerIp(String serverIp) { + this.serverIp = serverIp; + } + + public Integer getServerPort() { + return serverPort; + } + + public void setServerPort(Integer serverPort) { + this.serverPort = serverPort; + } + + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public boolean isStatus() { + return status; + } + + public void setStatus(boolean status) { + this.status = status; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServerAccount.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServerAccount.java new file mode 100644 index 00000000..b1672970 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipServerAccount.java @@ -0,0 +1,163 @@ +package com.genersoft.iot.vmp.sip.bean; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * sip用户帐号,用于接收sip视频以及推送视频到sip服务中 + */ +public class SipServerAccount { + + /** + * ID + */ + private Integer id; + + /** + * 关联的SIP服务器ID + */ + private Integer sipServerId; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 关联一个国标设备作为帐号视频,用于将自己的视频加入到sip会议中 + * deviceChannelId/pushStreamId/proxyStreamId任选其一 + */ + private Integer deviceChannelId; + + /** + * 关联一个推流设备作为帐号视频,用于将自己的视频加入到sip会议中 + * deviceChannelId/pushStreamId/proxyStreamId任选其一 + */ + private Integer pushStreamId; + + /** + * 关联一个拉流代理作为帐号视频,用于将自己的视频加入到sip会议中 + * deviceChannelId/pushStreamId/proxyStreamId任选其一 + */ + private Integer proxyStreamId; + + /** + * 状态 + */ + private boolean status; + + /** + * 创建时间 + */ + private String createTime; + + /** + * 更新时间 + */ + private String updateTime; + + private final List sipVideoList = new CopyOnWriteArrayList<>(); + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getSipServerId() { + return sipServerId; + } + + public void setSipServerId(Integer sipServerId) { + this.sipServerId = sipServerId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getDeviceChannelId() { + return deviceChannelId; + } + + public void setDeviceChannelId(Integer deviceChannelId) { + this.deviceChannelId = deviceChannelId; + } + + public Integer getPushStreamId() { + return pushStreamId; + } + + public void setPushStreamId(Integer pushStreamId) { + this.pushStreamId = pushStreamId; + } + + public Integer getProxyStreamId() { + return proxyStreamId; + } + + public void setProxyStreamId(Integer proxyStreamId) { + this.proxyStreamId = proxyStreamId; + } + + public boolean isStatus() { + return status; + } + + public void setStatus(boolean status) { + this.status = status; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } + + public void addVideo(SipVideo sipVideo) { + sipVideoList.add(sipVideo); + } + + public void removeVideoById(int id) { + if (sipVideoList.size() == 0) { + return; + } + for (int i = 0; i < sipVideoList.size(); i++) { + if (sipVideoList.get(i).getId() == id) { + sipVideoList.remove(i); + return; + } + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/bean/SipVideo.java b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipVideo.java new file mode 100644 index 00000000..ca7a8bad --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/bean/SipVideo.java @@ -0,0 +1,101 @@ +package com.genersoft.iot.vmp.sip.bean; + +/** + * 从一个sip服务中获取一个视频 + */ +public class SipVideo { + + /** + * ID + */ + private int id; + + /** + * sip 服务器帐号信息ID + */ + private int sipAccountId; + + /** + * 指定使用的流媒体,为空则自动获取 + */ + private String mediaServerId; + + /** + * 从sip服务中获取的视频的设备编号 + */ + private String requestNo; + + /** + * 服务重启时释放自动拉起视频 + */ + private boolean autoReconnectOnReboot; + + /** + * 创建时间 + */ + private String createTime; + + /** + * 更新时间 + */ + private String updateTime; + + + + + public int getSipAccountId() { + return sipAccountId; + } + + public void setSipAccountId(int sipAccountId) { + this.sipAccountId = sipAccountId; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getRequestNo() { + return requestNo; + } + + public void setRequestNo(String requestNo) { + this.requestNo = requestNo; + } + + public boolean isAutoReconnectOnReboot() { + return autoReconnectOnReboot; + } + + public void setAutoReconnectOnReboot(boolean autoReconnectOnReboot) { + this.autoReconnectOnReboot = autoReconnectOnReboot; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerAccountMapper.java b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerAccountMapper.java new file mode 100644 index 00000000..ba367dc6 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerAccountMapper.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.sip.dao; + +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import org.apache.ibatis.annotations.*; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Mapper +@Repository +public interface SipServerAccountMapper { + + @Insert("INSERT INTO wvp_sip_server_account (sip_server_id, username, password, device_channel_id, " + + "create_time, update_time, push_stream_id, proxy_stream_id, status ) " + + "VALUES (#{sipServerId}, #{username}, #{password}, #{deviceChannelId}, " + + "#{createTime}, #{updateTime}, #{pushStreamId}, #{proxyStreamId}, #{status})") + int add(SipServerAccount sipServerAccount); + + @Delete("DELETE FROM wvp_sip_server_account WHERE id = #{id}") + int remove(int id); + + @Update(value = {" "}) + int update(SipServerAccount sipServerAccount); + + @Select("SELECT * FROM wvp_sip_server_account WHERE id = #{id}") + SipServerAccount query(int id); + + @Select("SELECT * FROM wvp_sip_server_account") + List all(int id); +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerMapper.java b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerMapper.java new file mode 100644 index 00000000..9fcd467e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipServerMapper.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.sip.dao; + +import com.genersoft.iot.vmp.sip.bean.SipServer; +import org.apache.ibatis.annotations.*; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Mapper +@Repository +public interface SipServerMapper { + + @Insert("INSERT INTO wvp_sip_server (local_ip, local_port, server_ip, server_port, create_time, update_time, transport, status ) " + + "VALUES (#{localIp}, #{localPort}, #{serverIp}, #{serverPort}, #{createTime}, #{updateTime}, #{transport}, #{status})") + int add(SipServer sipServer); + + @Delete("DELETE FROM wvp_sip_server WHERE id = #{sipServerId}") + int remove(int sipServerId); + + @Update(value = {" "}) + int update(SipServer sipServer); + + @Select("SELECT * FROM wvp_sip_server WHERE id = #{sipServerId}") + SipServer query(int sipServerId); + + @Select("SELECT * FROM wvp_sip_server WHERE id = #{sipServerId}") + List all(); + + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/dao/SipVideoMapper.java b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipVideoMapper.java new file mode 100644 index 00000000..418b9b75 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/dao/SipVideoMapper.java @@ -0,0 +1,33 @@ +package com.genersoft.iot.vmp.sip.dao; + +import com.genersoft.iot.vmp.sip.bean.SipVideo; +import org.apache.ibatis.annotations.*; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Mapper +@Repository +public interface SipVideoMapper { + + @Select("SELECT * FROM wvp_sip_video WHERE sip_server_id = #{serverId} AND sip_account_id = #{accountId}") + List all(int serverId, int accountId); + + @Insert("INSERT INTO wvp_sip_video (sip_server_id, sip_account_id, media_server_id, request_no, create_time, update_time, auto_reconnect_on_reboot, status ) " + + "VALUES (#{sipServerId}, #{sipAccountId}, #{mediaServerId}, #{requestNo}, #{createTime}, #{updateTime}, #{autoReconnectOnReboot}, #{status})") + int add(SipVideo sipVideo); + + @Update(value = {" "}) + int update(SipVideo video); + + @Delete("DELETE FROM wvp_sip_video WHERE id = #{videoId}") + void remove(Integer videoId); +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/ISIPResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/sip/service/ISIPResponseProcessor.java new file mode 100644 index 00000000..f2300ea9 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/ISIPResponseProcessor.java @@ -0,0 +1,11 @@ +package com.genersoft.iot.vmp.sip.service; + +import javax.sip.ResponseEvent; + + +public interface ISIPResponseProcessor { + + void process(ResponseEvent evt); + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/ISipService.java b/src/main/java/com/genersoft/iot/vmp/sip/service/ISipService.java new file mode 100644 index 00000000..8b5bb56d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/ISipService.java @@ -0,0 +1,99 @@ +package com.genersoft.iot.vmp.sip.service; + +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import com.genersoft.iot.vmp.sip.bean.SipVideo; +import com.github.pagehelper.PageInfo; + +/** + * 接入SIP系统的接口 + */ +public interface ISipService { + + /** + * 添加服务器信息 + */ + SipServer getSipServer(int sipServerId); + + /** + * 添加服务器信息 + */ + void addSipServer(SipServer sipServer); + + /** + * 删除服务器信息 + */ + void removeSipServer(int sipServerId); + + /** + * 更新服务器信息 + */ + void updateSipServer(SipServer sipServer); + + /** + * 开始连接服务器 + */ + void startSipServer(int sipServerId, SipServerAccount account, SipVideo video); + + /** + * 与服务器断开连接 + */ + void stopSipServer(int sipServerId); + + /** + * 服务器上线 + */ + void sipServerOnline(int sipServerId); + + /** + * 服务器下线 + */ + void sipServerOffline(int sipServerId); + + + /** + * 分页获取服务器列表 + */ + PageInfo getServerList(int page, int count); + + + /** + * 添加一个sip视频 + */ + void addSipVideo(SipVideo sipVideo); + + /** + * 获取帐号列表 + */ + PageInfo getAccountList(int serverId, Integer page, Integer count); + + /** + * 获取视频列表 + */ + PageInfo getVideoList(int serverId, int accountId, Integer page, Integer count); + + /** + * 增加帐号 + */ + void addSipServerAccount(SipServerAccount account); + + /** + * 更新帐号 + */ + void updateSipServerAccount(SipServerAccount account); + + /** + * 移除帐号 + */ + void removeSipServerAccount(Integer accountId); + + /** + * 更新SIP推送视频 + */ + void updateSipVideo(SipVideo video); + + /** + * 移除 + */ + void removeSipVideo(Integer videoId); +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipListenerImpl.java b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipListenerImpl.java new file mode 100755 index 00000000..022ae1a7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipListenerImpl.java @@ -0,0 +1,107 @@ +package com.genersoft.iot.vmp.sip.service.Impl; + + +import com.genersoft.iot.vmp.sip.bean.SipEvent; +import com.genersoft.iot.vmp.sip.service.ISIPResponseProcessor; +import com.genersoft.iot.vmp.sip.service.SipSubscribe; +import gov.nist.javax.sip.message.SIPResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.sip.*; +import javax.sip.header.CSeqHeader; +import javax.sip.header.CallIdHeader; +import javax.sip.message.Response; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 监听sip消息 + * @author lin + */ + +@Service +public class SipListenerImpl implements SipListener { + + + private final static Logger log = LoggerFactory.getLogger(SipListenerImpl.class); + + private Map responseProcessorMap = new ConcurrentHashMap<>(); + + @Override + @Async("taskExecutor") + public void processRequest(RequestEvent requestEvent) { + String method = requestEvent.getRequest().getMethod(); + // TODO 暂不处理 +// if (method.equalsIgnoreCase("")) { +// +// } + + } + + @Override + @Async("taskExecutor") + public void processResponse(ResponseEvent responseEvent) { + SIPResponse response = (SIPResponse) responseEvent.getResponse(); + int status = response.getStatusCode(); + + // Success + if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) { + CSeqHeader cseqHeader = (CSeqHeader) responseEvent.getResponse().getHeader(CSeqHeader.NAME); + String method = cseqHeader.getMethod(); + ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(method); + if (sipRequestProcessor != null) { + sipRequestProcessor.process(responseEvent); + } + if (status != Response.UNAUTHORIZED && responseEvent.getResponse() != null && SipSubscribe.getInstance().getOkSubscribesSize() > 0 ) { + CallIdHeader callIdHeader = (CallIdHeader)responseEvent.getResponse().getHeader(CallIdHeader.NAME); + if (callIdHeader != null) { + SipEvent subscribe = SipSubscribe.getInstance().getOkSubscribe(callIdHeader.getCallId()); + if (subscribe != null) { + SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId()); + subscribe.response(response.getStatusCode(), response.getReasonPhrase(), responseEvent); + } + } + } + } else if ((status >= Response.TRYING) && (status < Response.OK)) { + // 增加其它无需回复的响应,如101、180等 + } else { + log.warn("[SIP] 接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()); + if (responseEvent.getResponse() != null && SipSubscribe.getInstance().getErrorSubscribesSize() > 0 ) { + CallIdHeader callIdHeader = (CallIdHeader)responseEvent.getResponse().getHeader(CallIdHeader.NAME); + if (callIdHeader != null) { + SipEvent subscribe = SipSubscribe.getInstance().getErrorSubscribe(callIdHeader.getCallId()); + if (subscribe != null) { + SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId()); + subscribe.response(response.getStatusCode(), response.getReasonPhrase(), null); + } + } + } + if (responseEvent.getDialog() != null) { + responseEvent.getDialog().delete(); + } + } + } + + @Override + public void processTimeout(TimeoutEvent timeoutEvent) { + + } + + @Override + public void processIOException(IOExceptionEvent exceptionEvent) { + + } + + @Override + public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { + + } + + @Override + public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipSdkImpl.java b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipSdkImpl.java new file mode 100644 index 00000000..8794af51 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipSdkImpl.java @@ -0,0 +1,135 @@ +package com.genersoft.iot.vmp.sip.service.Impl; + +import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties; +import com.genersoft.iot.vmp.sip.SipCommander; +import com.genersoft.iot.vmp.sip.bean.ResultCallback; +import com.genersoft.iot.vmp.sip.bean.SipEvent; +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import com.genersoft.iot.vmp.sip.service.SipSdk; +import com.genersoft.iot.vmp.sip.utils.SipUtils; +import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.SipStackImpl; +import gov.nist.javax.sip.message.SIPResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.sip.*; +import javax.sip.header.WWWAuthenticateHeader; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.Map; +import java.util.TooManyListenersException; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class SipSdkImpl implements SipSdk { + + private Logger logger = LoggerFactory.getLogger(SipSdkImpl.class); + + private Map sipProviderMap = new ConcurrentHashMap<>(); + + @Autowired + private SipListener sipListenerImpl; + + @Autowired + private SipCommander sipCommander; + + @Override + public void register(SipServer sipServer, SipServerAccount account, ResultCallback callback) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException { + + if (sipServer == null || account == null) { + return; + } + if (sipServer.getLocalIp() == null) { + sipServer.setLocalIp("0.0.0.0"); + } + if (sipServer.getLocalPort() == null) { + if (sipServer.getTransport().equalsIgnoreCase("UDP")) { + sipServer.setLocalPort(SipUtils.getRandomUdpPort()); + }else { + sipServer.setLocalPort(SipUtils.getRandomTcpPort()); + } + } + SipProviderImpl sipProvider = initSipLister(sipServer); + try { + SipEvent successEvent = (code, msg, data)->{ + // 注册成功后开启定时任务 + }; + + sipCommander.register(sipServer, account, sipProvider, true, (code, msg, data)->{ + if (code == 200) { + // 注册成功 + if (callback != null) { + callback.run(code, msg, data); + } + successEvent.response(code, msg, data); + }else if (code == 401) { + ResponseEvent responseEvent = (ResponseEvent) data; + SIPResponse response = (SIPResponse) responseEvent.getResponse(); + WWWAuthenticateHeader authenticateHeader = (WWWAuthenticateHeader) response.getHeader(WWWAuthenticateHeader.NAME); + try { + sipCommander.register(sipServer, account, sipProvider, authenticateHeader, true, (code1, msg1, data1)->{ + if (code1 == 200) { + // 注册成功 + if (callback != null) { + callback.run(code1, msg1, data1); + } + successEvent.response(code1, msg1, data1); + }else { + // 注册失败 + if (callback != null) { + callback.run(code1, msg1, data1); + } + } + + }, (code2, msg2, data2)->{ + // 注册失败 + if (callback != null) { + callback.run(code2, msg2, data2); + } + }); + } catch (SipException | ParseException | NoSuchAlgorithmException| InvalidArgumentException e) { + logger.warn("[SIP] 发送注册消息失败", e); + if (callback != null) { + callback.run(-1, e.getMessage(), null); + } + } + }else { + // 注册失败 + if (callback != null) { + callback.run(code, msg, data); + } + } + + }, (code, msg, data)->{ + + // 注册失败 + if (callback != null) { + callback.run(code, msg, data); + } + }); + } catch (SipException | ParseException | NoSuchAlgorithmException e) { + logger.warn("[SIP] 发送注册消息失败", e); + if (callback != null) { + callback.run(-1, e.getMessage(), null); + } + } + + } + + private SipProviderImpl initSipLister(SipServer sipServer) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException { + SipFactory.getInstance().setPathName("gov.nist"); + SipStackImpl sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack( + DefaultProperties.getProperties(sipServer.getLocalIp(), false)); + + ListeningPoint listeningPoint = sipStack.createListeningPoint(sipServer.getLocalIp(), sipServer.getLocalPort(), sipServer.getTransport()); + SipProviderImpl sipProvider = (SipProviderImpl)sipStack.createSipProvider(listeningPoint); + sipProvider.setDialogErrorsAutomaticallyHandled(); + sipProvider.addSipListener(sipListenerImpl); + sipProviderMap.put(sipServer.getLocalIp() + ":" + sipServer.getLocalPort(), sipProvider); + return sipProvider; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipServiceImpl.java new file mode 100644 index 00000000..9737eeb1 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/Impl/SipServiceImpl.java @@ -0,0 +1,162 @@ +package com.genersoft.iot.vmp.sip.service.Impl; + +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import com.genersoft.iot.vmp.sip.bean.SipVideo; +import com.genersoft.iot.vmp.sip.dao.SipServerAccountMapper; +import com.genersoft.iot.vmp.sip.dao.SipServerMapper; +import com.genersoft.iot.vmp.sip.dao.SipVideoMapper; +import com.genersoft.iot.vmp.sip.service.ISipService; +import com.genersoft.iot.vmp.sip.service.SipSdk; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.sip.InvalidArgumentException; +import javax.sip.ObjectInUseException; +import javax.sip.PeerUnavailableException; +import javax.sip.TransportNotSupportedException; +import java.util.List; +import java.util.TooManyListenersException; + +@Service +public class SipServiceImpl implements ISipService { + + private final static Logger logger = LoggerFactory.getLogger(SipServiceImpl.class); + + + @Autowired + private SipServerMapper serverMapper; + + @Autowired + private SipServerAccountMapper accountMapper; + + @Autowired + private SipVideoMapper videoMapper; + + @Autowired + private SipSdk sipSdk; + + + @Override + public SipServer getSipServer(int sipServerId) { + return serverMapper.query(sipServerId); + } + + @Override + public void addSipServer(SipServer sipServer) { + serverMapper.add(sipServer); + } + + @Override + public void removeSipServer(int sipServerId) { + serverMapper.remove(sipServerId); + } + + @Override + public void updateSipServer(SipServer sipServer) { + serverMapper.update(sipServer); + } + + @Override + public void startSipServer(int sipServerId, SipServerAccount account, SipVideo video) { + SipServer sipServer = getSipServer(sipServerId); + if (sipServer == null){ + return; + } + if (account.getId() == null) { + + } + + } + + @Override + public void stopSipServer(int sipAccountId) { + + } + + @Override + public void sipServerOnline(int sipAccountId) { + + } + + @Override + public void sipServerOffline(int sipAccountId) { + + } + + @Override + public PageInfo getServerList(int page, int count) { + PageHelper.startPage(page, count); + List all = serverMapper.all(); + return new PageInfo<>(all); + } + + @Override + public void addSipVideo(SipVideo sipVideo) { + videoMapper.add(sipVideo); + } + + @Override + public PageInfo getAccountList(int serverId, Integer page, Integer count) { + PageHelper.startPage(page, count); + List all = accountMapper.all(serverId); + return new PageInfo<>(all); + } + + @Override + public PageInfo getVideoList(int serverId, int accountId, Integer page, Integer count) { + PageHelper.startPage(page, count); + List all = videoMapper.all(serverId, accountId); + return new PageInfo<>(all); + } + + @Override + public void addSipServerAccount(SipServerAccount account) { + if (account.getSipServerId() == null) { + return; + } + SipServer server = serverMapper.query(account.getSipServerId()); + if (server == null) { + return; + } + accountMapper.add(account); + try { + sipSdk.register(server, account, (code, msg, data) -> { + if (code == 200) { + + }else { + + } + }); + } catch (PeerUnavailableException | TransportNotSupportedException | InvalidArgumentException | + ObjectInUseException | TooManyListenersException e) { + + } + + + } + + @Override + public void updateSipServerAccount(SipServerAccount account) { + accountMapper.update(account); + } + + @Override + public void removeSipServerAccount(Integer accountId) { + accountMapper.remove(accountId); + } + + @Override + public void updateSipVideo(SipVideo video) { + videoMapper.update(video); + } + + @Override + public void removeSipVideo(Integer videoId) { + videoMapper.remove(videoId); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/SipSdk.java b/src/main/java/com/genersoft/iot/vmp/sip/service/SipSdk.java new file mode 100644 index 00000000..69c7a93b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/SipSdk.java @@ -0,0 +1,19 @@ +package com.genersoft.iot.vmp.sip.service; + +import com.genersoft.iot.vmp.sip.bean.ResultCallback; +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; + +import javax.sip.InvalidArgumentException; +import javax.sip.ObjectInUseException; +import javax.sip.PeerUnavailableException; +import javax.sip.TransportNotSupportedException; +import java.util.TooManyListenersException; + +/** + * java实现SIP SDK + */ +public interface SipSdk { + + void register(SipServer sipServer, SipServerAccount account, ResultCallback callback) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException; +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/service/SipSubscribe.java b/src/main/java/com/genersoft/iot/vmp/sip/service/SipSubscribe.java new file mode 100755 index 00000000..754f6f49 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/service/SipSubscribe.java @@ -0,0 +1,80 @@ +package com.genersoft.iot.vmp.sip.service; + + +import com.genersoft.iot.vmp.sip.bean.SipEvent; + +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author lin + */ + +public class SipSubscribe { + + private static SipSubscribe instance; + + private SipSubscribe() {} + + public static SipSubscribe getInstance() { + if (instance == null) { + synchronized (SipSubscribe.class) { + if (instance == null) { + instance = new SipSubscribe(); + } + } + } + + return instance; + } + + private Map errorSubscribes = new ConcurrentHashMap<>(); + + private Map okSubscribes = new ConcurrentHashMap<>(); + + private Map okTimeSubscribes = new ConcurrentHashMap<>(); + + private Map errorTimeSubscribes = new ConcurrentHashMap<>(); + + + public void addErrorSubscribe(String key, SipEvent event) { + errorSubscribes.put(key, event); + errorTimeSubscribes.put(key, Instant.now()); + } + + public void addOkSubscribe(String key, SipEvent event) { + okSubscribes.put(key, event); + okTimeSubscribes.put(key, Instant.now()); + } + + public SipEvent getErrorSubscribe(String key) { + return errorSubscribes.get(key); + } + + public void removeErrorSubscribe(String key) { + if(key == null){ + return; + } + errorSubscribes.remove(key); + errorTimeSubscribes.remove(key); + } + + public SipEvent getOkSubscribe(String key) { + return okSubscribes.get(key); + } + + public void removeOkSubscribe(String key) { + if(key == null){ + return; + } + okSubscribes.remove(key); + okTimeSubscribes.remove(key); + } + public int getErrorSubscribesSize(){ + return errorSubscribes.size(); + } + public int getOkSubscribesSize(){ + return okSubscribes.size(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/utils/DigestClientAuthenticationHelper.java b/src/main/java/com/genersoft/iot/vmp/sip/utils/DigestClientAuthenticationHelper.java new file mode 100644 index 00000000..ba372e0f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/utils/DigestClientAuthenticationHelper.java @@ -0,0 +1,214 @@ +package com.genersoft.iot.vmp.sip.utils; + +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; +import javax.sip.address.SipURI; +import javax.sip.address.URI; +import javax.sip.header.AuthorizationHeader; +import javax.sip.message.Request; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.time.Instant; +import java.util.Random; +import java.util.UUID; + +public class DigestClientAuthenticationHelper { + private Logger logger = LoggerFactory.getLogger(DigestClientAuthenticationHelper.class); + + public static final String DEFAULT_ALGORITHM = "MD5"; + public static final String DEFAULT_SCHEME = "Digest"; + + private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + + public static String toHexString(byte b[]) { + int pos = 0; + char[] c = new char[b.length * 2]; + for (int i = 0; i < b.length; i++) { + c[pos++] = toHex[(b[i] >> 4) & 0x0F]; + c[pos++] = toHex[b[i] & 0x0f]; + } + return new String(c); + } + + /** + * Generate the challenge string. + * + * @return a generated nonce. + */ + private static String generateNonce() throws NoSuchAlgorithmException { + long time = Instant.now().toEpochMilli(); + Random rand = new Random(); + long pad = rand.nextLong(); + String nonceString = Long.valueOf(time).toString() + + Long.valueOf(pad).toString(); + byte mdbytes[] = MessageDigest.getInstance(DEFAULT_ALGORITHM).digest(nonceString.getBytes()); + return toHexString(mdbytes); + } + + public static AuthorizationHeader getAuthorizationHeader(SipServer server, SipServerAccount account) + throws PeerUnavailableException, ParseException, NoSuchAlgorithmException { + return getAuthorizationHeader(server, account, null, null, null, null); + } + + public static AuthorizationHeader getAuthorizationHeader(SipServer server, SipServerAccount account, + String realm, String nonce, String algorithm, String qop) + throws PeerUnavailableException, ParseException, NoSuchAlgorithmException { + + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), + server.getServerIp() + ":" + server.getServerPort()); + + AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader(DEFAULT_SCHEME); + authorizationHeader.setUsername(account.getUsername()); + if (realm == null) { + authorizationHeader.setRealm(server.getServerIp()); + }else { + authorizationHeader.setRealm(realm); + } + if (nonce == null) { + authorizationHeader.setRealm(generateNonce()); + }else { + authorizationHeader.setRealm(realm); + } + if (algorithm == null) { + authorizationHeader.setAlgorithm("MD5"); + }else { + authorizationHeader.setAlgorithm(algorithm); + } + if (qop == null) { + authorizationHeader.setQop("auth"); + }else { + authorizationHeader.setQop(qop); + } + + authorizationHeader.setNonce(generateNonce()); + authorizationHeader.setNonceCount(1); + authorizationHeader.setURI(requestURI); + authorizationHeader.setCNonce(UUID.randomUUID().toString()); + + String responseForAuth = getResponseForAuth(authorizationHeader, account.getPassword()); + authorizationHeader.setResponse(responseForAuth); + return authorizationHeader; + } + + private static String getResponseForAuth(AuthorizationHeader authHeader, String password) throws NoSuchAlgorithmException { + + String realm = authHeader.getRealm(); + String username = authHeader.getUsername(); + + if ( username == null || realm == null ) { + return null; + } + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return null; + } + + String A2 = "REGISTER:" + uri.toString(); + String HA1 = password; + + MessageDigest messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); + + byte[] mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + + String cnonce = authHeader.getCNonce(); + String KD = HA1 + ":" + nonce; + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + HA2; + mdbytes = messageDigest.digest(KD.getBytes()); + + return toHexString(mdbytes); + } + + /** + * Authenticate the inbound request given plain text password. + * + * @param request - the request to authenticate. + * @param pass -- the plain text password. + * + * @return true if authentication succeded and false otherwise. + */ + public boolean doAuthenticatePlainTextPassword(Request request, String pass) throws NoSuchAlgorithmException { + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + if ( authHeader == null || authHeader.getRealm() == null) { + return false; + } + String realm = authHeader.getRealm().trim(); + String username = authHeader.getUsername().trim(); + + if ( username == null || realm == null ) { + return false; + } + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return false; + } + // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 + String qop = authHeader.getQop(); + + // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。 + // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护 + String cnonce = authHeader.getCNonce(); + + // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量 + int nc = authHeader.getNonceCount(); + String ncStr = String.format("%08x", nc).toUpperCase(); + // String ncStr = new DecimalFormat("00000000").format(nc); + // String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16)); + + String A1 = username + ":" + realm + ":" + pass; + + String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); + + MessageDigest messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); + + byte mdbytes[] = messageDigest.digest(A1.getBytes()); + String HA1 = toHexString(mdbytes); + logger.debug("A1: " + A1); + logger.debug("A2: " + A2); + mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + logger.debug("HA1: " + HA1); + logger.debug("HA2: " + HA2); + // String cnonce = authHeader.getCNonce(); + logger.debug("nonce: " + nonce); + logger.debug("nc: " + ncStr); + logger.debug("cnonce: " + cnonce); + logger.debug("qop: " + qop); + String KD = HA1 + ":" + nonce; + + if (qop != null && qop.equalsIgnoreCase("auth") ) { + if (nc != -1) { + KD += ":" + ncStr; + } + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + qop; + } + KD += ":" + HA2; + logger.debug("KD: " + KD); + mdbytes = messageDigest.digest(KD.getBytes()); + String mdString = toHexString(mdbytes); + logger.debug("mdString: " + mdString); + String response = authHeader.getResponse(); + logger.debug("response: " + response); + return mdString.equals(response); + + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/utils/SIPRequestFactory.java b/src/main/java/com/genersoft/iot/vmp/sip/utils/SIPRequestFactory.java new file mode 100755 index 00000000..c8f08483 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/utils/SIPRequestFactory.java @@ -0,0 +1,84 @@ +package com.genersoft.iot.vmp.sip.utils; + +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; + +import javax.sip.InvalidArgumentException; +import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.*; +import javax.sip.message.Request; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.ArrayList; + +public class SIPRequestFactory { + + public static Request createRegisterRequest(SipServer server, SipServerAccount account, String callId, boolean isRegister, WWWAuthenticateHeader wwwAuthenticateHeader) throws ParseException, PeerUnavailableException, InvalidArgumentException, NoSuchAlgorithmException { + Request request = null; + String serverAddress = server.getServerIp()+ ":" + server.getServerPort(); + String clientAddress = server.getLocalIp()+ ":" + server.getLocalPort(); + //请求行 + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), + serverAddress); + //via + ArrayList viaHeaders = new ArrayList<>(); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(server.getLocalIp(), + server.getLocalPort(), server.getTransport(), SipUtils.getNewViaTag()); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + //from + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), serverAddress); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, SipUtils.getNewFromTag()); + //to + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), serverAddress); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); + + //Forwards + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); + + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(callId); + + //ceq + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(SipUtils.getCSEQ(), Request.REGISTER); + request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader, + cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() + .createSipURI(account.getUsername(), clientAddress)); + ContactHeader contactHeader = SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress); + contactHeader.setParameter("methods", "INVITE,ACK,BYE,CANCEL,OPTIONS,PRACK,MESSAGE,SUBSCRIBE,NOTIFY,REFER,UPDATE"); +// contactHeader.setQValue(); + request.addHeader(contactHeader); + + ExpiresHeader expires = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(isRegister ? 180 : 0); + request.addHeader(expires); + + AllowHeader allowHeader = SipFactory.getInstance().createHeaderFactory().createAllowHeader("INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE"); + request.addHeader(allowHeader); + + SupportedHeader supportedHeader = SipFactory.getInstance().createHeaderFactory().createSupportedHeader("timer, 100rel, replaces, gruu, outbound"); + request.addHeader(supportedHeader); + + AuthorizationHeader authorizationHeader; + if (wwwAuthenticateHeader != null) { + String qop = wwwAuthenticateHeader.getQop(); + String algorithm = wwwAuthenticateHeader.getAlgorithm(); + String nonce = wwwAuthenticateHeader.getNonce(); + String realm = wwwAuthenticateHeader.getRealm(); + + authorizationHeader = DigestClientAuthenticationHelper.getAuthorizationHeader(server, account, realm, nonce, algorithm, qop); + }else { + authorizationHeader = DigestClientAuthenticationHelper.getAuthorizationHeader(server, account); + } + + request.addHeader(authorizationHeader); + + return request; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/sip/utils/SipUtils.java b/src/main/java/com/genersoft/iot/vmp/sip/utils/SipUtils.java new file mode 100644 index 00000000..0f72b8ad --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/sip/utils/SipUtils.java @@ -0,0 +1,83 @@ +package com.genersoft.iot.vmp.sip.utils; + +import com.genersoft.iot.vmp.utils.GitUtil; +import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.Utils; +import org.springframework.util.ObjectUtils; + +import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; +import javax.sip.header.UserAgentHeader; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class SipUtils { + + public static int getRandomTcpPort(){ + try { + ServerSocket serverSocket = new ServerSocket(0); + int localPort = serverSocket.getLocalPort(); + serverSocket.close(); + return localPort; + }catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static int getRandomUdpPort(){ + try { + DatagramSocket datagramSocket = new DatagramSocket(0); + int localPort = datagramSocket.getLocalPort(); + datagramSocket.close(); + return localPort; + }catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String getNewCallId(SipProviderImpl sipProvider) { + return Utils.getInstance().generateCallIdentifier(sipProvider.getListeningPoint() + .getIPAddress()); + + } + + public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException { + List agentParam = new ArrayList<>(); + agentParam.add("WVP-Pro "); + if (gitUtil != null ) { + if (!ObjectUtils.isEmpty(gitUtil.getBuildVersion())) { + agentParam.add("v"); + agentParam.add(gitUtil.getBuildVersion() + "."); + } + if (!ObjectUtils.isEmpty(gitUtil.getCommitTime())) { + agentParam.add(gitUtil.getCommitTime()); + } + } + return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam); + } + + private static long cseq = 0L; + + public static long getCSEQ() { + return cseq++; + } + + public static String getNewViaTag() { + return "z9hG4bK" + System.currentTimeMillis(); + } + + public static String getNewFromTag(){ + return UUID.randomUUID().toString().replace("-", ""); + +// return getNewTag(); + } + + public static String getNewTag(){ + return String.valueOf(System.currentTimeMillis()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/sip/SipController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/sip/SipController.java new file mode 100644 index 00000000..9199b763 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/sip/SipController.java @@ -0,0 +1,137 @@ +package com.genersoft.iot.vmp.vmanager.sip; + +import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.sip.bean.SipServer; +import com.genersoft.iot.vmp.sip.bean.SipServerAccount; +import com.genersoft.iot.vmp.sip.bean.SipVideo; +import com.genersoft.iot.vmp.sip.service.ISipService; +import com.github.pagehelper.PageInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +/** + * SIP接口 + */ +@Tag(name = "SIP接口", description = "") +@Controller + +@RequestMapping(value = "/api/sip") +public class SipController { + + private final static Logger logger = LoggerFactory.getLogger(SipController.class); + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private ISipService sipService; + + @Operation(summary = "分页获取SIP服务") + @Parameter(name = "page", description = "当前页") + @Parameter(name = "count", description = "每页查询数量") + @GetMapping(value = "/server/list") + @ResponseBody + public PageInfo getServerList(@RequestParam(required = false)Integer page, + @RequestParam(required = false)Integer count ){ + + return sipService.getServerList(page, count); + } + + @Operation(summary = "添加SIP服务") + @PostMapping(value = "/server/add") + @ResponseBody + public void addServer(@RequestBody SipServer server){ + sipService.addSipServer(server); + } + + @Operation(summary = "更新SIP服务") + @PostMapping(value = "/server/update") + @ResponseBody + public void updateServer(@RequestBody SipServer server){ + sipService.updateSipServer(server); + } + + @Operation(summary = "删除SIP服务") + @DeleteMapping(value = "/server/remove") + @ResponseBody + public void deleteServer(Integer serverId){ + sipService.removeSipServer(serverId); + } + + + + @Operation(summary = "分页获取SIP帐号") + @Parameter(name = "page", description = "当前页") + @Parameter(name = "count", description = "每页查询数量") + @GetMapping(value = "/account/list") + @ResponseBody + public PageInfo getAccountList(@RequestParam(required = false)Integer page, + @RequestParam(required = false)Integer count, + Integer serverId){ + + return sipService.getAccountList(serverId, page, count); + } + + @Operation(summary = "添加SIP帐号") + @PostMapping(value = "/account/add") + @ResponseBody + public void addServerAccount(@RequestBody SipServerAccount account){ + sipService.addSipServerAccount(account); + } + + @Operation(summary = "更新SIP帐号") + @PostMapping(value = "/account/update") + @ResponseBody + public void updateServerAccount(@RequestBody SipServerAccount account){ + sipService.updateSipServerAccount(account); + } + + @Operation(summary = "删除SIP帐号") + @DeleteMapping(value = "/account/remove") + @ResponseBody + public void deleteServerAccount(Integer accountId){ + sipService.removeSipServerAccount(accountId); + } + + @Operation(summary = "分页获取SIP推送视频") + @Parameter(name = "page", description = "当前页") + @Parameter(name = "count", description = "每页查询数量") + @GetMapping(value = "/video/list") + @ResponseBody + public PageInfo getVideoList(@RequestParam(required = false)Integer page, + @RequestParam(required = false)Integer count, + Integer serverId, + Integer accountId){ + + return sipService.getVideoList(serverId, accountId, page, count); + } + + @Operation(summary = "添加SIP推送视频") + @PostMapping(value = "/video/add") + @ResponseBody + public void addVideo(@RequestBody SipVideo video){ + sipService.addSipVideo(video); + } + + @Operation(summary = "更新SIP推送视频") + @PostMapping(value = "/video/update") + @ResponseBody + public void updateVideo(@RequestBody SipVideo video){ + sipService.updateSipVideo(video); + } + + @Operation(summary = "删除SIP推送视频") + @DeleteMapping(value = "/video/remove") + @ResponseBody + public void deleteVideo(Integer videoId){ + sipService.removeSipVideo(videoId); + } + + +}