1  /*
2   * Copyright (c) 2023 Huawei Device Co., Ltd.
3   * Licensed under the Apache License, Version 2.0 (the "License");
4   * you may not use this file except in compliance with the License.
5   * You may obtain a copy of the License at
6   *
7   *     http://www.apache.org/licenses/LICENSE-2.0
8   *
9   * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  #include "util/logger.h"
17  
18  #include "codegen/rust_code_emitter.h"
19  
20  #include "securec.h"
21  #include "util/file.h"
22  
23  namespace OHOS {
24  namespace Idl {
25  namespace {
26  const uint32_t WRAP_ANCHOR = 4;
27  }
28  
EmitInterface()29  void RustCodeEmitter::EmitInterface()
30  {
31      String filePath = String::Format("%s/%s.rs", directory_.string(), interfaceName_.string());
32      File file(filePath, File::WRITE);
33      StringBuilder sb;
34      EmitInterface(sb);
35      String data = sb.ToString();
36      file.WriteData(data.string(), data.GetLength());
37      file.Flush();
38      file.Close();
39  }
40  
EmitInterfaceProxy()41  void RustCodeEmitter::EmitInterfaceProxy()
42  {
43      return;
44  }
45  
EmitInterfaceStub()46  void RustCodeEmitter::EmitInterfaceStub()
47  {
48      return;
49  }
50  
EmitInterface(StringBuilder & sb)51  void RustCodeEmitter::EmitInterface(StringBuilder& sb)
52  {
53      if (metaInterface_->license_) {
54          EmitLicense(sb);
55          sb.Append("\n");
56      }
57      EmitMacros(sb);
58      EmitHeaders(sb);
59      sb.Append("\n");
60      EmitCommands(sb);
61      sb.Append("\n");
62      EmitRemoteObject(sb);
63      sb.Append("\n");
64      EmitBrokers(sb);
65      sb.Append("\n");
66      EmitRemoteRequest(sb);
67      sb.Append("\n");
68      EmitStub(sb);
69      sb.Append("\n");
70      EmitProxy(sb);
71  }
72  
EmitLicense(StringBuilder & sb)73  void RustCodeEmitter::EmitLicense(StringBuilder& sb)
74  {
75      sb.Append(metaInterface_->license_).Append("\n");
76  }
77  
EmitMacros(StringBuilder & sb)78  void RustCodeEmitter::EmitMacros(StringBuilder& sb)
79  {
80      sb.Append("#![allow(missing_docs)]\n");
81      sb.Append("#![allow(unused_variables)]\n");
82      sb.Append("#![allow(unused_mut)]\n");
83      sb.Append("\n");
84  }
EmitHeaders(StringBuilder & sb)85  void RustCodeEmitter::EmitHeaders(StringBuilder& sb)
86  {
87      EmitCommonHeaders(sb);
88      EmitIPCHeaders(sb);
89      if (EmitCustomHeaders(sb)) {
90          sb.Append("\n");
91      }
92  }
93  
EmitIPCHeaders(StringBuilder & sb)94  void RustCodeEmitter::EmitIPCHeaders(StringBuilder& sb)
95  {
96      sb.Append("extern crate ipc_rust;\n");
97      sb.Append("\n");
98      sb.Append("use ipc_rust::{\n");
99      sb.Append("    IRemoteBroker, IRemoteObj, RemoteStub, Result,\n");
100      sb.Append("    RemoteObj, define_remote_object, FIRST_CALL_TRANSACTION\n");
101      sb.Append("};\n");
102      sb.Append("use ipc_rust::{MsgParcel, BorrowedMsgParcel};\n");
103      sb.Append("\n");
104  }
105  
EmitCommonHeaders(StringBuilder & sb)106  void RustCodeEmitter::EmitCommonHeaders(StringBuilder& sb)
107  {
108      bool useMap = false;
109      for (int i = 0; i < metaComponent_->typeNumber_; i++) {
110          MetaType* mt = metaComponent_->types_[i];
111          switch (mt->kind_) {
112              case TypeKind::Map: {
113                  if (!useMap) {
114                      sb.Append("use std::collections::HashMap;\n");
115                      useMap = true;
116                  }
117                  break;
118              }
119  
120              default:
121                  break;
122          }
123      }
124      if (useMap) {
125          sb.Append("\n");
126      }
127  }
128  
TrimDot(const String & fpnp)129  String RustCodeEmitter::TrimDot(const String& fpnp)
130  {
131      if (fpnp.IsEmpty()) {
132          return nullptr;
133      }
134  
135      int left = 0;
136      int right = fpnp.GetLength() - 1;
137      while (fpnp[left] == ' ' || fpnp[left] == '.') {
138          left++;
139      }
140  
141      while (fpnp[right] == ' ' || fpnp[right] == '.') {
142          right--;
143      }
144  
145      if (left >= right) {
146          return nullptr;
147      }
148  
149      return fpnp.Substring(left, right + 1);
150  }
151  
GeneratePath(const String & fpnp)152  String RustCodeEmitter::GeneratePath(const String& fpnp)
153  {
154      int pos = fpnp.IndexOf("..");
155      if (pos == -1) {
156          String path = TrimDot(fpnp);
157          if (path.IsEmpty()) {
158              return nullptr;
159          }
160          return path.Replace(".", "::");
161      }
162  
163      String path = TrimDot(fpnp.Substring(0, pos + 1));
164      String file = TrimDot(fpnp.Substring(pos));
165      if (path.IsEmpty()) {
166          return nullptr;
167      }
168  
169      if (path.IndexOf("..") != -1 || file.IndexOf("..") != -1) {
170          return nullptr;
171      }
172  
173      StringBuilder realPath;
174      realPath.Append(path.Replace(".", "::")).Append("::{");
175      realPath.Append(file.Replace(".", ", "));
176      realPath.Append("}");
177  
178      return realPath.ToString();
179  }
180  
AppendRealPath(StringBuilder & sb,const String & fpnpp)181  bool RustCodeEmitter::AppendRealPath(StringBuilder& sb, const String& fpnpp)
182  {
183      String result = GeneratePath(fpnpp);
184      if (result.IsEmpty()) {
185          return false;
186      }
187      sb.Append("use ").Append(result).Append(";\n");
188      return true;
189  }
190  
EmitCustomHeaders(StringBuilder & sb)191  bool RustCodeEmitter::EmitCustomHeaders(StringBuilder& sb)
192  {
193      uint32_t custom = false;
194      for (int i = 0; i < metaComponent_->sequenceableNumber_; i++) {
195          MetaSequenceable* ms = metaComponent_->sequenceables_[i];
196          bool addPathMsRes = AppendRealPath(sb, String(ms->namespace_) + String(ms->name_));
197          custom |= static_cast<uint32_t>(addPathMsRes);
198      }
199  
200      for (int i = 0; i < metaComponent_->interfaceNumber_; i++) {
201          MetaInterface* mi = metaComponent_->interfaces_[i];
202          if (mi->external_) {
203              bool addPathMiRes = AppendRealPath(sb, String(mi->namespace_) + String(mi->name_));
204              custom |= static_cast<uint32_t>(addPathMiRes);
205          }
206      }
207      return static_cast<bool>(custom);
208  }
209  
EmitCommands(StringBuilder & sb)210  void RustCodeEmitter::EmitCommands(StringBuilder& sb)
211  {
212      EmitCommandEnums(sb);
213  }
214  
AppendCommandEnums(StringBuilder & sb)215  void RustCodeEmitter::AppendCommandEnums(StringBuilder& sb)
216  {
217      if (metaInterface_->methodNumber_ > 0) {
218          sb.AppendFormat("    %s  = FIRST_CALL_TRANSACTION,\n",
219              GetCodeFromMethod(metaInterface_->methods_[0]->name_).string());
220      }
221  
222      for (int i = 1; i < metaInterface_->methodNumber_; i++) {
223          MetaMethod* mm = metaInterface_->methods_[i];
224          sb.AppendFormat("    %s,\n", GetCodeFromMethod(mm->name_).string(), i);
225      }
226  }
227  
GetCodeFromMethod(const char * name)228  String RustCodeEmitter::GetCodeFromMethod(const char* name)
229  {
230      StringBuilder sb;
231      sb.Append("Code");
232      const char* p = name;
233      bool hasUpper = false;
234      while (p != nullptr && *p != '\0') {
235          if (*p != '_') {
236              if (!hasUpper) {
237                  sb.Append(toupper(*p));
238                  hasUpper = true;
239              } else {
240                  sb.Append(*p);
241              }
242          } else {
243              hasUpper = false;
244          }
245          p++;
246      }
247      return sb.ToString();
248  }
249  
GetNameFromParameter(const char * name)250  String RustCodeEmitter::GetNameFromParameter(const char* name)
251  {
252      StringBuilder sb;
253      const char* p = name;
254      bool start = true;
255      while (p != nullptr && *p != '\0') {
256          if (start) {
257              if (isupper(*p)) {
258                  sb.Append('p');
259              }
260              start = false;
261          }
262  
263          if (isupper(*p)) {
264              sb.Append('_');
265              sb.Append(tolower(*p));
266          } else {
267              sb.Append(*p);
268          }
269          p++;
270      }
271      return sb.ToString();
272  }
273  
EmitCommandEnums(StringBuilder & sb)274  void RustCodeEmitter::EmitCommandEnums(StringBuilder& sb)
275  {
276      sb.AppendFormat("pub enum %sCode {\n", interfaceName_.string());
277      AppendCommandEnums(sb);
278      sb.Append("}\n");
279  }
280  
EmitRemoteObject(StringBuilder & sb)281  void RustCodeEmitter::EmitRemoteObject(StringBuilder& sb)
282  {
283      sb.Append("define_remote_object!(\n");
284      if (interfaceFullName_.StartsWith(".")) {
285          sb.AppendFormat("    %s[\"%s\"] {\n", interfaceName_.string(), interfaceName_.string());
286      } else {
287          sb.AppendFormat("    %s[\"%s\"] {\n", interfaceName_.string(), interfaceFullName_.string());
288      }
289      sb.AppendFormat("        stub: %s(on_remote_request),\n", stubName_.string());
290      sb.AppendFormat("        proxy: %s,\n", proxyName_.string());
291      sb.Append("    }\n");
292      sb.Append(");\n");
293  }
294  
EmitBrokers(StringBuilder & sb)295  void RustCodeEmitter::EmitBrokers(StringBuilder& sb)
296  {
297      sb.AppendFormat("pub trait %s: IRemoteBroker {\n", interfaceName_.string());
298      AppendBrokerMethods(sb);
299      sb.Append("}\n");
300  }
301  
WrapLine(StringBuilder & sb,int index,const String & prefix)302  void RustCodeEmitter::WrapLine(StringBuilder& sb, int index, const String& prefix)
303  {
304      if ((index + 1) % WRAP_ANCHOR == 0) {
305          sb.AppendFormat(",\n%s", prefix.string());
306      } else {
307          sb.Append(", ");
308      }
309  }
310  
AppendBrokerMethods(StringBuilder & sb)311  void RustCodeEmitter::AppendBrokerMethods(StringBuilder& sb)
312  {
313      for (int i = 0; i < metaInterface_->methodNumber_; i++) {
314          MetaMethod* mm = metaInterface_->methods_[i];
315          sb.AppendFormat("    fn %s(&self", mm->name_);
316          for (int i = 0; i < mm->parameterNumber_; i++) {
317              WrapLine(sb, i, "        ");
318              AppendBrokerParameters(sb, mm->parameters_[i]);
319          }
320          sb.AppendFormat(") -> Result<%s>;\n", ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
321      }
322  }
323  
AppendBrokerParameters(StringBuilder & sb,MetaParameter * mp)324  void RustCodeEmitter::AppendBrokerParameters(StringBuilder& sb, MetaParameter* mp)
325  {
326      sb.AppendFormat("%s: &%s",
327          GetNameFromParameter(mp->name_).string(), ConvertType(metaComponent_->types_[mp->typeIndex_], true).string());
328  }
329  
ConvertType(MetaType * mt,bool pt)330  String RustCodeEmitter::ConvertType(MetaType* mt, bool pt)
331  {
332      switch (mt->kind_) {
333          case TypeKind::Unknown:
334          case TypeKind::Void:
335              return "()";
336          case TypeKind::Char:
337              return "char";
338          case TypeKind::Boolean:
339              return "bool";
340          case TypeKind::Byte:
341              return "i8";
342          case TypeKind::Short:
343              return "i16";
344          case TypeKind::Integer:
345              return "i32";
346          case TypeKind::Long:
347              return "i64";
348          case TypeKind::Float:
349              return "f32";
350          case TypeKind::Double:
351              return "f64";
352          case TypeKind::String:
353              return pt ? "str" : "String";
354          case TypeKind::Sequenceable:
355              return  metaComponent_->sequenceables_[mt->index_]->name_;
356          case TypeKind::Interface:
357              return metaComponent_->interfaces_[mt->index_]->name_;
358          case TypeKind::Map:
359              return String::Format("HashMap<%s, %s>",
360                  ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[0]]).string(),
361                  ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[1]]).string());
362          case TypeKind::List:
363          case TypeKind::Array:
364              return  String::Format((pt ? "[%s]" : "Vec<%s>"),
365                  ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[0]]).string());
366          default:
367              return "()";
368      }
369  }
370  
EmitRemoteRequest(StringBuilder & sb)371  void RustCodeEmitter::EmitRemoteRequest(StringBuilder& sb)
372  {
373      sb.AppendFormat("fn on_remote_request(stub: &dyn %s, code: u32, data: &BorrowedMsgParcel,\n",
374          interfaceName_.string());
375      sb.Append("    reply: &mut BorrowedMsgParcel) -> Result<()> {\n");
376      sb.Append("    match code {\n");
377      AddRemoteRequestMethods(sb);
378      sb.Append("        _ => Err(-1)\n");
379      sb.Append("    }\n");
380      sb.Append("}\n");
381  }
382  
AddRemoteRequestParameters(StringBuilder & sb,MetaMethod * mm)383  void RustCodeEmitter::AddRemoteRequestParameters(StringBuilder& sb, MetaMethod* mm)
384  {
385      for (int i = 0; i < mm->parameterNumber_; i++) {
386          MetaParameter* mp = mm->parameters_[i];
387          sb.AppendFormat("&%s", GetNameFromParameter(mp->name_).string());
388          if (i + 1 != mm->parameterNumber_) {
389              WrapLine(sb, i, "                ");
390          }
391      }
392  }
393  
ReadListFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)394  void RustCodeEmitter::ReadListFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
395      const String& name, const String& prefix)
396  {
397      sb.Append(prefix).AppendFormat("let %s : %s = %s.read()?;\n",
398          name.string(), ConvertType(mt).string(), result.string());
399  }
400  
ReadMapFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)401  void RustCodeEmitter::ReadMapFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
402      const String& name, const String& prefix)
403  {
404      sb.Append(prefix).AppendFormat("let mut %s = HashMap::new();\n", name.string());
405      sb.Append(prefix).AppendFormat("let len = %s.read()?;\n", result.string());
406      sb.Append(prefix).Append("for i in 0..len {\n");
407      StringBuilder k;
408      StringBuilder v;
409      k.Append(name).Append("k");
410      v.Append(name).Append("v");
411      ReadFromParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[0]],
412          result, k.ToString().string(), prefix + "    ");
413      ReadFromParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[1]],
414          result, v.ToString().string(), prefix + "    ");
415      sb.Append(prefix + "    ").AppendFormat("%s.insert(%s, %s);\n",
416          name.string(), k.ToString().string(), v.ToString().string());
417      sb.Append(prefix).Append("}\n");
418  }
419  
ReadFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)420  void RustCodeEmitter::ReadFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
421      const String& name, const String& prefix)
422  {
423      if (mt->kind_ == TypeKind::Map) {
424          ReadMapFromParcel(sb, mt, result, name, prefix);
425      } else if (mt->kind_ == TypeKind::List || mt->kind_ == TypeKind::Array) {
426          ReadListFromParcel(sb, mt, result, name, prefix);
427      } else {
428          sb.Append(prefix).AppendFormat("let %s : %s = %s.read()?;\n",
429              name.string(), ConvertType(mt).string(), result.string());
430      }
431  }
432  
WriteListToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)433  void RustCodeEmitter::WriteListToParcel(StringBuilder& sb, MetaType* mt, const String& result,
434      const String& name, const String& prefix)
435  {
436      sb.Append(prefix).AppendFormat("%s.write(&%s)?;\n", result.string(), name.string());
437  }
438  
WriteMapToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)439  void RustCodeEmitter::WriteMapToParcel(StringBuilder& sb, MetaType* mt, const String& result,
440      const String& name, const String& prefix)
441  {
442      sb.Append(prefix).AppendFormat("%s.write(&(%s.len() as u32))?;\n", result.string(), name.string());
443      sb.Append(prefix).AppendFormat("for (key, value) in %s.iter() {\n", name.string());
444      WriteToParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[0]], result, "key", prefix + "    ");
445      WriteToParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[1]], result, "value", prefix + "    ");
446      sb.Append(prefix).Append("}\n");
447  }
448  
WriteToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)449  void RustCodeEmitter::WriteToParcel(StringBuilder& sb, MetaType* mt, const String& result,
450      const String& name, const String& prefix)
451  {
452      if (mt->kind_ == TypeKind::Map) {
453          WriteMapToParcel(sb, mt, result, name, prefix);
454      } else if (mt->kind_ == TypeKind::List || mt->kind_ == TypeKind::Array) {
455          WriteListToParcel(sb, mt, result, name, prefix);
456      } else {
457          sb.Append(prefix).AppendFormat("%s.write(&%s)?;\n", result.string(), name.string());
458      }
459  }
460  
AddRemoteRequestMethods(StringBuilder & sb)461  void RustCodeEmitter::AddRemoteRequestMethods(StringBuilder& sb)
462  {
463      for (int i = 0; i < metaInterface_->methodNumber_; i++) {
464          MetaMethod* mm = metaInterface_->methods_[i];
465          sb.AppendFormat("        %d => {\n", i + 1);
466          for (int j = 0; j < mm->parameterNumber_; j++) {
467              ReadFromParcel(sb, metaComponent_->types_[mm->parameters_[j]->typeIndex_], "data",
468                  GetNameFromParameter(mm->parameters_[j]->name_), "            ");
469          }
470          MetaType* mt = metaComponent_->types_[mm->returnTypeIndex_];
471          if (mt->kind_ != TypeKind::Unknown && mt->kind_ != TypeKind::Void) {
472              sb.AppendFormat("            let result = stub.%s(", mm->name_);
473          } else {
474              sb.AppendFormat("            stub.%s(", mm->name_);
475          }
476          AddRemoteRequestParameters(sb, mm);
477          sb.Append(")?;\n");
478          if (mt->kind_ != TypeKind::Unknown && mt->kind_ != TypeKind::Void) {
479              WriteToParcel(sb, mt, "reply", "result", "            ");
480          }
481          sb.Append("            Ok(())\n");
482          sb.Append("        }\n");
483      }
484  }
485  
EmitStub(StringBuilder & sb)486  void RustCodeEmitter::EmitStub(StringBuilder& sb)
487  {
488      sb.AppendFormat("impl %s for RemoteStub<%s> {\n", interfaceName_.string(), stubName_.string());
489      AppendStubMethods(sb);
490      sb.Append("}\n");
491  }
492  
AppendStubParameters(StringBuilder & sb,MetaMethod * mm)493  void RustCodeEmitter::AppendStubParameters(StringBuilder& sb, MetaMethod* mm)
494  {
495      for (int i = 0; i < mm->parameterNumber_; i++) {
496          sb.Append(GetNameFromParameter(mm->parameters_[i]->name_));
497          if (i + 1 != mm->parameterNumber_) {
498              WrapLine(sb, i, "                ");
499          }
500      }
501  }
502  
AppendStubMethods(StringBuilder & sb)503  void RustCodeEmitter::AppendStubMethods(StringBuilder& sb)
504  {
505      for (int i = 0; i < metaInterface_->methodNumber_; i++) {
506          MetaMethod* mm = metaInterface_->methods_[i];
507          sb.AppendFormat("    fn %s(&self", mm->name_);
508          for (int i = 0; i < mm->parameterNumber_; i++) {
509              WrapLine(sb, i, "        ");
510              AppendBrokerParameters(sb, mm->parameters_[i]);
511          }
512  
513          sb.AppendFormat(") -> Result<%s> {\n",
514              ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
515          sb.AppendFormat("        self.0.%s(", mm->name_);
516          AppendStubParameters(sb, mm);
517          sb.Append(")\n");
518          sb.Append("    }\n");
519          if (i != metaInterface_->methodNumber_ - 1) {
520              sb.Append("\n");
521          }
522      }
523  }
524  
EmitProxy(StringBuilder & sb)525  void RustCodeEmitter::EmitProxy(StringBuilder& sb)
526  {
527      sb.AppendFormat("impl %s for %s {\n", interfaceName_.string(), proxyName_.string());
528      AppendProxyMethods(sb);
529      sb.Append("}\n");
530  }
531  
AppendProxyMethods(StringBuilder & sb)532  void RustCodeEmitter::AppendProxyMethods(StringBuilder& sb)
533  {
534      for (int i = 0; i < metaInterface_->methodNumber_; i++) {
535          MetaMethod* mm = metaInterface_->methods_[i];
536          sb.AppendFormat("    fn %s(&self", mm->name_);
537          for (int i = 0; i < mm->parameterNumber_; i++) {
538              WrapLine(sb, i, "        ");
539              AppendBrokerParameters(sb, mm->parameters_[i]);
540          }
541          sb.AppendFormat(") -> Result<%s> {\n",
542              ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
543          sb.Append("        let mut data = MsgParcel::new().expect(\"MsgParcel should success\");\n");
544          for (int j = 0; j < mm->parameterNumber_; j++) {
545              WriteToParcel(sb, metaComponent_->types_[mm->parameters_[j]->typeIndex_], "data",
546                  GetNameFromParameter(mm->parameters_[j]->name_), "        ");
547          }
548          MetaType* mt = metaComponent_->types_[mm->returnTypeIndex_];
549          if (mt->kind_ == TypeKind::Unknown || mt->kind_ == TypeKind::Void) {
550              sb.AppendFormat("        let _reply = self.remote.send_request(%sCode", interfaceName_.string());
551          } else {
552              sb.AppendFormat("        let reply = self.remote.send_request(%sCode", interfaceName_.string());
553          }
554          sb.AppendFormat("::%s as u32, &data, ", GetCodeFromMethod(mm->name_).string());
555          if ((mm->properties_ & METHOD_PROPERTY_ONEWAY) != 0) {
556              sb.Append("true");
557          } else {
558              sb.Append("false");
559          }
560          sb.Append(")?;\n");
561          if (mt->kind_ == TypeKind::Unknown || mt->kind_ == TypeKind::Void) {
562              sb.Append("        ").Append("Ok(())\n");
563          } else {
564              ReadFromParcel(sb, mt, "reply", "result", "        ");
565              sb.Append("        ").Append("Ok(result)\n");
566          }
567          sb.Append("    }\n");
568  
569          if (i != metaInterface_->methodNumber_ - 1) {
570              sb.Append("\n");
571          }
572      }
573  }
574  }
575  }
576