1518678f8Sopenharmony_ci/*
2518678f8Sopenharmony_ci * Copyright (C) 2021-2022 Huawei Device Co., Ltd.
3518678f8Sopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
4518678f8Sopenharmony_ci * you may not use this file except in compliance with the License.
5518678f8Sopenharmony_ci * You may obtain a copy of the License at
6518678f8Sopenharmony_ci *
7518678f8Sopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
8518678f8Sopenharmony_ci *
9518678f8Sopenharmony_ci * Unless required by applicable law or agreed to in writing, software
10518678f8Sopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
11518678f8Sopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12518678f8Sopenharmony_ci * See the License for the specific language governing permissions and
13518678f8Sopenharmony_ci * limitations under the License.
14518678f8Sopenharmony_ci */
15518678f8Sopenharmony_ci
16518678f8Sopenharmony_ci#include "dhcp_config.h"
17518678f8Sopenharmony_ci#include <errno.h>
18518678f8Sopenharmony_ci#include <securec.h>
19518678f8Sopenharmony_ci#include <stdio.h>
20518678f8Sopenharmony_ci#include <stdlib.h>
21518678f8Sopenharmony_ci#include <string.h>
22518678f8Sopenharmony_ci#include <time.h>
23518678f8Sopenharmony_ci#include "address_utils.h"
24518678f8Sopenharmony_ci#include "common_util.h"
25518678f8Sopenharmony_ci#include "dhcp_server_ipv4.h"
26518678f8Sopenharmony_ci#include "dhcp_logger.h"
27518678f8Sopenharmony_ci
28518678f8Sopenharmony_ciDEFINE_DHCPLOG_DHCP_LABEL("DhcpServerConfig");
29518678f8Sopenharmony_ci
30518678f8Sopenharmony_ci#define FILE_LINE_LEN_MAX       1024
31518678f8Sopenharmony_ci#define FILE_LINE_DELIMITER     "="
32518678f8Sopenharmony_ci
33518678f8Sopenharmony_cistatic int SetEnableConfigInfo(DhcpConfig *dhcpConfig, const char *pKey, const char *pValue)
34518678f8Sopenharmony_ci{
35518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pKey == nullptr) || (pValue == nullptr)) {
36518678f8Sopenharmony_ci        DHCP_LOGE("SetEnableConfigInfo param dhcpConfig or pKey or pValue is nullptr!");
37518678f8Sopenharmony_ci        return RET_FAILED;
38518678f8Sopenharmony_ci    }
39518678f8Sopenharmony_ci
40518678f8Sopenharmony_ci    uint32_t uValue = (uint32_t)atoi(pValue);
41518678f8Sopenharmony_ci    if ((uValue != 0) && (uValue != 1)) {
42518678f8Sopenharmony_ci        DHCP_LOGE("enable:%s error", pValue);
43518678f8Sopenharmony_ci        return RET_FAILED;
44518678f8Sopenharmony_ci    }
45518678f8Sopenharmony_ci
46518678f8Sopenharmony_ci    if (strcmp(pKey, "distribution") == 0) {
47518678f8Sopenharmony_ci        dhcpConfig->distribution = uValue;
48518678f8Sopenharmony_ci    } else if (strcmp(pKey, "broadcast") == 0) {
49518678f8Sopenharmony_ci        dhcpConfig->broadcast = uValue;
50518678f8Sopenharmony_ci    }
51518678f8Sopenharmony_ci    return RET_SUCCESS;
52518678f8Sopenharmony_ci}
53518678f8Sopenharmony_ci
54518678f8Sopenharmony_cistatic int SetTimeConfigInfo(DhcpConfig *dhcpConfig, const char *pKey, const char *pValue)
55518678f8Sopenharmony_ci{
56518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pKey == nullptr) || (pValue == nullptr)) {
57518678f8Sopenharmony_ci        DHCP_LOGE("SetTimeConfigInfo param dhcpConfig or pKey or pValue is nullptr!");
58518678f8Sopenharmony_ci        return RET_FAILED;
59518678f8Sopenharmony_ci    }
60518678f8Sopenharmony_ci
61518678f8Sopenharmony_ci    uint32_t uValue = 0;
62518678f8Sopenharmony_ci    if ((uValue = (uint32_t)atoi(pValue)) == 0) {
63518678f8Sopenharmony_ci        DHCP_LOGE("atoi failed, time:%s", pValue);
64518678f8Sopenharmony_ci        return RET_FAILED;
65518678f8Sopenharmony_ci    }
66518678f8Sopenharmony_ci
67518678f8Sopenharmony_ci    if (strcmp(pKey, "leaseTime") == 0) {
68518678f8Sopenharmony_ci        dhcpConfig->leaseTime = uValue;
69518678f8Sopenharmony_ci    } else if (strcmp(pKey, "renewalTime") == 0) {
70518678f8Sopenharmony_ci        dhcpConfig->renewalTime = uValue;
71518678f8Sopenharmony_ci    } else if (strcmp(pKey, "rebindingTime") == 0) {
72518678f8Sopenharmony_ci        dhcpConfig->rebindingTime = uValue;
73518678f8Sopenharmony_ci    }
74518678f8Sopenharmony_ci    return RET_SUCCESS;
75518678f8Sopenharmony_ci}
76518678f8Sopenharmony_ci
77518678f8Sopenharmony_cistatic int SetNetConfigInfo(DhcpConfig *dhcpConfig, const char *pKey, const char *pValue, int common)
78518678f8Sopenharmony_ci{
79518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pKey == nullptr) || (pValue == nullptr)) {
80518678f8Sopenharmony_ci        DHCP_LOGE("SetNetConfigInfo param dhcpConfig or pKey or pValue is nullptr!");
81518678f8Sopenharmony_ci        return RET_FAILED;
82518678f8Sopenharmony_ci    }
83518678f8Sopenharmony_ci
84518678f8Sopenharmony_ci    uint32_t uValue = 0;
85518678f8Sopenharmony_ci    if ((uValue = ParseIpAddr(pValue)) == 0) {
86518678f8Sopenharmony_ci        DHCP_LOGE("ParseIpAddr failed, ip:%s", pValue);
87518678f8Sopenharmony_ci        return RET_FAILED;
88518678f8Sopenharmony_ci    }
89518678f8Sopenharmony_ci
90518678f8Sopenharmony_ci    if (((strcmp(pKey, "serverId") == 0) && common) || ((strcmp(pKey, "server") == 0) && !common)) {
91518678f8Sopenharmony_ci        dhcpConfig->serverId = uValue;
92518678f8Sopenharmony_ci    } else if (strcmp(pKey, "gateway") == 0) {
93518678f8Sopenharmony_ci        dhcpConfig->gateway = uValue;
94518678f8Sopenharmony_ci    } else if (strcmp(pKey, "netmask") == 0) {
95518678f8Sopenharmony_ci        dhcpConfig->netmask = uValue;
96518678f8Sopenharmony_ci    }
97518678f8Sopenharmony_ci    return RET_SUCCESS;
98518678f8Sopenharmony_ci}
99518678f8Sopenharmony_ci
100518678f8Sopenharmony_cistatic int SetIpAddressPool(DhcpConfig *dhcpConfig, const char *pValue)
101518678f8Sopenharmony_ci{
102518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pValue == nullptr)) {
103518678f8Sopenharmony_ci        DHCP_LOGE("SetIpAddressPool param dhcpConfig or pValue is nullptr!");
104518678f8Sopenharmony_ci        return RET_FAILED;
105518678f8Sopenharmony_ci    }
106518678f8Sopenharmony_ci
107518678f8Sopenharmony_ci    char *pSrc = (char *)pValue;
108518678f8Sopenharmony_ci    char *pSave = nullptr;
109518678f8Sopenharmony_ci    char *pTok = strtok_r(pSrc, ",", &pSave);
110518678f8Sopenharmony_ci    if (((pTok == nullptr) || (strlen(pTok) == 0)) || ((pSave == nullptr) || (strlen(pSave) == 0))) {
111518678f8Sopenharmony_ci        DHCP_LOGE("strtok_r pTok or pSave nullptr or len is 0!");
112518678f8Sopenharmony_ci        return RET_FAILED;
113518678f8Sopenharmony_ci    }
114518678f8Sopenharmony_ci
115518678f8Sopenharmony_ci    uint32_t begin;
116518678f8Sopenharmony_ci    if ((begin = ParseIpAddr(pTok)) == 0) {
117518678f8Sopenharmony_ci        DHCP_LOGE("ParseIpAddr begin:%s failed!", pTok);
118518678f8Sopenharmony_ci        return RET_FAILED;
119518678f8Sopenharmony_ci    }
120518678f8Sopenharmony_ci    dhcpConfig->pool.beginAddress = begin;
121518678f8Sopenharmony_ci    uint32_t end;
122518678f8Sopenharmony_ci    if ((end = ParseIpAddr(pSave)) == 0) {
123518678f8Sopenharmony_ci        DHCP_LOGE("ParseIpAddr end:%s failed!", pSave);
124518678f8Sopenharmony_ci        return RET_FAILED;
125518678f8Sopenharmony_ci    }
126518678f8Sopenharmony_ci    dhcpConfig->pool.endAddress = end;
127518678f8Sopenharmony_ci    return RET_SUCCESS;
128518678f8Sopenharmony_ci}
129518678f8Sopenharmony_ci
130518678f8Sopenharmony_cistatic int SetDnsInfo(DhcpConfig *dhcpConfig, const char *pValue)
131518678f8Sopenharmony_ci{
132518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pValue == nullptr)) {
133518678f8Sopenharmony_ci        DHCP_LOGE("SetDnsInfo param dhcpConfig or pValue is nullptr!");
134518678f8Sopenharmony_ci        return RET_FAILED;
135518678f8Sopenharmony_ci    }
136518678f8Sopenharmony_ci
137518678f8Sopenharmony_ci    char *pSrc = (char *)pValue;
138518678f8Sopenharmony_ci    char *pSave = nullptr;
139518678f8Sopenharmony_ci    char *pTok = strtok_r(pSrc, ",", &pSave);
140518678f8Sopenharmony_ci    if ((pTok == nullptr) || (strlen(pTok) == 0)) {
141518678f8Sopenharmony_ci        DHCP_LOGE("strtok_r pTok nullptr or len is 0!");
142518678f8Sopenharmony_ci        return RET_FAILED;
143518678f8Sopenharmony_ci    }
144518678f8Sopenharmony_ci
145518678f8Sopenharmony_ci    DhcpOption optDns = {DOMAIN_NAME_SERVER_OPTION, 0, {0}};
146518678f8Sopenharmony_ci    if (GetOption(&dhcpConfig->options, optDns.code) != nullptr) {
147518678f8Sopenharmony_ci        RemoveOption(&dhcpConfig->options, optDns.code);
148518678f8Sopenharmony_ci    }
149518678f8Sopenharmony_ci
150518678f8Sopenharmony_ci    uint32_t dnsAddress;
151518678f8Sopenharmony_ci    while (pTok != nullptr) {
152518678f8Sopenharmony_ci        if ((dnsAddress = ParseIpAddr(pTok)) == 0) {
153518678f8Sopenharmony_ci            DHCP_LOGE("ParseIpAddr %s failed, code:%d", pTok, optDns.code);
154518678f8Sopenharmony_ci            return RET_FAILED;
155518678f8Sopenharmony_ci        }
156518678f8Sopenharmony_ci        if (AppendAddressOption(&optDns, dnsAddress) != RET_SUCCESS) {
157518678f8Sopenharmony_ci            DHCP_LOGW("failed append dns option.");
158518678f8Sopenharmony_ci        }
159518678f8Sopenharmony_ci        pTok = strtok_r(nullptr, ",", &pSave);
160518678f8Sopenharmony_ci    }
161518678f8Sopenharmony_ci    PushBackOption(&dhcpConfig->options, &optDns);
162518678f8Sopenharmony_ci    return RET_SUCCESS;
163518678f8Sopenharmony_ci}
164518678f8Sopenharmony_ci
165518678f8Sopenharmony_cistatic int SetIfnameInfo(DhcpConfig *dhcpConfig, const char *pValue)
166518678f8Sopenharmony_ci{
167518678f8Sopenharmony_ci    if ((dhcpConfig == nullptr) || (pValue == nullptr)) {
168518678f8Sopenharmony_ci        DHCP_LOGE("SetIfnameInfo dhcpConfig or pValue is nullptr!");
169518678f8Sopenharmony_ci        return RET_FAILED;
170518678f8Sopenharmony_ci    }
171518678f8Sopenharmony_ci    if (strlen(pValue) >= IFACE_NAME_SIZE) {
172518678f8Sopenharmony_ci        DHCP_LOGE("ifname:%s too long!", pValue);
173518678f8Sopenharmony_ci        return RET_FAILED;
174518678f8Sopenharmony_ci    }
175518678f8Sopenharmony_ci    if (memset_s(dhcpConfig->ifname, IFACE_NAME_SIZE, '\0', IFACE_NAME_SIZE) != EOK ||
176518678f8Sopenharmony_ci        strncpy_s(dhcpConfig->ifname, IFACE_NAME_SIZE, pValue, strlen(pValue)) != EOK) {
177518678f8Sopenharmony_ci        return RET_FAILED;
178518678f8Sopenharmony_ci    }
179518678f8Sopenharmony_ci    return RET_SUCCESS;
180518678f8Sopenharmony_ci}
181518678f8Sopenharmony_ci
182518678f8Sopenharmony_cistatic int SetDhcpConfig(DhcpConfig *dhcpConfig, const char *strLine, int common)
183518678f8Sopenharmony_ci{
184518678f8Sopenharmony_ci    if ((strLine == nullptr) || (strlen(strLine) == 0)) {
185518678f8Sopenharmony_ci        DHCP_LOGE("SetDhcpConfig param strLine is nullptr or len = 0!");
186518678f8Sopenharmony_ci        return RET_FAILED;
187518678f8Sopenharmony_ci    }
188518678f8Sopenharmony_ci
189518678f8Sopenharmony_ci    char *pSrc = (char *)strLine;
190518678f8Sopenharmony_ci    char *pSave = nullptr;
191518678f8Sopenharmony_ci    char *pTok = strtok_r(pSrc, FILE_LINE_DELIMITER, &pSave);
192518678f8Sopenharmony_ci    if (pTok == nullptr) {
193518678f8Sopenharmony_ci        DHCP_LOGE("strtok_r pTok nullptr!");
194518678f8Sopenharmony_ci        return RET_FAILED;
195518678f8Sopenharmony_ci    }
196518678f8Sopenharmony_ci    if (strcmp(pTok, "interface") == 0) {
197518678f8Sopenharmony_ci        return SetIfnameInfo(dhcpConfig, pSave);
198518678f8Sopenharmony_ci    } else if (strcmp(pTok, "dns") == 0) {
199518678f8Sopenharmony_ci        return SetDnsInfo(dhcpConfig, pSave);
200518678f8Sopenharmony_ci    } else if (strcmp(pTok, "pool") == 0) {
201518678f8Sopenharmony_ci        return SetIpAddressPool(dhcpConfig, pSave);
202518678f8Sopenharmony_ci    } else if ((((strcmp(pTok, "serverId") == 0) && common) || ((strcmp(pTok, "server") == 0) && !common)) ||
203518678f8Sopenharmony_ci        (strcmp(pTok, "gateway") == 0) || (strcmp(pTok, "netmask") == 0)) {
204518678f8Sopenharmony_ci        return SetNetConfigInfo(dhcpConfig, pTok, pSave, common);
205518678f8Sopenharmony_ci    } else if ((strcmp(pTok, "leaseTime") == 0) || (strcmp(pTok, "renewalTime") == 0) ||
206518678f8Sopenharmony_ci               (strcmp(pTok, "rebindingTime") == 0)) {
207518678f8Sopenharmony_ci        return SetTimeConfigInfo(dhcpConfig, pTok, pSave);
208518678f8Sopenharmony_ci    } else if ((strcmp(pTok, "distribution") == 0) || (strcmp(pTok, "broadcast") == 0)) {
209518678f8Sopenharmony_ci        return SetEnableConfigInfo(dhcpConfig, pTok, pSave);
210518678f8Sopenharmony_ci    } else {
211518678f8Sopenharmony_ci        DHCP_LOGD("invalid key:%s", pTok);
212518678f8Sopenharmony_ci        return RET_SUCCESS;
213518678f8Sopenharmony_ci    }
214518678f8Sopenharmony_ci}
215518678f8Sopenharmony_ci
216518678f8Sopenharmony_cistatic int ParseConfigFile(const char *configFile, const char *ifname, DhcpConfig *dhcpConfig)
217518678f8Sopenharmony_ci{
218518678f8Sopenharmony_ci    if ((configFile == nullptr) || (strlen(configFile) == 0) || (dhcpConfig == nullptr)) {
219518678f8Sopenharmony_ci        DHCP_LOGE("ParseConfigFile param configFile or dhcpConfig is nullptr or len = 0!");
220518678f8Sopenharmony_ci        return RET_FAILED;
221518678f8Sopenharmony_ci    }
222518678f8Sopenharmony_ci
223518678f8Sopenharmony_ci    FILE *fp = fopen(configFile, "r");
224518678f8Sopenharmony_ci    if (fp == nullptr) {
225518678f8Sopenharmony_ci        DHCP_LOGE("fopen %{public}s failed, err:%{public}d", configFile, errno);
226518678f8Sopenharmony_ci        return RET_FAILED;
227518678f8Sopenharmony_ci    }
228518678f8Sopenharmony_ci
229518678f8Sopenharmony_ci    int bComm = 1;
230518678f8Sopenharmony_ci    int bValid = 1;
231518678f8Sopenharmony_ci    char strLine[FILE_LINE_LEN_MAX] = {0};
232518678f8Sopenharmony_ci    while (fgets(strLine, FILE_LINE_LEN_MAX, fp) != nullptr) {
233518678f8Sopenharmony_ci        DHCP_LOGI("fgets strLine = %{public}s", strLine);
234518678f8Sopenharmony_ci        if ((strchr(strLine, '#') != nullptr) || (strchr(strLine, '=') == nullptr) ||
235518678f8Sopenharmony_ci            !RemoveSpaceCharacters(strLine, FILE_LINE_LEN_MAX)) {
236518678f8Sopenharmony_ci            if (memset_s(strLine, FILE_LINE_LEN_MAX, 0, FILE_LINE_LEN_MAX) != EOK) {
237518678f8Sopenharmony_ci                break;
238518678f8Sopenharmony_ci            }
239518678f8Sopenharmony_ci            continue;
240518678f8Sopenharmony_ci        }
241518678f8Sopenharmony_ci        if (memcmp(strLine, "interface", strlen("interface")) == 0) {
242518678f8Sopenharmony_ci            bComm = 0;
243518678f8Sopenharmony_ci            bValid = 0;
244518678f8Sopenharmony_ci            if ((ifname == nullptr) || (strlen(ifname) == 0) || (strstr(strLine, ifname) != nullptr)) {
245518678f8Sopenharmony_ci                DHCP_LOGI("%s %s find ifname:%s", configFile, strLine, ((ifname == nullptr) ? "" : ifname));
246518678f8Sopenharmony_ci                bValid = 1;
247518678f8Sopenharmony_ci            } else {
248518678f8Sopenharmony_ci                DHCP_LOGI("%s %s no find ifname:%s", configFile, strLine, ifname);
249518678f8Sopenharmony_ci            }
250518678f8Sopenharmony_ci        }
251518678f8Sopenharmony_ci        if (bValid && SetDhcpConfig(dhcpConfig, strLine, bComm) != RET_SUCCESS) {
252518678f8Sopenharmony_ci            DHCP_LOGE("set dhcp config %s %s failed", configFile, strLine);
253518678f8Sopenharmony_ci            fclose(fp);
254518678f8Sopenharmony_ci            return RET_FAILED;
255518678f8Sopenharmony_ci        }
256518678f8Sopenharmony_ci        if (memset_s(strLine, FILE_LINE_LEN_MAX, 0, FILE_LINE_LEN_MAX) != EOK) {
257518678f8Sopenharmony_ci            break;
258518678f8Sopenharmony_ci        }
259518678f8Sopenharmony_ci    }
260518678f8Sopenharmony_ci    if (fclose(fp) != 0) {
261518678f8Sopenharmony_ci        DHCP_LOGE("ParseConfigFile fclose fp failed!");
262518678f8Sopenharmony_ci    }
263518678f8Sopenharmony_ci    return RET_SUCCESS;
264518678f8Sopenharmony_ci}
265518678f8Sopenharmony_ci
266518678f8Sopenharmony_cistatic int CheckDhcpConfig(DhcpConfig *config)
267518678f8Sopenharmony_ci{
268518678f8Sopenharmony_ci    if (config == nullptr) {
269518678f8Sopenharmony_ci        DHCP_LOGE("CheckDhcpConfig param config is null");
270518678f8Sopenharmony_ci        return DHCP_FALSE;
271518678f8Sopenharmony_ci    }
272518678f8Sopenharmony_ci    if ((strlen(config->ifname) > 0) && ((config->serverId == 0))) {
273518678f8Sopenharmony_ci        DHCP_LOGE("failed to config serverId or netmask");
274518678f8Sopenharmony_ci        return DHCP_FALSE;
275518678f8Sopenharmony_ci    }
276518678f8Sopenharmony_ci
277518678f8Sopenharmony_ci    if (config->renewalTime == 0) {
278518678f8Sopenharmony_ci        config->renewalTime = config->leaseTime * DHCP_RENEWAL_MULTIPLE;
279518678f8Sopenharmony_ci    }
280518678f8Sopenharmony_ci    if (config->rebindingTime == 0) {
281518678f8Sopenharmony_ci        config->rebindingTime = config->leaseTime * DHCP_REBIND_MULTIPLE;
282518678f8Sopenharmony_ci    }
283518678f8Sopenharmony_ci    return DHCP_TRUE;
284518678f8Sopenharmony_ci}
285518678f8Sopenharmony_ci
286518678f8Sopenharmony_ciint LoadConfig(const char *configFile, const char *ifname, DhcpConfig *config)
287518678f8Sopenharmony_ci{
288518678f8Sopenharmony_ci    if ((configFile == nullptr) || (strlen(configFile) == 0) || (ifname == nullptr) || (strlen(ifname) == 0) ||
289518678f8Sopenharmony_ci        (config == nullptr)) {
290518678f8Sopenharmony_ci        DHCP_LOGE("LoadConfig param configFile or ifname or config is nullptr or len = 0!");
291518678f8Sopenharmony_ci        return RET_FAILED;
292518678f8Sopenharmony_ci    }
293518678f8Sopenharmony_ci
294518678f8Sopenharmony_ci    /* Default config. */
295518678f8Sopenharmony_ci    config->leaseTime = DHCP_LEASE_TIME;
296518678f8Sopenharmony_ci    config->distribution = 1;
297518678f8Sopenharmony_ci    config->broadcast = 1;
298518678f8Sopenharmony_ci
299518678f8Sopenharmony_ci    /* Set file config. */
300518678f8Sopenharmony_ci    if (ParseConfigFile(configFile, ifname, config) != RET_SUCCESS) {
301518678f8Sopenharmony_ci        DHCP_LOGE("parse config file %{public}s error!", configFile);
302518678f8Sopenharmony_ci        return RET_FAILED;
303518678f8Sopenharmony_ci    }
304518678f8Sopenharmony_ci    DHCP_LOGI("parse config file %{public}s success", configFile);
305518678f8Sopenharmony_ci
306518678f8Sopenharmony_ci    if (!CheckDhcpConfig(config)) {
307518678f8Sopenharmony_ci        DHCP_LOGE("check dhcp config failed");
308518678f8Sopenharmony_ci        return RET_FAILED;
309518678f8Sopenharmony_ci    }
310518678f8Sopenharmony_ci
311518678f8Sopenharmony_ci    if ((strlen(config->ifname) == 0) && SetIfnameInfo(config, ifname) != RET_SUCCESS) {
312518678f8Sopenharmony_ci        DHCP_LOGE("set ifname %s error!", ifname);
313518678f8Sopenharmony_ci        return RET_FAILED;
314518678f8Sopenharmony_ci    }
315518678f8Sopenharmony_ci    return RET_SUCCESS;
316518678f8Sopenharmony_ci}
317