package ru.serega6531.packmate.service; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import ru.serega6531.packmate.model.*; import ru.serega6531.packmate.model.enums.PatternActionType; import ru.serega6531.packmate.model.enums.PatternDirectionType; import ru.serega6531.packmate.model.enums.SubscriptionMessageType; import ru.serega6531.packmate.model.pojo.*; import ru.serega6531.packmate.repository.StreamRepository; import ru.serega6531.packmate.service.optimization.RsaKeysHolder; import ru.serega6531.packmate.service.optimization.StreamOptimizer; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.stream.Collectors; @Service @Slf4j public class StreamService { private final StreamRepository repository; private final PatternService patternService; private final ServicesService servicesService; private final CountingService countingService; private final SubscriptionService subscriptionService; private final RsaKeysHolder keysHolder; private final boolean ignoreEmptyPackets; private final java.util.regex.Pattern userAgentPattern = java.util.regex.Pattern.compile("User-Agent: (.+)\\r\\n"); private final ModelMapper modelMapper = new ModelMapper(); @Autowired public StreamService(StreamRepository repository, PatternService patternService, ServicesService servicesService, CountingService countingService, SubscriptionService subscriptionService, RsaKeysHolder keysHolder, @Value("${ignore-empty-packets}") boolean ignoreEmptyPackets) { this.repository = repository; this.patternService = patternService; this.servicesService = servicesService; this.countingService = countingService; this.subscriptionService = subscriptionService; this.keysHolder = keysHolder; this.ignoreEmptyPackets = ignoreEmptyPackets; } /** * @return был ли сохранен стрим */ @Transactional(propagation = Propagation.NEVER) public boolean saveNewStream(UnfinishedStream unfinishedStream, List packets) { final var serviceOptional = servicesService.findService( unfinishedStream.getFirstIp(), unfinishedStream.getFirstPort(), unfinishedStream.getSecondIp(), unfinishedStream.getSecondPort() ); if (serviceOptional.isEmpty()) { log.warn("Failed to save the stream: service at port {} or {} does not exist", unfinishedStream.getFirstPort(), unfinishedStream.getSecondPort()); return false; } CtfService service = serviceOptional.get(); if (ignoreEmptyPackets) { packets.removeIf(packet -> packet.getContent().length == 0); if (packets.isEmpty()) { log.debug("Stream consists only of empty packets and will not be saved"); return false; } } Optional firstIncoming = packets.stream() .filter(Packet::isIncoming) .findFirst(); final Stream stream = new Stream(); stream.setProtocol(unfinishedStream.getProtocol()); stream.setTtl(firstIncoming.map(Packet::getTtl).orElse(0)); stream.setStartTimestamp(packets.get(0).getTimestamp()); stream.setEndTimestamp(packets.get(packets.size() - 1).getTimestamp()); stream.setService(service.getPort()); countingService.countStream(service.getPort(), packets.size()); packets = new StreamOptimizer(keysHolder, service, packets).optimizeStream(); if (isStreamIgnored(packets)) { log.debug("New stream is ignored"); return false; } processUserAgent(packets, stream); Stream savedStream = save(stream); for (Packet packet : packets) { packet.setStream(savedStream); } Set foundPatterns = matchPatterns(packets); savedStream.setFoundPatterns(foundPatterns); savedStream.setPackets(packets); savedStream = save(savedStream); subscriptionService.broadcast(new SubscriptionMessage(SubscriptionMessageType.NEW_STREAM, streamToDto(savedStream))); return true; } @Async @Transactional public void processLookbackPattern(Pattern pattern, long start, long end) { List streams = findAllBetweenTimestamps(start, end); for (Stream stream : streams) { boolean found = matchPattern(stream.getPackets(), pattern); if (found) { stream.getFoundPatterns().add(pattern); repository.save(stream); } } subscriptionService.broadcast(new SubscriptionMessage(SubscriptionMessageType.FINISH_LOOKBACK, pattern.getId())); } private void processUserAgent(List packets, Stream stream) { String ua = null; for (Packet packet : packets) { String content = packet.getContentString(); final Matcher matcher = userAgentPattern.matcher(content); if (matcher.find()) { ua = matcher.group(1); break; } } if (ua != null) { stream.setUserAgentHash(calculateUserAgentHash(ua)); } } private String calculateUserAgentHash(String ua) { char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); int l = alphabet.length; int hashCode = ua.hashCode(); if (hashCode == Integer.MIN_VALUE) { // abs(MIN_VALUE) вернет то же значение hashCode = Integer.MAX_VALUE; } final int hash = Math.abs(hashCode) % (l * l * l); return "" + alphabet[hash % l] + alphabet[(hash / l) % l] + alphabet[(hash / (l * l)) % l]; } private Set matchPatterns(List packets) { Set foundPatterns = new HashSet<>(); for (Packet packet : packets) { PatternDirectionType direction = packet.isIncoming() ? PatternDirectionType.INPUT : PatternDirectionType.OUTPUT; final Set matches = patternService.findMatches(packet.getContent(), direction, PatternActionType.FIND); packet.setMatches(matches); matches.forEach(m -> m.setPacket(packet)); foundPatterns.addAll(matches.stream() .map(FoundPattern::getPatternId) .map(patternService::find) .collect(Collectors.toList())); } return foundPatterns; } private boolean matchPattern(List packets, Pattern pattern) { boolean matched = false; for (Packet packet : packets) { PatternDirectionType direction = packet.isIncoming() ? PatternDirectionType.INPUT : PatternDirectionType.OUTPUT; if (pattern.getDirectionType() != PatternDirectionType.BOTH && pattern.getDirectionType() != direction) { continue; } final Optional matchOpt = patternService.tryMatch(packet.getContent(), pattern); if (matchOpt.isPresent()) { FoundPattern match = matchOpt.get(); packet.getMatches().add(match); match.setPacket(packet); matched = true; } } return matched; } private boolean isStreamIgnored(List packets) { for (Packet packet : packets) { PatternDirectionType direction = packet.isIncoming() ? PatternDirectionType.INPUT : PatternDirectionType.OUTPUT; final Set matches = patternService.findMatches(packet.getContent(), direction, PatternActionType.IGNORE); if (!matches.isEmpty()) { return true; } } return false; } private Stream save(Stream stream) { Stream saved; if (stream.getId() == null) { saved = repository.save(stream); log.debug("Saved stream with id {}", saved.getId()); } else { saved = repository.save(stream); } return saved; } public Optional find(long id) { return repository.findById(id); } @Transactional public void setFavorite(long id, boolean favorite) { repository.setFavorite(id, favorite); } public List findAll(Pagination pagination, Optional service, boolean onlyFavorites) { PageRequest page = PageRequest.of(0, pagination.getPageSize(), pagination.getDirection(), "id"); Specification spec; if (pagination.getDirection() == Sort.Direction.ASC) { spec = streamIdGreaterThan(pagination.getStartingFrom()); } else { spec = streamIdLessThan(pagination.getStartingFrom()); } if (service.isPresent()) { spec = spec.and(streamServiceEquals(service.get())); } if (onlyFavorites) { spec = spec.and(streamIsFavorite()); } if (pagination.getPattern() != null) { spec = spec.and(streamPatternsContains(pagination.getPattern())); } return repository.findAll(spec, page).getContent(); } public List findAllBetweenTimestamps(long start, long end) { Specification spec = streamTimestampBetween(start, end); return repository.findAll(spec); } public StreamDto streamToDto(Stream stream) { return modelMapper.map(stream, StreamDto.class); } public PacketDto packetToDto(Packet packet) { return modelMapper.map(packet, PacketDto.class); } private Specification streamServiceEquals(long service) { return (root, query, cb) -> cb.equal(root.get("service"), service); } private Specification streamIsFavorite() { return (root, query, cb) -> cb.equal(root.get("favorite"), true); } private Specification streamIdGreaterThan(long id) { return (root, query, cb) -> cb.greaterThan(root.get("id"), id); } private Specification streamIdLessThan(long id) { return (root, query, cb) -> cb.lessThan(root.get("id"), id); } private Specification streamTimestampBetween(long start, long end) { return (root, query, cb) -> cb.between(root.get("startTimestamp"), start, end); } private Specification streamPatternsContains(Pattern pattern) { return (root, query, cb) -> cb.isMember(pattern, root.get("foundPatterns")); } }