[Improve] Add common protoctl entity check (#4084)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: Tomsun28 <tomsun28@outlook.com>
This commit is contained in:
shown
2026-03-28 12:10:06 +08:00
committed by GitHub
co-authored by Copilot aias00 Tomsun28
parent 2bb4f43494
commit b73f824e3c
11 changed files with 783 additions and 16 deletions
@@ -17,10 +17,14 @@
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validPort;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validateIpDomain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* IPMI2 Protocol
@@ -72,8 +76,20 @@ public class IpmiProtocol implements CommonRequestProtocol, Protocol {
@Override
public boolean isInvalid() {
// todo: add
return true;
if (!validateIpDomain(host) || !validPort(port) || Integer.parseInt(port) <= 0) {
return true;
}
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return true;
}
if (StringUtils.isBlank(type)) {
return true;
}
if (!"Chassis".equalsIgnoreCase(type)
&& !"Sensor".equalsIgnoreCase(type)
&& !"Raw".equalsIgnoreCase(type)) {
return true;
}
return "Raw".equalsIgnoreCase(type) && field == null;
}
}
@@ -17,10 +17,14 @@
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validPort;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validateIpDomain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* Jmx protocol
@@ -67,8 +71,26 @@ public class JmxProtocol implements CommonRequestProtocol, Protocol {
@Override
public boolean isInvalid() {
// todo: add
return true;
if (StringUtils.isBlank(objectName)) {
return true;
}
if (StringUtils.isNotBlank(ssl)
&& !"true".equalsIgnoreCase(ssl)
&& !"false".equalsIgnoreCase(ssl)) {
return true;
}
if (StringUtils.isNotBlank(username) && StringUtils.isBlank(password)) {
return true;
}
if (StringUtils.isBlank(username) && StringUtils.isNotBlank(password)) {
return true;
}
if (StringUtils.isNotBlank(url)) {
return !url.startsWith("service:jmx:rmi:") || url.contains("/stub/");
}
if (!validateIpDomain(host) || !validPort(port)) {
return true;
}
return Integer.parseInt(port) <= 0;
}
}
@@ -23,7 +23,7 @@ package org.apache.hertzbeat.common.entity.job.protocol;
public interface Protocol {
/**
* Check Protocol params vaild.
* Check Protocol params valid.
* @return True or False.
*/
boolean isInvalid();
@@ -17,11 +17,16 @@
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.apache.hertzbeat.common.util.IpDomainUtil.isHasSchema;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validPort;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validateIpDomain;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.dto.Field;
/**
@@ -39,8 +44,26 @@ public class PushProtocol implements CommonRequestProtocol, Protocol {
@Override
public boolean isInvalid() {
// todo: add
return true;
if ((!validateIpDomain(host) && !isHasSchema(host)) || !validPort(port)) {
return true;
}
if (Integer.parseInt(port) <= 0) {
return true;
}
if (StringUtils.isBlank(uri) || !uri.startsWith("/") || StringUtils.containsWhitespace(uri)) {
return true;
}
if (fields == null || fields.isEmpty()) {
return true;
}
for (Field field : fields) {
if (field == null
|| StringUtils.isBlank(field.getName())
|| field.getType() == null
|| (field.getType() != 0 && field.getType() != 1)) {
return true;
}
}
return false;
}
}
@@ -17,11 +17,16 @@
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.apache.hertzbeat.common.util.IpDomainUtil.isHasSchema;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validPort;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validateIpDomain;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* Redfish Protocol
@@ -65,8 +70,30 @@ public class RedfishProtocol implements CommonRequestProtocol, Protocol {
@Override
public boolean isInvalid() {
// todo: add
return true;
if ((!validateIpDomain(host) && !isHasSchema(host)) || !validPort(port)) {
return true;
}
if (Integer.parseInt(port) <= 0) {
return true;
}
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return true;
}
if (!StringUtils.isNumeric(timeout)) {
return true;
}
if (StringUtils.isNotBlank(schema)
&& (!schema.startsWith("/") || StringUtils.containsWhitespace(schema))) {
return true;
}
if (jsonPath == null || jsonPath.isEmpty()) {
return true;
}
for (String path : jsonPath) {
if (StringUtils.isBlank(path)) {
return true;
}
}
return false;
}
}
@@ -17,10 +17,14 @@
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validPort;
import static org.apache.hertzbeat.common.util.IpDomainUtil.validateIpDomain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* Websocket Protocol
@@ -47,8 +51,15 @@ public class WebsocketProtocol implements CommonRequestProtocol, Protocol {
@Override
public boolean isInvalid() {
// todo: add
return true;
if (!validateIpDomain(host) || !validPort(port)) {
return true;
}
if (Integer.parseInt(port) <= 0) {
return true;
}
if (StringUtils.isEmpty(path)) {
return false;
}
return !path.startsWith("/") || StringUtils.containsWhitespace(path);
}
}
@@ -0,0 +1,135 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class IpmiProtocolTest {
@Test
void isInvalidValidProtocol() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("password")
.type("Chassis")
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidValidRawProtocol() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("password")
.type("Raw")
.field(new IpmiProtocol.Field())
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidInvalidHost() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("")
.port("623")
.username("admin")
.password("password")
.type("Chassis")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidInvalidPort() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("70000")
.username("admin")
.password("password")
.type("Chassis")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankUsername() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("")
.password("password")
.type("Chassis")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankPassword() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("")
.type("Chassis")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankType() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("password")
.type("")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidUnsupportedType() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("password")
.type("Unknown")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidRawWithoutField() {
IpmiProtocol protocol = IpmiProtocol.builder()
.host("192.168.1.1")
.port("623")
.username("admin")
.password("password")
.type("Raw")
.build();
assertTrue(protocol.isInvalid());
}
}
@@ -0,0 +1,128 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class JmxProtocolTest {
@Test
void isInvalidValidHostPortProtocol() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("9999")
.objectName("java.lang:type=Runtime")
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidValidUrlProtocol() {
JmxProtocol protocol = JmxProtocol.builder()
.url("service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/jmxrmi")
.objectName("java.lang:type=Runtime")
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidBlankObjectName() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("9999")
.objectName("")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidInvalidHost() {
JmxProtocol protocol = JmxProtocol.builder()
.host("")
.port("9999")
.objectName("java.lang:type=Runtime")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidZeroPort() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("0")
.objectName("java.lang:type=Runtime")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidUnsupportedSsl() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("9999")
.objectName("java.lang:type=Runtime")
.ssl("yes")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidOnlyUsername() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("9999")
.objectName("java.lang:type=Runtime")
.username("admin")
.password("")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidOnlyPassword() {
JmxProtocol protocol = JmxProtocol.builder()
.host("192.168.1.1")
.port("9999")
.objectName("java.lang:type=Runtime")
.username("")
.password("password")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidUrlProtocolMismatch() {
JmxProtocol protocol = JmxProtocol.builder()
.url("http://127.0.0.1:9999/jmxrmi")
.objectName("java.lang:type=Runtime")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidUrlContainsStub() {
JmxProtocol protocol = JmxProtocol.builder()
.url("service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/stub/path")
.objectName("java.lang:type=Runtime")
.build();
assertTrue(protocol.isInvalid());
}
}
@@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.Field;
import org.junit.jupiter.api.Test;
class PushProtocolTest {
@Test
void isInvalidValidProtocol() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("cpuUsage").type((byte) 0).build()))
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidValidProtocolWithSchemaHost() {
PushProtocol protocol = PushProtocol.builder()
.host("http://127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("status").type((byte) 1).build()))
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidInvalidHost() {
PushProtocol protocol = PushProtocol.builder()
.host("")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("status").type((byte) 1).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidZeroPort() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("0")
.uri("/api/push")
.fields(List.of(Field.builder().name("status").type((byte) 1).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankUri() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("")
.fields(List.of(Field.builder().name("status").type((byte) 1).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidUriWithoutLeadingSlash() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("api/push")
.fields(List.of(Field.builder().name("status").type((byte) 1).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankFields() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of())
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidFieldWithoutName() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("").type((byte) 1).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidFieldWithoutType() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("status").type(null).build()))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidFieldWithUnsupportedType() {
PushProtocol protocol = PushProtocol.builder()
.host("127.0.0.1")
.port("1157")
.uri("/api/push")
.fields(List.of(Field.builder().name("status").type((byte) 2).build()))
.build();
assertTrue(protocol.isInvalid());
}
}
@@ -0,0 +1,171 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.Test;
class RedfishProtocolTest {
@Test
void isInvalidValidProtocol() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidValidProtocolWithSchemaHost() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("https://127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidInvalidHost() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidZeroPort() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("0")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankUsername() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankPassword() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("")
.timeout("5000")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankTimeout() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidNonNumericTimeout() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5s")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidInvalidSchema() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.schema("redfish/v1/Chassis")
.jsonPath(List.of("$.Id"))
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidBlankJsonPath() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of())
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidJsonPathContainsBlankItem() {
RedfishProtocol protocol = RedfishProtocol.builder()
.host("127.0.0.1")
.port("443")
.username("Administrator")
.password("Password")
.timeout("5000")
.jsonPath(List.of("$.Id", ""))
.build();
assertTrue(protocol.isInvalid());
}
}
@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class WebsocketProtocolTest {
@Test
void isInvalidValidProtocol() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("80")
.path("/")
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidValidProtocolWithoutPath() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("80")
.path("")
.build();
assertFalse(protocol.isInvalid());
}
@Test
void isInvalidInvalidHost() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("")
.port("80")
.path("/")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidInvalidPort() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("99999")
.path("/")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidZeroPort() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("0")
.path("/")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidPathWithoutLeadingSlash() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("80")
.path("ws")
.build();
assertTrue(protocol.isInvalid());
}
@Test
void isInvalidPathContainsWhitespace() {
WebsocketProtocol protocol = WebsocketProtocol.builder()
.host("127.0.0.1")
.port("80")
.path("/chat room")
.build();
assertTrue(protocol.isInvalid());
}
}