├── .github └── workflows │ └── test.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── README_zh.md ├── benches └── bench_read.rs ├── cip ├── Cargo.toml ├── README.md └── src │ ├── codec │ ├── decode.rs │ ├── decode │ │ └── message_reply.rs │ ├── encode.rs │ ├── encode │ │ ├── epath.rs │ │ └── message.rs │ └── mod.rs │ ├── connection.rs │ ├── epath.rs │ ├── error.rs │ ├── identity.rs │ ├── lib.rs │ ├── list_service.rs │ ├── message.rs │ ├── revision.rs │ ├── service │ ├── common_services.rs │ ├── common_services │ │ └── multiple_packet.rs │ ├── heartbeat.rs │ ├── message_service.rs │ ├── mod.rs │ └── request.rs │ ├── socket.rs │ └── status.rs ├── core ├── Cargo.toml ├── README.md └── src │ ├── cip │ ├── common_packet.rs │ └── mod.rs │ ├── codec │ ├── decode.rs │ ├── decode │ │ ├── impls.rs │ │ ├── little_endian.rs │ │ └── visitor.rs │ ├── encode.rs │ ├── encode │ │ ├── impls.rs │ │ └── slice.rs │ └── mod.rs │ ├── either.rs │ ├── error.rs │ ├── hex.rs │ ├── iter.rs │ ├── lib.rs │ └── string.rs ├── eip ├── Cargo.toml ├── README.md └── src │ ├── codec │ ├── command.rs │ ├── common_packet.rs │ └── mod.rs │ ├── command.rs │ ├── consts.rs │ ├── context.rs │ ├── discover.rs │ ├── encapsulation.rs │ ├── error.rs │ ├── framed.rs │ └── lib.rs ├── examples ├── ab-list-tag.rs ├── ab-multiple-service.rs ├── ab-program-tag.rs ├── ab-read-modify-write.rs ├── ab-read-template.rs ├── ab-symbol-instance-address.rs ├── ab-tag-read-fragmented.rs ├── ab-tag-rw-connected.rs ├── ab-tag-rw.rs ├── eip-discovery.rs ├── get-attribute-all.rs └── get-attribute-single.rs ├── rustfmt.toml └── src ├── adapters ├── eip.rs └── mod.rs ├── client ├── ab_eip.rs ├── ab_eip │ ├── interceptor.rs │ ├── path.rs │ ├── service.rs │ ├── symbol.rs │ ├── template.rs │ └── value.rs ├── eip.rs └── mod.rs ├── error.rs └── lib.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | rust: [stable, beta] 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install Rust toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: ${{ matrix.rust }} 25 | components: rustfmt 26 | override: true 27 | - name: Verify versions 28 | run: rustc --version && rustup --version && cargo --version 29 | - name: Cargo Build 30 | run: cargo build --verbose 31 | - name: Build examples 32 | run: cargo build --examples 33 | - name: Run tests 34 | run: cargo test --verbose 35 | - name: Check code style 36 | run: cargo fmt -- --check 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["rseip"] 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rseip" 3 | version = "0.3.1" 4 | edition = "2021" 5 | readme = "README.md" 6 | description = "rseip - Ethernet/IP (CIP) client in pure Rust" 7 | license = "MIT" 8 | homepage = "https://github.com/Joylei/eip-rs" 9 | repository = "https://github.com/Joylei/eip-rs.git" 10 | documentation = "https://docs.rs/crate/rseip/" 11 | keywords = ["ethernet", "codec", "industry", "eip", "cip"] 12 | categories = ["asynchronous", "hardware-support"] 13 | authors = ["joylei "] 14 | resolver = "2" 15 | 16 | [badges] 17 | maintenance = { status = "passively-maintained" } 18 | 19 | [package.metadata.docs.rs] 20 | all-features = true 21 | 22 | [workspace] 23 | members = ["./cip", "./core", "./eip", "."] 24 | 25 | [dependencies] 26 | rseip-core = { path = "./core", default-features = false, features = [ 27 | "cip", 28 | ], version = "0.1" } 29 | rseip-eip = { path = "./eip", default-features = false, version = "0.2" } 30 | rseip-cip = { path = "./cip", default-features = false, version = "0.2" } 31 | bytes = "1" 32 | byteorder = "1" 33 | log = "0.4" 34 | tokio = { version = "1", features = ["net", "io-util"] } 35 | tokio-util = { version = "0.7", features = ["codec"] } 36 | futures-util = { version = "0.3", features = ["sink"] } 37 | async-trait = "0.1" 38 | smallvec = "1" 39 | 40 | [dev-dependencies] 41 | env_logger = "0.9" 42 | futures = "0.3" 43 | tokio = { version = "1", default-features = false, features = [ 44 | "rt-multi-thread", 45 | "time", 46 | "sync", 47 | "macros", 48 | ] } 49 | anyhow = "1" 50 | criterion = { version = "0.3", features = [ 51 | "async_futures", 52 | "async_tokio", 53 | "cargo_bench_support", 54 | ] } 55 | 56 | [features] 57 | default = ["inlinable-string", "error-explain"] 58 | inlinable-string = ["rseip-cip/inlinable-string", "rseip-eip/inlinable-string"] 59 | error-explain = ["rseip-cip/error-explain", "rseip-eip/error-explain"] 60 | 61 | [build] 62 | #rustc-wrapper = "sccache" 63 | 64 | [profile.release] 65 | lto = 'thin' 66 | panic = 'abort' 67 | codegen-units = 1 68 | 69 | [profile.bench] 70 | lto = 'thin' 71 | codegen-units = 1 72 | debug = 1 73 | 74 | [profile.test] 75 | debug = 1 76 | 77 | [[bench]] 78 | name = "bench_read" 79 | harness = false 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 joylei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [EN](./README.md) | [中文](./README_zh.md) 2 | 3 | # rseip 4 | [![crates.io](https://img.shields.io/crates/v/rseip.svg)](https://crates.io/crates/rseip) 5 | [![docs](https://docs.rs/rseip/badge.svg)](https://docs.rs/rseip) 6 | [![build](https://github.com/joylei/eip-rs/workflows/build/badge.svg?branch=main)](https://github.com/joylei/eip-rs/actions?query=workflow%3A%22build%22) 7 | [![license](https://img.shields.io/crates/l/rseip.svg)](https://github.com/joylei/eip-rs/blob/master/LICENSE) 8 | 9 | Ethernet/IP (CIP) client in pure Rust, for generic CIP and AB PLC 10 | 11 | ## Features 12 | 13 | - Pure Rust Library 14 | - Asynchronous 15 | - Prefer static dispatch 16 | - Extensible 17 | - Explicit Messaging (Connected / Unconnected) 18 | - Open Source 19 | 20 | ### Services Supported for AB PLC 21 | 22 | - Read Tag 23 | - Write Tag 24 | - Read Tag Fragmented 25 | - Write Tag Fragmented 26 | - Read Modify Write Tag 27 | - Get Instance Attribute List (list tag) 28 | - Read Template 29 | 30 | ## How to use 31 | 32 | Add `rseip` to your cargo project's dependencies 33 | 34 | ```toml 35 | rseip="0.3" 36 | ``` 37 | 38 | Please find detailed guides and examples from below sections. 39 | 40 | 41 | ## Example 42 | 43 | ### Tag Read/Write for Allen-bradley CompactLogIx device 44 | 45 | ```rust 46 | use anyhow::Result; 47 | use rseip::client::ab_eip::*; 48 | use rseip::precludes::*; 49 | 50 | #[tokio::main] 51 | pub async fn main() -> Result<()> { 52 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 53 | .await? 54 | .with_connection_path(PortSegment::default()); 55 | let tag = EPath::parse_tag("test_car1_x")?; 56 | println!("read tag..."); 57 | let value: TagValue = client.read_tag(tag.clone()).await?; 58 | println!("tag value: {:?}", value); 59 | client.write_tag(tag, value).await?; 60 | println!("write tag - done"); 61 | client.close().await?; 62 | Ok(()) 63 | } 64 | ``` 65 | 66 | Please find more examples within [examples](https://github.com/Joylei/eip-rs/tree/main/examples). 67 | 68 | ## Guides 69 | ### Quick start 70 | 71 | Add `rseip` to your cargo project's dependencies 72 | 73 | ```toml 74 | rseip="0.3" 75 | ``` 76 | 77 | Then, import modules of `rseip` to your project 78 | ```rust 79 | use rseip::client::ab_eip::*; 80 | use rseip::precludes::*; 81 | ``` 82 | 83 | Then, create an unconnected client 84 | ```rust 85 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 86 | .await? 87 | .with_connection_path(PortSegment::default()); 88 | ``` 89 | 90 | or create a connection 91 | ```rust 92 | let mut client = 93 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 94 | ``` 95 | 96 | #### Read from a tag 97 | ```rust 98 | let tag = EPath::parse_tag("test_car1_x")?; 99 | println!("read tag..."); 100 | let value: TagValue = client.read_tag(tag.clone()).await?; 101 | ``` 102 | #### Write to a tag 103 | ```rust 104 | let tag = EPath::parse_tag("test_car1_x")?; 105 | let value = TagValue { 106 | tag_type: TagType::Dint, 107 | value: 10_i32, 108 | }; 109 | client.write_tag(tag, value).await?; 110 | println!("write tag - done"); 111 | ``` 112 | 113 | ### About `TagValue`, `Decode`, and `Encode` 114 | 115 | As you may know, there are atomic types, structure types, and array type of tags. The library provides `Encode` to encode values, `Decode` to decode values, and `TagValue` to manipulate tag data values. The library already implements `Encode` and `Decode` for some rust types: `bool`,`i8`,`u8`,`i16`,`u16`,`i32`,`u32`,`i64`,`u64`,`f32`,`f64`,`i128`,`u128`,`()`,`Option`,`Tuple`,`Vec`,`[T;N]`,`SmallVec`. For structure type, you need to implement `Encode` and `Decode` by yourself. 116 | 117 | #### Read 118 | 119 | To get a single value (atomic/structure), and you know the exact mapped type, do like this 120 | ```rust 121 | let value: TagValue = client.read_tag(tag).await?; 122 | println!("{:?}",value); 123 | ``` 124 | 125 | To get the tag type, and you do not care about the data part, do like this: 126 | ```rust 127 | let value: TagValue<()> = client.read_tag(tag).await?; 128 | println!("{:?}",value.tag_type); 129 | ``` 130 | 131 | To get the raw bytes whatever the data part holds, do like this: 132 | ```rust 133 | let value: TagValue = client.read_tag(tag).await?; 134 | ``` 135 | 136 | To iterate values, and you know the exact mapped type, do like this: 137 | ```rust 138 | let iter: TagValueTypedIter = client.read_tag(tag).await?; 139 | println!("{:?}", iter.tag_type()); 140 | while let Some(res) = iter.next(){ 141 | println!("{:?}", res); 142 | } 143 | ``` 144 | 145 | To iterate values, and you do not know the exact mapped type, do like this: 146 | ```rust 147 | let iter: TagValueIter = client.read_tag(tag).await?; 148 | println!("{:?}", iter.tag_type()); 149 | let res = iter.next::().unwrap(); 150 | println!("{:?}", res); 151 | let res = iter.next::().unwrap(); 152 | println!("{:?}", res); 153 | let res = iter.next::().unwrap(); 154 | println!("{:?}", res); 155 | ``` 156 | 157 | To read more than 1 elements of an `Array`, do like this: 158 | ```rust 159 | let value: TagValue> = client.read_tag((tag,5_u16)).await?; 160 | println!("{:?}",value); 161 | ``` 162 | 163 | #### Write 164 | 165 | You must provide the tag type before you write to a tag. Normally, you can retrieve it by reading the tag. For structure type, you cannot reply on or persist the tag type (so called `structure handle`), it might change because it is a calculated value (CRC based). 166 | 167 | To write a single value (atomic/structure), do like this: 168 | ```rust 169 | let value = TagValue { 170 | tag_type: TagType::Dint, 171 | value: 10_i32, 172 | }; 173 | client.write_tag(tag, value).await?; 174 | ``` 175 | 176 | To write raw bytes, do like this: 177 | ```rust 178 | let bytes:&[u8] = &[0,1,2,3]; 179 | let value = TagValue { 180 | tag_type: TagType::Dint, 181 | value: bytes, 182 | }; 183 | client.write_tag(tag, value).await?; 184 | ``` 185 | 186 | To write multiple values to an array, do like this: 187 | ```rust 188 | let items: Vec = ...; 189 | let value = TagValue { 190 | tag_type: TagType::Dint, 191 | value: items, 192 | }; 193 | client.write_tag(tag, value).await?; 194 | ``` 195 | 196 | ### Moreover 197 | 198 | For some reasons, `TagValue` does not work for all type that implements `Encode` or `Decode`. 199 | 200 | But you can work without `TagValue`. You can define your own value holder, as long as it implements `Encode` and `Decode`. 201 | 202 | For simple cases, `Tuple` should be a good option. 203 | ```rust 204 | let (tag_type,value):(TagType, i32) = client.read_tag(tag).await?; 205 | client.write_tag(tag, (tag_type, 1_u16, value)).await?; 206 | ``` 207 | 208 | ## License 209 | 210 | MIT 211 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [EN](./README.md) | [中文](./README_zh.md) 2 | 3 | # rseip 4 | [![crates.io](https://img.shields.io/crates/v/rseip.svg)](https://crates.io/crates/rseip) 5 | [![docs](https://docs.rs/rseip/badge.svg)](https://docs.rs/rseip) 6 | [![build](https://github.com/joylei/eip-rs/workflows/build/badge.svg?branch=main)](https://github.com/joylei/eip-rs/actions?query=workflow%3A%22build%22) 7 | [![license](https://img.shields.io/crates/l/rseip.svg)](https://github.com/joylei/eip-rs/blob/master/LICENSE) 8 | 9 | 纯Rust语言写的CIP客户端,支持CIP和AB PLC 10 | 11 | ## 特性 12 | 13 | - 纯Rust语言 14 | - 异步支持 15 | - 偏好静态派发 16 | - 可扩展 17 | - 显式CIP消息(无连接或有连接) 18 | - 开源 19 | 20 | ### 支持AB PLC的服务 21 | 22 | - TAG 读操作 23 | - TAG 写操作 24 | - TAG 分片写操作 (Tag Read Fragmented) 25 | - TAG 分片写操作 (Tag Write Fragmented) 26 | - TAG 读修改写操作 (Tag Read Modify Write) 27 | - TAG 遍历 (Tag List) 28 | - 读取模板 29 | 30 | ## 怎么安装 31 | 32 | 添加 `rseip` 到 `cargo` 项目的依赖 33 | 34 | ```toml 35 | rseip="0.3" 36 | ``` 37 | 38 | 继续往下查看更多示例和帮助。 39 | 40 | 41 | ## 示例 42 | 43 | ### AB PLC Tag 读写 44 | 45 | ```rust 46 | use anyhow::Result; 47 | use rseip::client::ab_eip::*; 48 | use rseip::precludes::*; 49 | 50 | #[tokio::main] 51 | pub async fn main() -> Result<()> { 52 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 53 | .await? 54 | .with_connection_path(PortSegment::default()); 55 | let tag = EPath::parse_tag("test_car1_x")?; 56 | println!("read tag..."); 57 | let value: TagValue = client.read_tag(tag.clone()).await?; 58 | println!("tag value: {:?}", value); 59 | client.write_tag(tag, value).await?; 60 | println!("write tag - done"); 61 | client.close().await?; 62 | Ok(()) 63 | } 64 | ``` 65 | 66 | 请到这里查看更多示例代码[examples](https://github.com/Joylei/eip-rs/tree/main/examples). 67 | 68 | ## 指南 69 | 70 | ### 快速开始 71 | 72 | 添加 `rseip` 到 `cargo` 项目的依赖 73 | 74 | ```toml 75 | rseip="0.3" 76 | ``` 77 | 78 | 然后,导入 `rseip` 的模块到项目中 79 | ```rust 80 | use rseip::client::ab_eip::*; 81 | use rseip::precludes::*; 82 | ``` 83 | 84 | 然后, 创建一个无连接的客户端 85 | ```rust 86 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 87 | .await? 88 | .with_connection_path(PortSegment::default()); 89 | ``` 90 | 91 | 或者, 创建一个有连接的客户端 92 | ```rust 93 | let mut client = 94 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 95 | ``` 96 | 97 | #### 读 TAG 98 | ```rust 99 | let tag = EPath::parse_tag("test_car1_x")?; 100 | println!("read tag..."); 101 | let value: TagValue = client.read_tag(tag.clone()).await?; 102 | ``` 103 | #### 写 TAG 104 | ```rust 105 | let tag = EPath::parse_tag("test_car1_x")?; 106 | let value = TagValue { 107 | tag_type: TagType::Dint, 108 | value: 10_i32, 109 | }; 110 | client.write_tag(tag, value).await?; 111 | println!("write tag - done"); 112 | ``` 113 | 114 | ### 关于 `TagValue`, `Decode`, and `Encode` 115 | 116 | AB PLC 有原子类型, 结构类型,以及数组。本项目提供了 `Encode` 来编码数据,`Decode`来解码数据,以及`TagValue`来辅助数据值的操作。此项目已经为以下类型实现了`Encode` and `Decode`: `bool`,`i8`,`u8`,`i16`,`u16`,`i32`,`u32`,`i64`,`u64`,`f32`,`f64`,`i128`,`u128`,`()`,`Option`,`Tuple`,`Vec`,`[T;N]`,`SmallVec`。对于结构类型的数据你应该自己实现`Encode` and `Decode`。 117 | 118 | #### 读数据 119 | 120 | 想要读取单个值(原子类型或结构类型),并且你知道要映射到的类型,可以这样操作: 121 | ```rust 122 | let value: TagValue = client.read_tag(tag).await?; 123 | println!("{:?}",value); 124 | ``` 125 | 126 | 想要读取 TAG 值类型,并且你不关心数据值,可以这样操作: 127 | ```rust 128 | let value: TagValue<()> = client.read_tag(tag).await?; 129 | println!("{:?}",value.tag_type); 130 | ``` 131 | 132 | 想要读取原始的字节数据,可以这样操作: 133 | ```rust 134 | let value: TagValue = client.read_tag(tag).await?; 135 | ``` 136 | 137 | 想要遍历读取的数据值,可以这样操作: 138 | ```rust 139 | let iter: TagValueTypedIter = client.read_tag(tag).await?; 140 | println!("{:?}", iter.tag_type()); 141 | while let Some(res) = iter.next(){ 142 | println!("{:?}", res); 143 | } 144 | ``` 145 | 146 | 想要遍历读取的数据值,并且不知道具体类型,可以这样操作: 147 | ```rust 148 | let iter: TagValueIter = client.read_tag(tag).await?; 149 | println!("{:?}", iter.tag_type()); 150 | let res = iter.next::().unwrap(); 151 | println!("{:?}", res); 152 | let res = iter.next::().unwrap(); 153 | println!("{:?}", res); 154 | let res = iter.next::().unwrap(); 155 | println!("{:?}", res); 156 | ``` 157 | 158 | 想要读取数组的多个元素,可以这样操作: 159 | ```rust 160 | let value: TagValue> = client.read_tag((tag,5_u16)).await?; 161 | println!("{:?}",value); 162 | ``` 163 | 164 | #### 写数据 165 | 166 | 如果想要写数据,你必须知道TAG的类型。通常情况下,可以通过读的方式获得。但是对于结构类型,你不能持久的依赖所获得类型(`structure handle`),因为它是一个计算值(CRC),当结构的定义变化时,这个值可能改变。 167 | 168 | 想要写入单个值(原子类型或结构类型),可以这样操作: 169 | ```rust 170 | let value = TagValue { 171 | tag_type: TagType::Dint, 172 | value: 10_i32, 173 | }; 174 | client.write_tag(tag, value).await?; 175 | ``` 176 | 177 | 想要写入字节数据,可以这样操作: 178 | ```rust 179 | let bytes:&[u8] = &[0,1,2,3]; 180 | let value = TagValue { 181 | tag_type: TagType::Dint, 182 | value: bytes, 183 | }; 184 | client.write_tag(tag, value).await?; 185 | ``` 186 | 也支持写入`bytes::Bytes`。 187 | 188 | 189 | 想要向数组写入多个数据元素,可以这样操作: 190 | ```rust 191 | let items: Vec = ...; 192 | let value = TagValue { 193 | tag_type: TagType::Dint, 194 | value: items, 195 | }; 196 | client.write_tag(tag, value).await?; 197 | ``` 198 | 也支持写入`[T;N]`。 199 | 200 | 201 | ### 此外 202 | 203 | 由于一些考虑,`TagValue`并不支持任意实现了`Encode` or `Decode`的类型的读写。 204 | 205 | 但是你可以不使用 `TagValue`,可以定义自己的值编解码器,只要实现了`Encode`和`Decode`。由于`Encode`需要计算字节数,自己实现的编码器比通用的实现性能会更好。 206 | 207 | 对于简单场景来说,`Tuple`应该足够应对了。 208 | ```rust 209 | let (tag_type,value):(TagType, i32) = client.read_tag(tag).await?; 210 | client.write_tag(tag, (tag_type, 1_u16, value)).await?; 211 | ``` 212 | 213 | ## 开源协议 214 | 215 | MIT 216 | -------------------------------------------------------------------------------- /benches/bench_read.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::sync::Arc; 3 | 4 | use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; 5 | use rseip::cip::connection::OpenOptions; 6 | use rseip::client::ab_eip::*; 7 | use rseip::precludes::*; 8 | 9 | fn bench_read(c: &mut Criterion) { 10 | c.bench_function("async read", |b| { 11 | let rt = tokio::runtime::Builder::new_multi_thread() 12 | .enable_all() 13 | .build() 14 | .unwrap(); 15 | let client = rt.block_on(async { 16 | let client = AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()) 17 | .await 18 | .unwrap(); 19 | Arc::new(UnsafeCell::new(client)) 20 | }); 21 | b.to_async(rt).iter_batched( 22 | || client.clone(), 23 | |client| async move { 24 | let tag = EPath::from_symbol("test_car1_x"); 25 | let client = unsafe { &mut *client.get() }; 26 | let _value: TagValue = client.read_tag(tag).await.unwrap(); 27 | }, 28 | BatchSize::PerIteration, 29 | ) 30 | }); 31 | } 32 | 33 | criterion_group!(benches, bench_read); 34 | criterion_main!(benches); 35 | -------------------------------------------------------------------------------- /cip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rseip-cip" 3 | version = "0.2.1" 4 | edition = "2021" 5 | description = "common industry protocol for rseip" 6 | license = "MIT" 7 | homepage = "https://github.com/Joylei/eip-rs" 8 | repository = "https://github.com/Joylei/eip-rs.git" 9 | documentation = "https://docs.rs/crate/rseip-cip/" 10 | keywords = ["ethernet", "codec", "industry", "eip", "cip"] 11 | categories = ["asynchronous", "hardware-support"] 12 | authors = ["joylei "] 13 | resolver = "2" 14 | 15 | [dependencies] 16 | rseip-core = { path = "../core", default-features = false, features = [ 17 | "cip", 18 | ], version = "0.1" } 19 | bytes = "1" 20 | byteorder = "1" 21 | log = "0.4" 22 | tokio = { version = "1", features = ["io-util"] } 23 | tokio-util = { version = "0.7", features = ["codec", "net"] } 24 | futures-util = { version = "0.3", features = ["sink"] } 25 | async-trait = "0.1" 26 | rand = "0.8" 27 | smallvec = "1" 28 | 29 | [features] 30 | default = ["inlinable-string", "error-explain"] 31 | inlinable-string = ["rseip-core/feat-inlinable-string"] 32 | error-explain = [] 33 | -------------------------------------------------------------------------------- /cip/README.md: -------------------------------------------------------------------------------- 1 | # rseip-cip 2 | 3 | Common industry protocol for `rseip`, please look at [rseip project](https://github.com/Joylei/eip-rs) for more information. 4 | 5 | ## License 6 | 7 | MIT -------------------------------------------------------------------------------- /cip/src/codec/decode.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | pub mod message_reply; 8 | use crate::*; 9 | use crate::{identity::IdentityObject, socket::SocketAddr}; 10 | use bytes::Buf; 11 | use core::{slice, str}; 12 | use rseip_core::codec::{Decode, Decoder}; 13 | use std::borrow::Cow; 14 | 15 | impl<'de> Decode<'de> for IdentityObject<'de> { 16 | fn decode(mut decoder: D) -> Result 17 | where 18 | D: rseip_core::codec::Decoder<'de>, 19 | { 20 | decoder.ensure_size(33)?; 21 | //let product_name_len = data[32]; 22 | 23 | let identity = IdentityObject { 24 | protocol_version: decoder.decode_u16(), 25 | socket_addr: { 26 | let addr = decoder.buf_mut().copy_to_bytes(16); 27 | SocketAddr::from_bytes::(addr)? 28 | }, 29 | vendor_id: decoder.decode_u16(), 30 | device_type: decoder.decode_u16(), 31 | product_code: decoder.decode_u16(), 32 | revision: Revision { 33 | major: decoder.decode_u8(), 34 | minor: decoder.decode_u8(), 35 | }, 36 | status: decoder.decode_u16(), 37 | serial_number: decoder.decode_u32(), 38 | product_name: { 39 | let name_len = decoder.decode_u8(); 40 | decoder.ensure_size(name_len as usize + 1)?; 41 | let data = decoder.buf_mut().copy_to_bytes(name_len as usize); 42 | unsafe { 43 | let buf = data.as_ptr(); 44 | let buf = slice::from_raw_parts(buf, name_len as usize); 45 | let name = str::from_utf8_unchecked(buf); 46 | Cow::from(name) 47 | } 48 | }, 49 | state: decoder.decode_u8(), 50 | }; 51 | 52 | Ok(identity) 53 | } 54 | } 55 | 56 | impl<'de> Decode<'de> for ListServiceItem<'de> { 57 | fn decode(mut decoder: D) -> Result 58 | where 59 | D: Decoder<'de>, 60 | { 61 | debug_assert!(decoder.remaining() > 4); 62 | 63 | let item = ListServiceItem { 64 | protocol_version: decoder.decode_u16(), 65 | capability: decoder.decode_u16(), 66 | name: { 67 | const STR_LEN: usize = 16; 68 | decoder.ensure_size(STR_LEN)?; 69 | let data = decoder.buf_mut().copy_to_bytes(STR_LEN); 70 | unsafe { 71 | let buf = data.as_ptr(); 72 | let buf = slice::from_raw_parts(buf, STR_LEN); 73 | let name = str::from_utf8_unchecked(buf); 74 | Cow::from(name) 75 | } 76 | }, 77 | }; 78 | 79 | Ok(item) 80 | } 81 | } 82 | 83 | // fn decode_c_str<'de>(data: Bytes) -> &'de str { 84 | // unsafe { 85 | // let buf = data.as_ptr(); 86 | // let len = libc::strlen(buf as *const i8); 87 | // let buf = slice::from_raw_parts(buf, len); 88 | // str::from_utf8_unchecked(buf) 89 | // } 90 | // } 91 | -------------------------------------------------------------------------------- /cip/src/codec/decode/message_reply.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::*; 8 | use crate::{ 9 | connection::*, 10 | error::{cip_error, cip_error_status}, 11 | Status, 12 | }; 13 | use bytes::Buf; 14 | use rseip_core::{ 15 | cip::CommonPacketIter, 16 | codec::{Decode, Decoder}, 17 | Either, Error, 18 | }; 19 | 20 | #[inline] 21 | pub fn decode_service_and_status<'de, D>(mut decoder: D) -> Result<(u8, Status), D::Error> 22 | where 23 | D: Decoder<'de>, 24 | { 25 | decoder.ensure_size(4)?; 26 | let reply_service = decoder.decode_u8(); // buf[0] 27 | decoder.buf_mut().advance(1); // buf[1] 28 | let general_status = decoder.decode_u8(); //buf[2] 29 | let extended_status_size = decoder.decode_u8(); // buf[3] 30 | decoder.ensure_size((extended_status_size * 2) as usize)?; 31 | let extended_status = match extended_status_size { 32 | 0 => None, 33 | 1 => Some(decoder.decode_u16()), 34 | v => return Err(Error::invalid_value("one of 0,1", v)), 35 | }; 36 | let status = Status { 37 | general: general_status, 38 | extended: extended_status, 39 | }; 40 | 41 | Ok((reply_service, status)) 42 | } 43 | 44 | impl<'de, R> Decode<'de> for MessageReply 45 | where 46 | R: Decode<'de>, 47 | { 48 | #[inline] 49 | fn decode(mut decoder: D) -> Result 50 | where 51 | D: Decoder<'de>, 52 | { 53 | let (reply_service, status) = decode_service_and_status(&mut decoder)?; 54 | if status.is_err() { 55 | return Err(cip_error_status(status)); 56 | } 57 | let data = decoder.decode_any()?; 58 | Ok(MessageReply::new(reply_service, status, data)) 59 | } 60 | } 61 | 62 | /// decode message reply from common packet for connected send 63 | /// 64 | /// ```rust, ignore 65 | /// let data:(u16, MessageReply) = decode_connected_send(cpf)?; 66 | /// ```` 67 | #[inline] 68 | pub fn decode_connected_send<'de, D, R>( 69 | mut cpf: CommonPacketIter<'de, D>, 70 | ) -> Result<(u16, R), D::Error> 71 | where 72 | D: Decoder<'de>, 73 | R: Decode<'de> + 'static, 74 | { 75 | if cpf.len() != 2 { 76 | return Err(cip_error("common packet - expect 2 items")); 77 | } 78 | // should be connected address 79 | ensure_connected_address(&mut cpf)?; 80 | // should be connected data item 81 | if let Some(res) = cpf.next_typed() { 82 | let data_item: CommonPacketItem<(u16, _)> = res?; 83 | data_item.ensure_type_code::(0xB1)?; 84 | return Ok(data_item.data); 85 | } 86 | Err(cip_error("common packet - expect connected data item")) 87 | } 88 | 89 | /// decode message reply from common packet for unconnected send 90 | /// 91 | /// ```rust, ignore 92 | /// let data:MessageReply= decode_unconnected_send(cpf)?; 93 | /// ``` 94 | #[inline] 95 | pub fn decode_unconnected_send<'de, D, R>(mut cpf: CommonPacketIter<'de, D>) -> Result 96 | where 97 | D: Decoder<'de>, 98 | R: Decode<'de> + 'static, 99 | { 100 | if cpf.len() != 2 { 101 | return Err(cip_error("common packet - expected 2 items")); 102 | } 103 | // should be null address 104 | ensure_null_address(&mut cpf)?; 105 | 106 | // should be unconnected data item 107 | if let Some(res) = cpf.next_typed() { 108 | let data_item: CommonPacketItem<_> = res?; 109 | data_item.ensure_type_code::(0xB2)?; 110 | return Ok(data_item.data); 111 | } 112 | Err(cip_error("common packet - expect connected data item")) 113 | } 114 | 115 | impl<'de> Decode<'de> for ForwardOpenReply { 116 | #[inline] 117 | fn decode(mut decoder: D) -> Result 118 | where 119 | D: Decoder<'de>, 120 | { 121 | let (reply_service, status) = decode_service_and_status(&mut decoder)?; 122 | let data = if status.is_err() { 123 | let v = decode_forward_fail(decoder, status)?; 124 | Either::Right(v) 125 | } else { 126 | let v = decode_forward_open_success(decoder)?; 127 | Either::Left(v) 128 | }; 129 | Ok(Self(MessageReply::new(reply_service, status, data))) 130 | } 131 | } 132 | 133 | impl<'de> Decode<'de> for ForwardCloseReply { 134 | #[inline] 135 | fn decode(mut decoder: D) -> Result 136 | where 137 | D: Decoder<'de>, 138 | { 139 | let (reply_service, status) = decode_service_and_status(&mut decoder)?; 140 | let data = if status.is_err() { 141 | let v = decode_forward_fail(decoder, status)?; 142 | Either::Right(v) 143 | } else { 144 | let v = decode_forward_close_success(decoder)?; 145 | Either::Left(v) 146 | }; 147 | Ok(Self(MessageReply::new(reply_service, status, data))) 148 | } 149 | } 150 | 151 | #[inline] 152 | fn decode_forward_open_success<'de, D>(mut decoder: D) -> Result 153 | where 154 | D: Decoder<'de>, 155 | { 156 | decoder.ensure_size(26)?; 157 | let v = ForwardOpenSuccess { 158 | o_t_connection_id: decoder.decode_u32(), 159 | t_o_connection_id: decoder.decode_u32(), 160 | connection_serial_number: decoder.decode_u16(), 161 | originator_vendor_id: decoder.decode_u16(), 162 | originator_serial_number: decoder.decode_u32(), 163 | o_t_api: decoder.decode_u32(), 164 | t_o_api: decoder.decode_u32(), 165 | app_data: { 166 | // buf[24], size in words 167 | let app_data_size = 2 * decoder.decode_u8() as usize; 168 | decoder.ensure_size(app_data_size)?; 169 | decoder.buf_mut().advance(1); // reserved = buf[25] 170 | decoder.buf_mut().copy_to_bytes(app_data_size) 171 | }, 172 | }; 173 | Ok(v) 174 | } 175 | 176 | #[inline] 177 | fn decode_forward_close_success<'de, D>(mut decoder: D) -> Result 178 | where 179 | D: Decoder<'de>, 180 | { 181 | decoder.ensure_size(10)?; 182 | let v = ForwardCloseSuccess { 183 | connection_serial_number: decoder.decode_u16(), // buf[0..2] 184 | originator_vendor_id: decoder.decode_u16(), // buf[2..4] 185 | originator_serial_number: decoder.decode_u32(), // buf[4..8] 186 | app_data: { 187 | // buf[8], size in words 188 | let app_data_size = 2 * decoder.decode_u8() as usize; 189 | decoder.ensure_size(app_data_size)?; 190 | decoder.buf_mut().advance(1); // reserved = buf[9] 191 | decoder.buf_mut().copy_to_bytes(app_data_size) 192 | }, 193 | }; 194 | Ok(v) 195 | } 196 | 197 | #[inline] 198 | fn decode_forward_fail<'de, D>( 199 | mut decoder: D, 200 | status: Status, 201 | ) -> Result 202 | where 203 | D: Decoder<'de>, 204 | { 205 | let is_routing_error = status.is_routing_error(); 206 | let max_size = if is_routing_error { 9 } else { 8 }; 207 | decoder.ensure_size(max_size)?; 208 | let res = ForwardRequestFail { 209 | connection_serial_number: decoder.decode_u16(), 210 | originator_vendor_id: decoder.decode_u16(), 211 | originator_serial_number: decoder.decode_u32(), 212 | remaining_path_size: if is_routing_error { 213 | Some(decoder.decode_u8()) 214 | } else { 215 | None 216 | }, 217 | }; 218 | Ok(res) 219 | } 220 | 221 | #[inline] 222 | pub fn ensure_null_address<'de, D>(cpf: &mut CommonPacketIter<'de, D>) -> Result<(), D::Error> 223 | where 224 | D: Decoder<'de>, 225 | { 226 | if let Some(res) = cpf.next_item() { 227 | let item = res?; 228 | if item.type_code == 0 { 229 | return Ok(()); 230 | } 231 | } 232 | Err(cip_error("common packet - expect null address")) 233 | } 234 | 235 | #[inline] 236 | pub fn ensure_connected_address<'de, D>(cpf: &mut CommonPacketIter<'de, D>) -> Result<(), D::Error> 237 | where 238 | D: Decoder<'de>, 239 | { 240 | if let Some(res) = cpf.next_item() { 241 | let item = res?; 242 | if item.type_code == 0xA1 { 243 | return Ok(()); 244 | } 245 | } 246 | Err(cip_error("common packet - expect null address")) 247 | } 248 | -------------------------------------------------------------------------------- /cip/src/codec/encode.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod epath; 8 | mod message; 9 | 10 | use crate::{ 11 | connection::{ConnectionParameters, ForwardCloseRequest, OpenOptions}, 12 | service::request::UnconnectedSend, 13 | MessageRequest, 14 | }; 15 | use bytes::{BufMut, BytesMut}; 16 | use rseip_core::codec::{Encode, Encoder}; 17 | 18 | impl OpenOptions

{ 19 | #[inline] 20 | fn encode_common( 21 | &self, 22 | dst: &mut BytesMut, 23 | encoder: &mut A, 24 | ) -> Result<(), A::Error> { 25 | let transport_class_trigger = self.transport_class_trigger(); 26 | 27 | dst.put_u8(self.priority_tick_time); 28 | dst.put_u8(self.timeout_ticks); 29 | dst.put_u32_le(self.o_t_connection_id); 30 | dst.put_u32_le(self.t_o_connection_id); 31 | dst.put_u16_le(self.connection_serial_number); 32 | dst.put_u16_le(self.vendor_id); 33 | dst.put_u32_le(self.originator_serial_number); 34 | dst.put_u8(self.timeout_multiplier); 35 | dst.put_slice(&[0, 0, 0]); // reserved 36 | dst.put_u32_le(self.o_t_rpi); 37 | Self::encode_parameters(self.large_open, &self.o_t_params, dst, encoder)?; 38 | dst.put_u32_le(self.t_o_rpi); 39 | Self::encode_parameters(self.large_open, &self.t_o_params, dst, encoder)?; 40 | 41 | dst.put_u8(transport_class_trigger); 42 | 43 | let path_len = self.connection_path.bytes_count(); 44 | assert!(path_len % 2 == 0 && path_len <= u8::MAX as usize); 45 | dst.put_u8((path_len / 2) as u8); 46 | 47 | Ok(()) 48 | } 49 | 50 | #[inline] 51 | fn encode_parameters( 52 | large: bool, 53 | parameters: &ConnectionParameters, 54 | dst: &mut BytesMut, 55 | _encoder: &mut A, 56 | ) -> Result<(), A::Error> { 57 | if large { 58 | let mut v = parameters.connection_size as u32; 59 | v |= (parameters.variable_length as u32) << 25; 60 | v |= (parameters.priority as u32) << 26; 61 | v |= (parameters.connection_type as u32) << 29; 62 | v |= (parameters.redundant_owner as u32) << 31; 63 | dst.put_u32_le(v); 64 | } else { 65 | let mut v = parameters.connection_size & 0x01FF; 66 | v |= (parameters.variable_length as u16) << 9; 67 | v |= (parameters.priority as u16) << 10; 68 | v |= (parameters.connection_type as u16) << 13; 69 | v |= (parameters.redundant_owner as u16) << 15; 70 | dst.put_u16_le(v); 71 | } 72 | Ok(()) 73 | } 74 | } 75 | 76 | impl Encode for OpenOptions

{ 77 | #[inline] 78 | fn encode(self, dst: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> { 79 | self.encode_common(dst, encoder)?; 80 | self.connection_path.encode(dst, encoder)?; 81 | Ok(()) 82 | } 83 | 84 | #[inline] 85 | fn encode_by_ref( 86 | &self, 87 | dst: &mut BytesMut, 88 | encoder: &mut A, 89 | ) -> Result<(), A::Error> { 90 | self.encode_common(dst, encoder)?; 91 | self.connection_path.encode_by_ref(dst, encoder)?; 92 | Ok(()) 93 | } 94 | 95 | #[inline] 96 | fn bytes_count(&self) -> usize { 97 | let base_size = if self.large_open { 40 } else { 36 }; 98 | base_size + self.connection_path.bytes_count() 99 | } 100 | } 101 | 102 | impl ForwardCloseRequest

{ 103 | #[inline] 104 | fn encode_common( 105 | &self, 106 | dst: &mut BytesMut, 107 | _encoder: &mut A, 108 | ) -> Result<(), A::Error> { 109 | dst.put_u8(self.priority_time_ticks); 110 | dst.put_u8(self.timeout_ticks); 111 | dst.put_u16_le(self.connection_serial_number); 112 | dst.put_u16_le(self.originator_vendor_id); 113 | dst.put_u32_le(self.originator_serial_number); 114 | 115 | let path_len = self.connection_path.bytes_count(); 116 | assert!(path_len % 2 == 0 && path_len <= u8::MAX as usize); 117 | dst.put_u8(path_len as u8 / 2); //path size 118 | dst.put_u8(0); // reserved 119 | 120 | Ok(()) 121 | } 122 | } 123 | 124 | impl Encode for ForwardCloseRequest

{ 125 | #[inline] 126 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> { 127 | self.encode_common(buf, encoder)?; 128 | self.connection_path.encode(buf, encoder)?; 129 | Ok(()) 130 | } 131 | 132 | #[inline] 133 | fn encode_by_ref( 134 | &self, 135 | buf: &mut BytesMut, 136 | encoder: &mut A, 137 | ) -> Result<(), A::Error> { 138 | self.encode_common(buf, encoder)?; 139 | self.connection_path.encode_by_ref(buf, encoder)?; 140 | Ok(()) 141 | } 142 | 143 | #[inline] 144 | fn bytes_count(&self) -> usize { 145 | 12 + self.connection_path.bytes_count() 146 | } 147 | } 148 | 149 | impl Encode for UnconnectedSend> 150 | where 151 | CP: Encode, 152 | P: Encode, 153 | D: Encode, 154 | { 155 | #[inline] 156 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> 157 | where 158 | Self: Sized, 159 | { 160 | let data_len = self.data.bytes_count(); 161 | 162 | buf.put_u8(self.priority_ticks); 163 | buf.put_u8(self.timeout_ticks); 164 | 165 | buf.put_u16_le(data_len as u16); // size of MR 166 | self.data.encode(buf, encoder)?; 167 | if data_len % 2 == 1 { 168 | buf.put_u8(0); // padded 0 169 | } 170 | 171 | let path_len = self.path.bytes_count(); 172 | buf.put_u8(path_len as u8 / 2); // path size in words 173 | buf.put_u8(0); // reserved 174 | self.path.encode(buf, encoder)?; // padded epath 175 | Ok(()) 176 | } 177 | 178 | #[inline] 179 | fn encode_by_ref( 180 | &self, 181 | buf: &mut BytesMut, 182 | encoder: &mut A, 183 | ) -> Result<(), A::Error> { 184 | let data_len = self.data.bytes_count(); 185 | 186 | buf.put_u8(self.priority_ticks); 187 | buf.put_u8(self.timeout_ticks); 188 | 189 | buf.put_u16_le(data_len as u16); // size of MR 190 | self.data.encode_by_ref(buf, encoder)?; 191 | if data_len % 2 == 1 { 192 | buf.put_u8(0); // padded 0 193 | } 194 | 195 | let path_len = self.path.bytes_count(); 196 | buf.put_u8(path_len as u8 / 2); // path size in words 197 | buf.put_u8(0); // reserved 198 | self.path.encode_by_ref(buf, encoder)?; // padded epath 199 | Ok(()) 200 | } 201 | 202 | #[inline] 203 | fn bytes_count(&self) -> usize { 204 | let data_len = self.data.bytes_count(); 205 | 4 + data_len + data_len % 2 + 2 + self.path.bytes_count() 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /cip/src/codec/encode/epath.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::epath::*; 8 | use bytes::{BufMut, BytesMut}; 9 | use rseip_core::codec::{Encode, Encoder}; 10 | 11 | impl Encode for PortSegment { 12 | #[inline] 13 | fn encode_by_ref( 14 | &self, 15 | buf: &mut BytesMut, 16 | _encoder: &mut A, 17 | ) -> Result<(), A::Error> { 18 | const EXTENDED_LINKED_ADDRESS_SIZE: u16 = 1 << 4; // 0x10 19 | 20 | let link_addr_len = self.link.len(); 21 | debug_assert!(link_addr_len < u8::MAX as usize); 22 | 23 | let start_pos = buf.len(); 24 | let mut segment_byte = if self.port > 14 { 0x0F } else { self.port }; 25 | if link_addr_len > 1 { 26 | segment_byte |= EXTENDED_LINKED_ADDRESS_SIZE; 27 | } 28 | buf.put_u8(segment_byte as u8); 29 | if link_addr_len > 1 { 30 | buf.put_u8(link_addr_len as u8); 31 | } 32 | if self.port > 14 { 33 | buf.put_u16(self.port); 34 | } 35 | 36 | buf.put_slice(&self.link); 37 | let end_pos = buf.len(); 38 | if (end_pos - start_pos) % 2 != 0 { 39 | buf.put_u8(0); 40 | } 41 | Ok(()) 42 | } 43 | 44 | #[inline] 45 | fn bytes_count(&self) -> usize { 46 | let link_addr_len = self.link.len(); 47 | let mut count = 1; 48 | if link_addr_len > 1 { 49 | count += 1; 50 | } 51 | if self.port > 14 { 52 | count += 2; 53 | } 54 | count += link_addr_len; 55 | count + count % 2 56 | } 57 | } 58 | 59 | impl Segment { 60 | #[inline] 61 | fn encode_class( 62 | v: u16, 63 | buf: &mut BytesMut, 64 | _encoder: &mut A, 65 | ) -> Result<(), A::Error> { 66 | if v <= u8::MAX as u16 { 67 | buf.put_u8(0x20); 68 | buf.put_u8(v as u8); 69 | } else { 70 | buf.put_u8(0x21); 71 | buf.put_u8(0); 72 | buf.put_u16_le(v); 73 | } 74 | Ok(()) 75 | } 76 | 77 | #[inline] 78 | fn encode_instance( 79 | v: u16, 80 | buf: &mut BytesMut, 81 | _encoder: &mut A, 82 | ) -> Result<(), A::Error> { 83 | if v <= u8::MAX as u16 { 84 | buf.put_u8(0x24); 85 | buf.put_u8(v as u8); 86 | } else { 87 | buf.put_u8(0x25); 88 | buf.put_u8(0); 89 | buf.put_u16_le(v); 90 | } 91 | Ok(()) 92 | } 93 | 94 | #[inline] 95 | fn encode_attribute( 96 | v: u16, 97 | buf: &mut BytesMut, 98 | _encoder: &mut A, 99 | ) -> Result<(), A::Error> { 100 | if v <= u8::MAX as u16 { 101 | buf.put_u8(0x30); 102 | buf.put_u8(v as u8); 103 | } else { 104 | buf.put_u8(0x31); 105 | buf.put_u8(0); 106 | buf.put_u16_le(v); 107 | } 108 | Ok(()) 109 | } 110 | 111 | #[inline] 112 | fn encode_element( 113 | elem: u32, 114 | buf: &mut BytesMut, 115 | _encoder: &mut A, 116 | ) -> Result<(), A::Error> { 117 | match elem { 118 | v if v <= (u8::MAX as u32) => { 119 | buf.put_u8(0x28); 120 | buf.put_u8(v as u8); 121 | } 122 | v if v <= (u16::MAX as u32) => { 123 | buf.put_u8(0x29); 124 | buf.put_u8(0); 125 | buf.put_u16_le(v as u16); 126 | } 127 | v => { 128 | buf.put_u8(0x2A); 129 | buf.put_u8(0); 130 | buf.put_u32_le(v); 131 | } 132 | } 133 | Ok(()) 134 | } 135 | 136 | #[inline] 137 | fn encode_symbol( 138 | symbol: &[u8], 139 | buf: &mut BytesMut, 140 | _encoder: &mut A, 141 | ) -> Result<(), A::Error> { 142 | let char_count = symbol.len(); 143 | assert!(char_count <= u8::MAX as usize); 144 | buf.put_u8(0x91); 145 | buf.put_u8(char_count as u8); 146 | buf.put_slice(symbol); 147 | if char_count % 2 != 0 { 148 | buf.put_u8(0); 149 | } 150 | Ok(()) 151 | } 152 | } 153 | 154 | impl Encode for Segment { 155 | #[inline] 156 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> { 157 | match self { 158 | Segment::Class(v) => Self::encode_class(v, buf, encoder), 159 | Segment::Instance(v) => Self::encode_instance(v, buf, encoder), 160 | Segment::Attribute(v) => Self::encode_attribute(v, buf, encoder), 161 | Segment::Element(v) => Self::encode_element(v, buf, encoder), 162 | Segment::Port(port) => port.encode_by_ref(buf, encoder), 163 | Segment::Symbol(symbol) => Self::encode_symbol(symbol.as_bytes(), buf, encoder), 164 | } 165 | } 166 | 167 | #[inline] 168 | fn encode_by_ref( 169 | &self, 170 | buf: &mut BytesMut, 171 | encoder: &mut A, 172 | ) -> Result<(), A::Error> { 173 | match self { 174 | Segment::Class(v) => Self::encode_class(*v, buf, encoder), 175 | Segment::Instance(v) => Self::encode_instance(*v, buf, encoder), 176 | Segment::Attribute(v) => Self::encode_attribute(*v, buf, encoder), 177 | Segment::Element(v) => Self::encode_element(*v, buf, encoder), 178 | Segment::Port(port) => port.encode_by_ref(buf, encoder), 179 | Segment::Symbol(symbol) => Self::encode_symbol(symbol.as_bytes(), buf, encoder), 180 | } 181 | } 182 | 183 | #[inline] 184 | fn bytes_count(&self) -> usize { 185 | match self { 186 | Segment::Class(v) | Segment::Instance(v) | Segment::Attribute(v) => { 187 | if *v <= u8::MAX as u16 { 188 | 2 189 | } else { 190 | 4 191 | } 192 | } 193 | Segment::Element(elem) => match elem { 194 | v if *v <= (u8::MAX as u32) => 2, 195 | v if *v <= (u16::MAX as u32) => 4, 196 | _ => 6, 197 | }, 198 | Segment::Port(port) => port.bytes_count(), 199 | Segment::Symbol(symbol) => { 200 | let char_count = symbol.as_bytes().len(); 201 | 2 + char_count + char_count % 2 202 | } 203 | } 204 | } 205 | } 206 | 207 | impl Encode for EPath { 208 | #[inline] 209 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> { 210 | for item in self { 211 | item.encode(buf, encoder)?; 212 | } 213 | Ok(()) 214 | } 215 | #[inline] 216 | fn encode_by_ref( 217 | &self, 218 | buf: &mut BytesMut, 219 | encoder: &mut A, 220 | ) -> Result<(), A::Error> { 221 | for item in self.iter() { 222 | item.encode_by_ref(buf, encoder)?; 223 | } 224 | Ok(()) 225 | } 226 | 227 | #[inline] 228 | fn bytes_count(&self) -> usize { 229 | self.iter().map(|v| v.bytes_count()).sum() 230 | } 231 | } 232 | 233 | impl From for EPath { 234 | fn from(port: PortSegment) -> Self { 235 | Self::from(vec![Segment::Port(port)]) 236 | } 237 | } 238 | 239 | #[cfg(test)] 240 | mod test { 241 | use super::*; 242 | use crate::epath::EPATH_CONNECTION_MANAGER; 243 | use rseip_core::tests::EncodeExt; 244 | 245 | #[test] 246 | fn test_epath_symbol() { 247 | let epath = EPath::from_symbol("TotalCount"); 248 | 249 | assert_eq!(epath.bytes_count(), 12); 250 | 251 | let buf = epath.try_into_bytes().unwrap(); 252 | 253 | assert_eq!( 254 | &buf[..], 255 | &[0x91, 0x0A, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x43, 0x6F, 0x75, 0x6E, 0x74] 256 | ); 257 | } 258 | 259 | #[test] 260 | fn test_epath_symbol_odd() { 261 | let epath = EPath::from_symbol("TotalCountt"); 262 | assert_eq!(epath.bytes_count(), 14); 263 | 264 | let buf = epath.try_into_bytes().unwrap(); 265 | 266 | assert_eq!( 267 | &buf[..], 268 | &[0x91, 0x0B, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x43, 0x6F, 0x75, 0x6E, 0x74, 0x74, 0x00] 269 | ); 270 | } 271 | 272 | #[test] 273 | fn test_epath_unconnected_send() { 274 | let epath = EPath::from(vec![Segment::Class(0x06), Segment::Instance(0x1)]); 275 | 276 | assert_eq!(epath.bytes_count(), 4); 277 | 278 | let buf = epath.try_into_bytes().unwrap(); 279 | 280 | assert_eq!(&buf[..], EPATH_CONNECTION_MANAGER); 281 | } 282 | 283 | #[test] 284 | fn test_epath_router_path() { 285 | let epath = EPath::from(vec![Segment::Port(PortSegment::default())]); 286 | 287 | assert_eq!(epath.bytes_count(), 2); 288 | 289 | let buf = epath.try_into_bytes().unwrap(); 290 | 291 | assert_eq!(&buf[..], &[01, 00]); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /cip/src/codec/encode/message.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::MessageRequest; 8 | use bytes::BufMut; 9 | use rseip_core::codec::{Encode, Encoder}; 10 | 11 | impl Encode for MessageRequest { 12 | #[inline] 13 | fn encode(self, buf: &mut bytes::BytesMut, encoder: &mut A) -> Result<(), A::Error> 14 | where 15 | Self: Sized, 16 | { 17 | // service code 18 | buf.put_u8(self.service_code); 19 | 20 | let path_len = self.path.bytes_count(); 21 | debug_assert!(path_len <= u8::MAX as usize && path_len % 2 == 0); 22 | buf.put_u8((path_len / 2) as u8); 23 | 24 | self.path.encode(buf, encoder)?; 25 | self.data.encode(buf, encoder)?; 26 | Ok(()) 27 | } 28 | 29 | #[inline] 30 | fn encode_by_ref( 31 | &self, 32 | buf: &mut bytes::BytesMut, 33 | encoder: &mut A, 34 | ) -> Result<(), A::Error> { 35 | // service code 36 | buf.put_u8(self.service_code); 37 | 38 | let path_len = self.path.bytes_count(); 39 | debug_assert!(path_len <= u8::MAX as usize && path_len % 2 == 0); 40 | buf.put_u8((path_len / 2) as u8); 41 | 42 | self.path.encode_by_ref(buf, encoder)?; 43 | self.data.encode_by_ref(buf, encoder)?; 44 | Ok(()) 45 | } 46 | 47 | #[inline] 48 | fn bytes_count(&self) -> usize { 49 | 2 + self.path.bytes_count() + self.data.bytes_count() 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod test { 55 | use super::*; 56 | use crate::{epath::EPath, MessageRequest}; 57 | use bytes::Bytes; 58 | use rseip_core::tests::EncodeExt; 59 | 60 | #[test] 61 | fn test_encode_message_router_request() { 62 | let mr = MessageRequest::new( 63 | 0x52, 64 | EPath::default().with_class(0x06).with_instance(0x01), 65 | Bytes::from_static(&[0x10, 0x00]), 66 | ); 67 | assert_eq!(mr.bytes_count(), 8); 68 | let buf = mr.try_into_bytes().unwrap(); 69 | assert_eq!( 70 | &buf[..], 71 | &[ 72 | 0x52, // service code 73 | 0x02, 0x20, 0x06, 0x24, 0x01, // epath 74 | 0x010, 0x00 // data 75 | ] 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cip/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | pub mod decode; 8 | mod encode; 9 | -------------------------------------------------------------------------------- /cip/src/epath.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use bytes::{BufMut, Bytes, BytesMut}; 8 | use core::ops::{Deref, DerefMut}; 9 | use rseip_core::String; 10 | use smallvec::{smallvec, SmallVec}; 11 | 12 | /// EPATH for unconnected send 13 | pub const EPATH_CONNECTION_MANAGER: &[u8] = &[0x20, 0x06, 0x24, 0x01]; 14 | 15 | /// Segment of EPATH 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub enum Segment { 18 | /// symbolic, ANSI Ext. String 19 | Symbol(String), 20 | /// class id 21 | Class(u16), 22 | /// instance id 23 | Instance(u16), 24 | /// attribute id 25 | Attribute(u16), 26 | /// element index 27 | Element(u32), 28 | /// port segment 29 | Port(PortSegment), 30 | } 31 | 32 | impl Segment { 33 | /// is port segment? 34 | #[inline] 35 | pub fn is_port(&self) -> bool { 36 | match self { 37 | Self::Port(_) => true, 38 | _ => false, 39 | } 40 | } 41 | } 42 | 43 | type Array = [Segment; 4]; 44 | 45 | /// EPATH 46 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 47 | pub struct EPath(SmallVec); 48 | 49 | impl EPath { 50 | /// new object 51 | #[inline] 52 | pub fn new() -> Self { 53 | Self::default() 54 | } 55 | 56 | /// into inner 57 | pub fn into_inner(self) -> SmallVec { 58 | self.0 59 | } 60 | 61 | /// append class id 62 | #[inline] 63 | pub fn with_class(mut self, class_id: u16) -> Self { 64 | self.0.push(Segment::Class(class_id)); 65 | self 66 | } 67 | 68 | /// append symbolic 69 | #[inline] 70 | pub fn with_symbol(mut self, symbol: impl Into) -> Self { 71 | self.0.push(Segment::Symbol(symbol.into())); 72 | self 73 | } 74 | 75 | /// append instance id 76 | #[inline] 77 | pub fn with_instance(mut self, instance_id: u16) -> Self { 78 | self.0.push(Segment::Instance(instance_id)); 79 | self 80 | } 81 | 82 | /// append attribute id 83 | #[inline] 84 | pub fn with_attribute(mut self, attribute_id: u16) -> Self { 85 | self.0.push(Segment::Attribute(attribute_id)); 86 | self 87 | } 88 | 89 | /// append element id 90 | #[inline] 91 | pub fn with_element(mut self, element_idx: u32) -> Self { 92 | self.0.push(Segment::Element(element_idx)); 93 | self 94 | } 95 | 96 | /// append port & default slot 0 97 | #[inline] 98 | pub fn with_port(mut self, port: u16) -> Self { 99 | self.0.push(Segment::Port(PortSegment { 100 | port, 101 | link: Bytes::from_static(&[0]), 102 | })); 103 | self 104 | } 105 | 106 | /// append port & slot 107 | #[inline] 108 | pub fn with_port_slot(mut self, port: u16, slot: u8) -> Self { 109 | let mut buf = BytesMut::new(); 110 | buf.put_u8(slot); 111 | self.0.push(Segment::Port(PortSegment { 112 | port, 113 | link: buf.freeze(), 114 | })); 115 | self 116 | } 117 | 118 | /// from symbolic segment, ANSI Ext. String 119 | /// 120 | /// ## Note 121 | /// No validation applied 122 | #[inline] 123 | pub fn from_symbol(symbol: impl Into) -> Self { 124 | EPath(smallvec![Segment::Symbol(symbol.into())]) 125 | } 126 | 127 | #[inline] 128 | pub fn insert(&mut self, index: usize, item: Segment) { 129 | self.0.insert(index, item) 130 | } 131 | 132 | #[inline] 133 | pub fn push(&mut self, item: Segment) { 134 | self.0.push(item); 135 | } 136 | 137 | #[inline] 138 | pub fn remove(&mut self, index: usize) { 139 | self.0.remove(index); 140 | } 141 | } 142 | 143 | impl IntoIterator for EPath { 144 | type Item = Segment; 145 | type IntoIter = rseip_core::iter::IntoIter; 146 | fn into_iter(self) -> Self::IntoIter { 147 | rseip_core::iter::IntoIter::new(self.0) 148 | } 149 | } 150 | 151 | impl Deref for EPath { 152 | type Target = [Segment]; 153 | #[inline] 154 | fn deref(&self) -> &Self::Target { 155 | &self.0 156 | } 157 | } 158 | 159 | impl DerefMut for EPath { 160 | #[inline] 161 | fn deref_mut(&mut self) -> &mut Self::Target { 162 | &mut self.0 163 | } 164 | } 165 | 166 | impl From> for EPath { 167 | fn from(src: Vec) -> Self { 168 | Self(SmallVec::from_vec(src)) 169 | } 170 | } 171 | 172 | /// EPATH Port Segment 173 | #[derive(Debug, PartialEq, Eq, Clone)] 174 | pub struct PortSegment { 175 | /// Port to leave Current Node (1 if Backplane) 176 | pub port: u16, 177 | /// link address to route packet (number or IP address) 178 | pub link: Bytes, 179 | } 180 | 181 | impl Default for PortSegment { 182 | fn default() -> Self { 183 | Self { 184 | port: 1, // Backplane 185 | link: Bytes::from_static(&[0]), // slot 0 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /cip/src/error.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::*; 8 | use core::fmt; 9 | use rseip_core::Error; 10 | 11 | pub fn cip_error(msg: U) -> E { 12 | E::custom(format_args!("cip error: {}", msg)) 13 | } 14 | 15 | pub fn cip_error_status(status: Status) -> E { 16 | E::custom(format_args!("cip error: message reply status {}", status)) 17 | } 18 | 19 | pub fn cip_error_reply(reply_service: u8, expected_service: u8) -> E { 20 | E::custom(format_args!( 21 | "cip error: unexpected message reply service {}, expect {}", 22 | reply_service, expected_service 23 | )) 24 | } 25 | -------------------------------------------------------------------------------- /cip/src/identity.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{socket::SocketAddr, Revision}; 8 | use std::borrow::Cow; 9 | 10 | /// Identity Object 11 | #[derive(Debug, Clone, PartialEq, Eq)] 12 | pub struct IdentityObject<'a> { 13 | /// encapsulation protocol version supported 14 | pub protocol_version: u16, 15 | /// socket addr 16 | pub socket_addr: SocketAddr, 17 | /// device manufacturers vendor id 18 | pub vendor_id: u16, 19 | /// device type of product 20 | pub device_type: u16, 21 | /// product code 22 | pub product_code: u16, 23 | /// device revision 24 | pub revision: Revision, 25 | /// current status of device 26 | pub status: u16, 27 | /// serial number of device 28 | pub serial_number: u32, 29 | //pub product_name_len: u8, 30 | /// short string 31 | pub product_name: Cow<'a, str>, 32 | /// current state of device 33 | pub state: u8, 34 | } 35 | -------------------------------------------------------------------------------- /cip/src/lib.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /*! 8 | 9 | # rseip-cip 10 | 11 | Common industry protocol for `rseip`, please look at [rseip project](https://github.com/Joylei/eip-rs) for more information. 12 | 13 | ## License 14 | 15 | MIT 16 | 17 | */ 18 | 19 | //#![warn(missing_docs)] 20 | #![allow(clippy::match_like_matches_macro)] 21 | 22 | pub mod codec; 23 | pub mod connection; 24 | pub mod epath; 25 | pub mod error; 26 | pub mod identity; 27 | mod list_service; 28 | pub mod message; 29 | mod revision; 30 | pub mod service; 31 | pub mod socket; 32 | mod status; 33 | 34 | pub use epath::EPath; 35 | pub use list_service::ListServiceItem; 36 | pub use message::*; 37 | pub use revision::Revision; 38 | pub use rseip_core::cip::{CommonPacket, CommonPacketItem}; 39 | pub use status::Status; 40 | 41 | pub const REPLY_MASK: u8 = 0x80; 42 | -------------------------------------------------------------------------------- /cip/src/list_service.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | // type code = 0x100 8 | // encoded bytes count: 24 9 | 10 | use std::borrow::Cow; 11 | 12 | /// only one service for ListServices 13 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 14 | pub struct ListServiceItem<'a> { 15 | /// version shall be 1 16 | pub protocol_version: u16, 17 | pub capability: u16, 18 | /// name of service, NULL-terminated ASCII string; 19 | /// it's Communications for CIP 20 | pub name: Cow<'a, str>, 21 | } 22 | 23 | impl ListServiceItem<'_> { 24 | /// supports CIP Encapsulation via TCP 25 | #[inline(always)] 26 | pub fn capability_tcp(&self) -> bool { 27 | self.capability & 0b100000 > 0 28 | } 29 | 30 | /// support CIP Class 0 or 1 via UDP 31 | #[inline(always)] 32 | pub fn capability_udp(&self) -> bool { 33 | self.capability & 0b100000000 > 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cip/src/message.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::Status; 8 | use crate::error::cip_error_reply; 9 | use rseip_core::{codec::Encode, Error}; 10 | 11 | /// Message request 12 | #[derive(Debug, Default, PartialEq, Eq)] 13 | pub struct MessageRequest { 14 | /// service request code 15 | pub service_code: u8, 16 | /// service request path 17 | pub path: P, 18 | /// service request data 19 | pub data: D, 20 | } 21 | 22 | impl MessageRequest 23 | where 24 | P: Encode, 25 | D: Encode, 26 | { 27 | #[inline] 28 | pub fn new(service_code: u8, path: P, data: D) -> Self { 29 | Self { 30 | service_code, 31 | path, 32 | data, 33 | } 34 | } 35 | } 36 | 37 | /// message reply 38 | #[derive(Debug)] 39 | pub struct MessageReply { 40 | /// reply service code 41 | pub reply_service: u8, 42 | /// general status and extended status 43 | pub status: Status, 44 | /// only present with routing type errors 45 | pub remaining_path_size: Option, 46 | pub data: D, 47 | } 48 | 49 | impl MessageReply { 50 | #[inline] 51 | pub fn new(reply_service: u8, status: Status, data: D) -> Self { 52 | Self { 53 | reply_service, 54 | status, 55 | remaining_path_size: None, 56 | data, 57 | } 58 | } 59 | } 60 | 61 | impl MessageReplyInterface for MessageReply { 62 | type Value = D; 63 | 64 | #[inline] 65 | fn reply_service(&self) -> u8 { 66 | self.reply_service 67 | } 68 | 69 | #[inline] 70 | fn status(&self) -> &Status { 71 | &self.status 72 | } 73 | 74 | #[inline] 75 | fn value(&self) -> &Self::Value { 76 | &self.data 77 | } 78 | 79 | #[inline] 80 | fn into_value(self) -> Self::Value { 81 | self.data 82 | } 83 | } 84 | 85 | /// CIP message reply abstraction 86 | pub trait MessageReplyInterface { 87 | type Value; 88 | 89 | fn reply_service(&self) -> u8; 90 | 91 | fn status(&self) -> &Status; 92 | 93 | fn value(&self) -> &Self::Value; 94 | 95 | fn into_value(self) -> Self::Value; 96 | 97 | #[inline] 98 | fn expect_service(&self, expected_service: u8) -> Result<(), E> { 99 | if self.reply_service() != expected_service { 100 | Err(cip_error_reply(self.reply_service(), expected_service)) 101 | } else { 102 | Ok(()) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cip/src/revision.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq)] 8 | pub struct Revision { 9 | pub major: u8, 10 | pub minor: u8, 11 | } 12 | -------------------------------------------------------------------------------- /cip/src/service/common_services.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod multiple_packet; 8 | 9 | use super::*; 10 | use crate::epath::EPath; 11 | pub use multiple_packet::MultipleServicePacket; 12 | use rseip_core::codec::{Decode, Encode, SliceContainer}; 13 | 14 | /// common services 15 | #[async_trait::async_trait] 16 | pub trait CommonServices: MessageService { 17 | /// invoke the Get_Attribute_All service 18 | #[inline] 19 | async fn get_attribute_all<'de, R>(&mut self, path: EPath) -> Result 20 | where 21 | R: Decode<'de> + 'static, 22 | { 23 | send_and_extract(self, 0x01, path, ()).await 24 | } 25 | 26 | /// invoke the Set_Attribute_All service 27 | #[inline] 28 | async fn set_attribute_all( 29 | &mut self, 30 | path: EPath, 31 | attrs: D, 32 | ) -> Result<(), Self::Error> { 33 | send_and_extract(self, 0x02, path, attrs).await 34 | } 35 | 36 | /// invoke the Get_Attribute_List 37 | #[inline] 38 | async fn get_attribute_list<'de, R>( 39 | &mut self, 40 | path: EPath, 41 | attrs: &[u16], 42 | ) -> Result 43 | where 44 | R: Decode<'de> + 'static, 45 | { 46 | let attrs_len = attrs.len(); 47 | debug_assert!(attrs_len <= u16::MAX as usize); 48 | send_and_extract( 49 | self, 50 | 0x03, 51 | path, 52 | ( 53 | attrs_len as u16, 54 | SliceContainer::new(attrs).with_bytes_count(2 * attrs_len), 55 | ), 56 | ) 57 | .await 58 | } 59 | 60 | /// invoke the Set_Attribute_List service 61 | #[inline] 62 | async fn set_attribute_list<'de, D, R>( 63 | &mut self, 64 | path: EPath, 65 | attrs: D, 66 | ) -> Result 67 | where 68 | D: Encode + Send + Sync, 69 | R: Decode<'de> + 'static, 70 | { 71 | send_and_extract(self, 0x04, path, attrs).await 72 | } 73 | 74 | /// invoke the Reset service 75 | #[inline] 76 | async fn reset(&mut self, path: EPath) -> Result<(), Self::Error> { 77 | send_and_extract(self, 0x05, path, ()).await 78 | } 79 | 80 | /// invoke the Start service 81 | #[inline] 82 | async fn start(&mut self, path: EPath) -> Result<(), Self::Error> { 83 | send_and_extract(self, 0x06, path, ()).await 84 | } 85 | 86 | /// invoke the Stop service 87 | #[inline] 88 | async fn stop(&mut self, path: EPath) -> Result<(), Self::Error> { 89 | send_and_extract(self, 0x07, path, ()).await 90 | } 91 | 92 | /// invoke the Create service 93 | #[inline] 94 | async fn create<'de, D, R>(&mut self, path: EPath, data: D) -> Result 95 | where 96 | D: Encode + Send + Sync, 97 | R: Decode<'de> + 'static, 98 | { 99 | send_and_extract(self, 0x08, path, data).await 100 | } 101 | 102 | /// invoke the Delete service 103 | #[inline] 104 | async fn delete(&mut self, path: EPath) -> Result<(), Self::Error> { 105 | send_and_extract(self, 0x09, path, ()).await 106 | } 107 | 108 | /// invoke the Apply_Attributes service 109 | #[inline] 110 | async fn apply_attributes<'de, D, R>(&mut self, path: EPath, data: D) -> Result 111 | where 112 | D: Encode + Send + Sync, 113 | R: Decode<'de> + 'static, 114 | { 115 | send_and_extract(self, 0x0D, path, data).await 116 | } 117 | 118 | /// invoke the Get_Attribute_Single service 119 | #[inline] 120 | async fn get_attribute_single<'de, R>(&mut self, path: EPath) -> Result 121 | where 122 | R: Decode<'de> + 'static, 123 | { 124 | send_and_extract(self, 0x0E, path, ()).await 125 | } 126 | 127 | /// invoke the Set_Attribute_Single service 128 | #[inline] 129 | async fn set_attribute_single( 130 | &mut self, 131 | path: EPath, 132 | data: D, 133 | ) -> Result<(), Self::Error> { 134 | send_and_extract(self, 0x10, path, data).await 135 | } 136 | 137 | /// invoke the Restore service 138 | #[inline] 139 | async fn restore(&mut self, path: EPath) -> Result<(), Self::Error> { 140 | send_and_extract(self, 0x15, path, ()).await 141 | } 142 | 143 | /// invoke the Save service 144 | #[inline] 145 | async fn save(&mut self, path: EPath) -> Result<(), Self::Error> { 146 | send_and_extract(self, 0x16, path, ()).await 147 | } 148 | 149 | /// invoke the Nop service 150 | #[inline] 151 | async fn no_operation(&mut self, path: EPath) -> Result<(), Self::Error> { 152 | send_and_extract(self, 0x17, path, ()).await 153 | } 154 | 155 | /// invoke the Get_Member service 156 | #[inline] 157 | async fn get_member<'de, R: Decode<'de> + 'static>( 158 | &mut self, 159 | path: EPath, 160 | ) -> Result { 161 | send_and_extract(self, 0x18, path, ()).await 162 | } 163 | 164 | /// invoke the Set_Member service 165 | #[inline] 166 | async fn set_member<'de, D, R>(&mut self, path: EPath, data: D) -> Result 167 | where 168 | D: Encode + Send + Sync, 169 | R: Decode<'de> + 'static, 170 | { 171 | send_and_extract(self, 0x19, path, data).await 172 | } 173 | 174 | /// invoke the Insert_Member service 175 | #[inline] 176 | async fn insert_member<'de, D, R>(&mut self, path: EPath, data: D) -> Result 177 | where 178 | D: Encode + Send + Sync, 179 | R: Decode<'de> + 'static, 180 | { 181 | send_and_extract(self, 0x1A, path, data).await 182 | } 183 | 184 | /// invoke the Remove_Member service 185 | #[inline] 186 | async fn remove_member(&mut self, path: EPath) -> Result<(), Self::Error> { 187 | send_and_extract(self, 0x1B, path, ()).await 188 | } 189 | 190 | /// invoke the Group_Sync service 191 | #[inline] 192 | async fn group_sync(&mut self, path: EPath) -> Result<(), Self::Error> { 193 | send_and_extract(self, 0x1C, path, ()).await 194 | } 195 | 196 | /// multiple service packet 197 | #[inline] 198 | fn multiple_service(&mut self) -> MultipleServicePacket<'_, Self, P, D> 199 | where 200 | Self: Sized, 201 | P: Encode, 202 | D: Encode, 203 | { 204 | MultipleServicePacket::new(self) 205 | } 206 | } 207 | 208 | #[async_trait::async_trait] 209 | impl CommonServices for T {} 210 | -------------------------------------------------------------------------------- /cip/src/service/common_services/multiple_packet.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::codec::decode::message_reply::decode_service_and_status; 8 | use crate::service::*; 9 | use crate::*; 10 | use crate::{epath::EPath, error::cip_error}; 11 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 12 | use rseip_core::codec::{BytesHolder, Decode, Decoder, Encode, Encoder, LittleEndianDecoder}; 13 | use smallvec::SmallVec; 14 | 15 | /// build and send multiple service packet 16 | pub struct MultipleServicePacket<'a, T, P, D> { 17 | inner: &'a mut T, 18 | items: SmallVec<[MessageRequest; 4]>, 19 | } 20 | 21 | impl<'a, T, P, D> MultipleServicePacket<'a, T, P, D> { 22 | pub(crate) fn new(inner: &'a mut T) -> Self { 23 | Self { 24 | inner, 25 | items: Default::default(), 26 | } 27 | } 28 | } 29 | 30 | impl<'a, T, P, D> MultipleServicePacket<'a, T, P, D> 31 | where 32 | T: MessageService, 33 | P: Encode + Send + Sync, 34 | D: Encode + Send + Sync, 35 | { 36 | /// append service request 37 | pub fn push(mut self, mr: MessageRequest) -> Self { 38 | self.items.push(mr); 39 | self 40 | } 41 | 42 | /// append all service requests 43 | pub fn push_all(mut self, items: impl Iterator>) -> Self { 44 | for mr in items { 45 | self.items.push(mr); 46 | } 47 | self 48 | } 49 | 50 | /// build and send requests 51 | #[inline] 52 | pub async fn call(self) -> Result>, T::Error> { 53 | let Self { inner, items } = self; 54 | if items.is_empty() { 55 | return Ok(ReplyIter::new(None)); 56 | } 57 | 58 | const SERVICE_CODE: u8 = 0x0A; 59 | let mr = MessageRequest::new( 60 | SERVICE_CODE, 61 | EPath::default().with_class(2).with_instance(1), 62 | MultipleServicesEncoder { items }, 63 | ); 64 | let reply: IgnoreStatusInterceptor = inner.send(mr).await?; 65 | reply.expect_service::(SERVICE_CODE + REPLY_MASK)?; 66 | 67 | let res = ReplyIter::new(Some(LittleEndianDecoder::new(reply.into_value().into()))); 68 | Ok(res) 69 | } 70 | } 71 | 72 | pub struct ReplyIter { 73 | buf: Option, 74 | offsets: Bytes, 75 | count: Option, 76 | last: Option, 77 | i: u16, 78 | } 79 | 80 | impl ReplyIter { 81 | fn new(decoder: Option) -> Self { 82 | Self { 83 | buf: decoder, 84 | offsets: Bytes::new(), 85 | count: None, 86 | last: None, 87 | i: 0, 88 | } 89 | } 90 | } 91 | 92 | impl<'de, D> ReplyIter 93 | where 94 | D: Decoder<'de>, 95 | { 96 | fn raise_err(&mut self) -> Option> { 97 | self.buf.take(); 98 | Some(Err(cip_error("failed to decode message reply"))) 99 | } 100 | 101 | /// decode next message reply from the multiple service reply 102 | pub fn next(&mut self) -> Option, D::Error>> 103 | where 104 | Item: Decode<'de>, 105 | { 106 | let buf = self.buf.as_mut()?; 107 | let count = if let Some(count) = self.count { 108 | count 109 | } else { 110 | if let Err(e) = buf.ensure_size(2) { 111 | return Some(Err(e)); 112 | } 113 | let count = buf.decode_u16(); 114 | self.count = Some(count); 115 | if count == 0 { 116 | return None; 117 | } 118 | let data_offsets = 2 * (count) as usize; 119 | if let Err(e) = buf.ensure_size(data_offsets) { 120 | return Some(Err(e)); 121 | } 122 | self.offsets = buf.buf_mut().copy_to_bytes(data_offsets); 123 | count 124 | }; 125 | if count == 0 { 126 | return None; 127 | } 128 | 129 | while self.i < count { 130 | self.i += 1; 131 | let offset = self.offsets.get_u16_le(); 132 | if let Some(last) = self.last.replace(offset) { 133 | if offset <= last { 134 | return self.raise_err(); 135 | } 136 | let size = (offset - last) as usize; 137 | if buf.remaining() < size { 138 | return self.raise_err(); 139 | } 140 | let res: Result, _> = buf.decode_any(); 141 | return Some(res); 142 | } 143 | } 144 | // process remaining 145 | if buf.remaining() > 0 { 146 | let res: Result, _> = buf.decode_any(); 147 | self.buf.take(); 148 | return Some(res); 149 | } 150 | self.buf.take(); 151 | None 152 | } 153 | } 154 | 155 | /// array encoder, expected array size in u16 156 | struct MultipleServicesEncoder 157 | where 158 | Array: smallvec::Array, 159 | { 160 | items: SmallVec, 161 | } 162 | 163 | impl MultipleServicesEncoder 164 | where 165 | Array: smallvec::Array, 166 | Array::Item: Encode, 167 | { 168 | #[inline] 169 | fn encode_common( 170 | &self, 171 | buf: &mut BytesMut, 172 | _encoder: &mut A, 173 | ) -> Result<(), A::Error> 174 | where 175 | Self: Sized, 176 | { 177 | let start_offset = 2 + 2 * self.items.len(); 178 | buf.put_u16_le(self.items.len() as u16); 179 | let mut offset = start_offset; 180 | for item in self.items.iter() { 181 | buf.put_u16_le(offset as u16); 182 | offset += item.bytes_count(); 183 | } 184 | Ok(()) 185 | } 186 | } 187 | 188 | impl Encode for MultipleServicesEncoder 189 | where 190 | Array: smallvec::Array, 191 | Array::Item: Encode, 192 | { 193 | #[inline] 194 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> 195 | where 196 | Self: Sized, 197 | { 198 | self.encode_common(buf, encoder)?; 199 | for item in self.items { 200 | item.encode(buf, encoder)?; 201 | } 202 | Ok(()) 203 | } 204 | 205 | #[inline] 206 | fn encode_by_ref( 207 | &self, 208 | buf: &mut BytesMut, 209 | encoder: &mut A, 210 | ) -> Result<(), A::Error> { 211 | self.encode_common(buf, encoder)?; 212 | for item in self.items.iter() { 213 | item.encode_by_ref(buf, encoder)?; 214 | } 215 | Ok(()) 216 | } 217 | 218 | #[inline] 219 | fn bytes_count(&self) -> usize { 220 | let start_offset = 2 + 2 * self.items.len(); 221 | let bytes_count = self.items.iter().map(|v| v.bytes_count()).sum::(); 222 | start_offset + bytes_count 223 | } 224 | } 225 | 226 | #[derive(Debug)] 227 | struct IgnoreStatusInterceptor(pub MessageReply); 228 | 229 | impl MessageReplyInterface for IgnoreStatusInterceptor { 230 | type Value = T; 231 | 232 | fn reply_service(&self) -> u8 { 233 | self.0.reply_service 234 | } 235 | 236 | fn status(&self) -> &Status { 237 | &self.0.status 238 | } 239 | 240 | fn value(&self) -> &Self::Value { 241 | &self.0.data 242 | } 243 | 244 | fn into_value(self) -> Self::Value { 245 | self.0.data 246 | } 247 | } 248 | 249 | impl<'de, T> Decode<'de> for IgnoreStatusInterceptor 250 | where 251 | T: Decode<'de>, 252 | { 253 | #[inline] 254 | fn decode(mut decoder: D) -> Result 255 | where 256 | D: Decoder<'de>, 257 | { 258 | let (reply_service, status) = decode_service_and_status(&mut decoder)?; 259 | let data = decoder.decode_any()?; 260 | Ok(Self(MessageReply::new(reply_service, status, data))) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /cip/src/service/heartbeat.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use rseip_core::Error; 8 | 9 | #[async_trait::async_trait] 10 | pub trait Heartbeat: Send + Sync { 11 | type Error: Error; 12 | /// send Heartbeat message to keep underline transport alive 13 | async fn heartbeat(&mut self) -> Result<(), Self::Error>; 14 | } 15 | -------------------------------------------------------------------------------- /cip/src/service/message_service.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::{MessageReplyInterface, MessageRequest}; 8 | use rseip_core::{ 9 | codec::{Decode, Encode}, 10 | Error, 11 | }; 12 | 13 | #[async_trait::async_trait] 14 | pub trait MessageService: Send + Sync { 15 | type Error: Error; 16 | /// send message request 17 | async fn send<'de, P, D, R>(&mut self, mr: MessageRequest) -> Result 18 | where 19 | P: Encode + Send + Sync, 20 | D: Encode + Send + Sync, 21 | R: MessageReplyInterface + Decode<'de> + 'static; 22 | 23 | /// close underline transport 24 | async fn close(&mut self) -> Result<(), Self::Error>; 25 | 26 | /// is underline transport closed? 27 | fn closed(&self) -> bool; 28 | } 29 | 30 | #[async_trait::async_trait] 31 | impl MessageService for &mut T { 32 | type Error = T::Error; 33 | #[inline] 34 | async fn send<'de, P, D, R>(&mut self, mr: MessageRequest) -> Result 35 | where 36 | P: Encode + Send + Sync, 37 | D: Encode + Send + Sync, 38 | R: MessageReplyInterface + Decode<'de> + 'static, 39 | { 40 | (**self).send(mr).await 41 | } 42 | 43 | #[inline] 44 | async fn close(&mut self) -> Result<(), Self::Error> { 45 | (**self).close().await 46 | } 47 | 48 | #[inline] 49 | fn closed(&self) -> bool { 50 | (**self).closed() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cip/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | //! CIP services 8 | 9 | mod common_services; 10 | mod heartbeat; 11 | mod message_service; 12 | pub mod request; 13 | 14 | use crate::*; 15 | #[doc(inline)] 16 | pub use common_services::CommonServices; 17 | #[doc(inline)] 18 | pub use heartbeat::Heartbeat; 19 | #[doc(inline)] 20 | pub use message_service::MessageService; 21 | use rseip_core::codec::{Decode, Encode}; 22 | 23 | pub const SERVICE_UNCONNECTED_SEND: u8 = 0x52; 24 | pub const SERVICE_FORWARD_OPEN: u8 = 0x54; 25 | pub const SERVICE_LARGE_FORWARD_OPEN: u8 = 0x5B; 26 | pub const SERVICE_FORWARD_CLOSE: u8 = 0x4E; 27 | 28 | /// send message request and extract the data from message reply 29 | #[doc(hidden)] 30 | #[inline] 31 | pub async fn send_and_extract<'de, S, P, D, R>( 32 | service: &mut S, 33 | service_code: u8, 34 | path: P, 35 | data: D, 36 | ) -> Result 37 | where 38 | S: MessageService + ?Sized, 39 | P: Encode + Send + Sync, 40 | D: Encode + Send + Sync, 41 | R: Decode<'de> + 'static, 42 | { 43 | let mr = MessageRequest { 44 | service_code, 45 | path, 46 | data, 47 | }; 48 | let reply: MessageReply = service.send(mr).await?; 49 | reply.expect_service::(service_code + REPLY_MASK)?; 50 | Ok(reply.data) 51 | } 52 | -------------------------------------------------------------------------------- /cip/src/service/request.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /// parameters for Unconnected Send 8 | #[derive(Debug)] 9 | pub struct UnconnectedSend { 10 | pub priority_ticks: u8, 11 | pub timeout_ticks: u8, 12 | /// connection path 13 | pub path: P, 14 | /// request data 15 | pub data: D, 16 | } 17 | 18 | impl UnconnectedSend { 19 | #[inline] 20 | pub fn new(path: P, data: D) -> Self { 21 | Self { 22 | priority_ticks: 0x03, 23 | timeout_ticks: 0xFA, 24 | path, 25 | data, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cip/src/socket.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use byteorder::{BigEndian, ByteOrder}; 8 | use bytes::{BufMut, Bytes}; 9 | use rseip_core::{ 10 | codec::{Encode, Encoder}, 11 | Error, 12 | }; 13 | 14 | pub const AF_INET: i16 = 2; 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 17 | pub struct SocketAddr { 18 | /// big-endian, shall be AF_INET=2 19 | pub sin_family: i16, 20 | /// big-endian 21 | pub sin_port: u16, 22 | /// big-endian 23 | pub sin_addr: u32, 24 | ///big-endian, shall be 0 25 | pub sin_zero: [u8; 8], 26 | } 27 | 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 29 | pub enum SocketType { 30 | /// type id = 0x8000 31 | ToTarget, 32 | /// type id = 0x8001 33 | ToOriginator, 34 | } 35 | 36 | impl SocketType { 37 | /// CIP type id 38 | pub const fn type_id(&self) -> u16 { 39 | match self { 40 | Self::ToTarget => 0x8000, 41 | Self::ToOriginator => 0x8001, 42 | } 43 | } 44 | } 45 | 46 | impl SocketAddr { 47 | /// note unchecked 48 | #[inline] 49 | pub(crate) fn from_bytes(buf: Bytes) -> Result { 50 | let mut addr = SocketAddr { 51 | sin_family: BigEndian::read_i16(&buf[0..2]), 52 | sin_port: BigEndian::read_u16(&buf[2..4]), 53 | sin_addr: BigEndian::read_u32(&buf[4..8]), 54 | sin_zero: Default::default(), 55 | }; 56 | addr.sin_zero.copy_from_slice(&buf[8..16]); 57 | Ok(addr) 58 | } 59 | } 60 | 61 | impl Encode for SocketAddr { 62 | #[inline] 63 | fn encode_by_ref( 64 | &self, 65 | buf: &mut bytes::BytesMut, 66 | _encoder: &mut A, 67 | ) -> Result<(), A::Error> { 68 | buf.put_i16(self.sin_family); 69 | buf.put_u16(self.sin_port); 70 | buf.put_u32(self.sin_addr); 71 | buf.put_slice(&self.sin_zero); 72 | Ok(()) 73 | } 74 | 75 | #[inline(always)] 76 | fn bytes_count(&self) -> usize { 77 | 16 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /cip/src/status.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use core::fmt; 8 | 9 | /// message reply status 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Status { 12 | pub general: u8, 13 | pub extended: Option, 14 | } 15 | 16 | impl Status { 17 | #[inline] 18 | pub fn is_ok(&self) -> bool { 19 | self.general == 0 20 | } 21 | 22 | #[inline] 23 | pub fn is_err(&self) -> bool { 24 | self.general != 0 25 | } 26 | 27 | #[inline] 28 | pub fn is_routing_error(&self) -> bool { 29 | // EIP-CIP-V1-3.3 3.5.5.4 30 | match (self.general, self.extended) { 31 | (1, Some(0x0204)) => true, 32 | (1, Some(0x0311)) => true, 33 | (1, Some(0x0312)) => true, 34 | (1, Some(0x0315)) => true, 35 | (2, _) => true, 36 | (4, _) => true, 37 | _ => false, 38 | } 39 | } 40 | 41 | #[inline] 42 | pub fn into_result(self) -> Result<(), Status> { 43 | if self.general == 0 { 44 | Ok(()) 45 | } else { 46 | Err(self) 47 | } 48 | } 49 | } 50 | 51 | impl fmt::Display for Status { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | write!(f, "CIP general status: {}", self.general)?; 54 | if let Some(v) = self.extended { 55 | write!(f, ", extended status: {}", v)?; 56 | } 57 | #[cfg(feature = "error-explain")] 58 | { 59 | let msg = match self.general { 60 | 0x00 => return Ok(()), 61 | 0x01 => match self.extended { 62 | Some(0x0103) => "Transport class and trigger combination not supported", 63 | Some(0x0204) => "timeout", 64 | Some(0x0205) => "Invalid SocketAddr Info item", 65 | Some(0x0302) => "Network bandwidth not available for data", 66 | Some(0x0311) => "Invalid Port ID specified in the Route_Path field", 67 | Some(0x0312) => "Invalid Node Address specified in the Route_Path field", 68 | Some(0x0315) =>"Invalid segment type in the Route_Path field", 69 | _ => "Connection failure", 70 | }, 71 | 0x02 => "Resource error", // processing unconnected send request 72 | 0x03 => "Bad parameter", 73 | 0x04 => "Request path segment error", 74 | 0x05 => "Request path destination unknown", //Probably instance number is not present 75 | 0x06 => "Partial transfer", 76 | 0x07 => "Connection lost", 77 | 0x08 => "Service not supported", 78 | 0x09 => "Invalid attribute value", 79 | 0x0A => "Attribute list error", 80 | 0x0B => "Already in requested mode/state", 81 | 0x0C => "Object state conflict", 82 | 0x0D => "Object already exists", 83 | 0x0E => "Attribute not settable", 84 | 0x0F => "Privilege violation", 85 | 0x10 => match self.extended { 86 | Some(0x2101) => "Device state conflict: keyswitch position: The requestor is changing force information in HARD RUN mode", 87 | Some(0x2802) => "Device state conflict: Safety Status: Unable to modify Safety Memory in the current controller state", 88 | _ => "Device state conflict", 89 | }, 90 | 0x11 => "Reply data too large", 91 | 0x13 => "Insufficient Request Data: Data too short for expected parameters", 92 | 0x014 => "Attribute not supported", 93 | 0x26 => "The Request Path Size received was shorter or longer than expected", 94 | 0xFF => match self.extended { 95 | Some(0x2104) => "Offset is beyond end of the requested tag", 96 | Some(0x2105) => "Number of Elements extends beyond the end of the requested tag", 97 | Some(0x2107) => "Tag type used in request does not match the data type of the target tag", 98 | _ => "General Error: Unknown", 99 | }, 100 | _ => "General Error: Unknown", 101 | }; 102 | write!(f, "\n\t{}", msg)?; 103 | } 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl std::error::Error for Status {} 109 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rseip-core" 3 | version = "0.1.3" 4 | edition = "2021" 5 | description = "core module for rseip" 6 | license = "MIT" 7 | homepage = "https://github.com/Joylei/eip-rs" 8 | repository = "https://github.com/Joylei/eip-rs.git" 9 | documentation = "https://docs.rs/crate/rseip-core/" 10 | keywords = ["ethernet", "codec", "industry", "eip", "cip"] 11 | categories = ["asynchronous", "hardware-support"] 12 | authors = ["joylei "] 13 | resolver = "2" 14 | 15 | [dependencies] 16 | inlinable_string = { version = "^0.1.14", optional = true } 17 | bytes = "1" 18 | byteorder = { version = "1", optional = true } 19 | smallvec = "1" 20 | 21 | [features] 22 | default = ["feat-inlinable-string", "cip"] 23 | feat-inlinable-string = ["inlinable_string"] 24 | cip = ["byteorder"] 25 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # rseip-core 2 | 3 | core module for `rseip`, please look at [rseip](https://github.com/Joylei/eip-rs) for more information. 4 | 5 | ## License 6 | 7 | MIT -------------------------------------------------------------------------------- /core/src/cip/common_packet.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::{codec::*, hex::AsHex, Error}; 8 | use bytes::{Buf, Bytes, BytesMut}; 9 | use core::{ 10 | marker::PhantomData, 11 | ops::{Deref, DerefMut}, 12 | }; 13 | use smallvec::SmallVec; 14 | 15 | type Array = [CommonPacketItem; 4]; 16 | 17 | /// common packet format 18 | #[derive(Default, Debug)] 19 | pub struct CommonPacket(SmallVec>); 20 | 21 | impl CommonPacket { 22 | /// new object 23 | #[inline] 24 | pub fn new() -> Self { 25 | Self(Default::default()) 26 | } 27 | 28 | /// append an item 29 | #[inline] 30 | pub fn push(&mut self, item: CommonPacketItem) { 31 | self.0.push(item); 32 | } 33 | 34 | /// panic if idx is out of range 35 | #[inline] 36 | pub fn remove(&mut self, idx: usize) -> CommonPacketItem { 37 | self.0.remove(idx) 38 | } 39 | } 40 | 41 | impl Deref for CommonPacket { 42 | type Target = [CommonPacketItem]; 43 | #[inline] 44 | fn deref(&self) -> &Self::Target { 45 | &self.0 46 | } 47 | } 48 | 49 | impl DerefMut for CommonPacket { 50 | #[inline] 51 | fn deref_mut(&mut self) -> &mut Self::Target { 52 | &mut self.0 53 | } 54 | } 55 | 56 | impl From>> for CommonPacket { 57 | #[inline] 58 | fn from(src: Vec>) -> Self { 59 | Self(SmallVec::from_vec(src)) 60 | } 61 | } 62 | 63 | impl IntoIterator for CommonPacket { 64 | type Item = CommonPacketItem; 65 | type IntoIter = crate::iter::IntoIter>; 66 | fn into_iter(self) -> Self::IntoIter { 67 | crate::iter::IntoIter::new(self.0) 68 | } 69 | } 70 | 71 | /// common packet format item 72 | #[derive(Debug)] 73 | pub struct CommonPacketItem { 74 | pub type_code: u16, 75 | pub data: T, 76 | } 77 | 78 | impl CommonPacketItem { 79 | /// null address 80 | #[inline] 81 | pub fn with_null_addr() -> Self { 82 | Self { 83 | type_code: 0, 84 | data: Bytes::from_static(&[0x00, 0x00]), 85 | } 86 | } 87 | 88 | /// unconnected data item 89 | #[inline] 90 | pub fn with_unconnected_data(data: Bytes) -> Self { 91 | Self { 92 | type_code: 0xB2, 93 | data, 94 | } 95 | } 96 | 97 | /// connected data item 98 | #[inline] 99 | pub fn with_connected_data(data: Bytes) -> Self { 100 | Self { 101 | type_code: 0xB1, 102 | data, 103 | } 104 | } 105 | 106 | /// is null address 107 | #[inline] 108 | pub fn is_null_addr(&self) -> bool { 109 | if self.type_code != 0 { 110 | return false; 111 | } 112 | self.data.is_empty() 113 | } 114 | } 115 | 116 | impl CommonPacketItem { 117 | /// ensure current item matches the specified type code 118 | #[inline] 119 | pub fn ensure_type_code(&self, type_code: u16) -> Result<(), E> { 120 | if self.type_code != type_code { 121 | return Err(E::invalid_value( 122 | format_args!("common packet item type {}", self.type_code.as_hex()), 123 | type_code.as_hex(), 124 | )); 125 | } 126 | Ok(()) 127 | } 128 | } 129 | 130 | /// lazy decoder for common packet 131 | pub struct CommonPacketIter<'de, D> { 132 | pub(crate) decoder: D, 133 | offset: u16, 134 | total: u16, 135 | _marker: PhantomData<&'de D>, 136 | } 137 | 138 | impl<'de, D> CommonPacketIter<'de, D> 139 | where 140 | D: Decoder<'de>, 141 | { 142 | #[inline] 143 | pub fn new(mut decoder: D) -> Result { 144 | let item_count: u16 = decoder.decode_any()?; 145 | Ok(Self { 146 | decoder, 147 | total: item_count, 148 | offset: 0, 149 | _marker: Default::default(), 150 | }) 151 | } 152 | 153 | #[inline] 154 | pub fn len(&self) -> usize { 155 | self.total as usize 156 | } 157 | 158 | #[inline] 159 | pub fn is_empty(&self) -> bool { 160 | self.total == 0 161 | } 162 | 163 | #[inline] 164 | fn has_remaining(&self) -> bool { 165 | !(self.total == 0 || self.offset >= self.total) 166 | } 167 | 168 | /// next common packet item, strong typed 169 | #[inline] 170 | pub fn next_typed(&mut self) -> Option, D::Error>> 171 | where 172 | T: Decode<'de> + 'static, 173 | { 174 | self.move_next() 175 | } 176 | 177 | /// next common packet item 178 | #[inline] 179 | pub fn next_item(&mut self) -> Option, D::Error>> { 180 | self.move_next() 181 | } 182 | 183 | /// next common packet item 184 | #[inline] 185 | fn move_next(&mut self) -> Option> 186 | where 187 | T: Decode<'de>, 188 | { 189 | if !self.has_remaining() { 190 | return None; 191 | } 192 | 193 | let res = self.decoder.decode_any(); 194 | if res.is_ok() { 195 | self.offset += 1; 196 | } 197 | Some(res) 198 | } 199 | 200 | /// visit next common packet item 201 | #[inline] 202 | pub fn accept( 203 | &mut self, 204 | expected_type_code: u16, 205 | visitor: V, 206 | ) -> Option, D::Error>> 207 | where 208 | V: Visitor<'de>, 209 | { 210 | if !self.has_remaining() { 211 | return None; 212 | } 213 | let res = 214 | CommonPacketItem::validate_and_decode(&mut self.decoder, expected_type_code, visitor); 215 | if res.is_ok() { 216 | self.offset += 1; 217 | } 218 | Some(res) 219 | } 220 | } 221 | 222 | impl Encode for CommonPacket { 223 | #[inline] 224 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> 225 | where 226 | Self: Sized, 227 | { 228 | debug_assert!(self.len() <= u16::MAX as usize); 229 | encoder.encode(self.len() as u16, buf)?; 230 | for item in self.into_iter() { 231 | encoder.encode(item, buf)?; 232 | } 233 | Ok(()) 234 | } 235 | #[inline] 236 | fn encode_by_ref( 237 | &self, 238 | buf: &mut BytesMut, 239 | encoder: &mut A, 240 | ) -> Result<(), A::Error> { 241 | debug_assert!(self.len() <= u16::MAX as usize); 242 | encoder.encode(self.len() as u16, buf)?; 243 | for item in self.0.iter() { 244 | encoder.encode_by_ref(item, buf)?; 245 | } 246 | Ok(()) 247 | } 248 | 249 | #[inline] 250 | fn bytes_count(&self) -> usize { 251 | let count: usize = self.iter().map(|v| v.bytes_count()).sum(); 252 | count + 2 253 | } 254 | } 255 | 256 | impl CommonPacketItem { 257 | pub fn validate_and_decode<'de, D, V>( 258 | mut decoder: D, 259 | expected_type_code: u16, 260 | visitor: V, 261 | ) -> Result 262 | where 263 | D: Decoder<'de>, 264 | V: Visitor<'de, Value = T>, 265 | { 266 | decoder.ensure_size(4)?; 267 | let type_code = decoder.decode_u16(); 268 | if type_code != expected_type_code { 269 | return Err(Error::invalid_value( 270 | format_args!("common packet type code {:#02x}", type_code), 271 | expected_type_code.as_hex(), 272 | )); 273 | } 274 | let item_length = decoder.decode_u16(); 275 | let data = decoder.decode_sized(item_length as usize, visitor)?; 276 | Ok(Self { type_code, data }) 277 | } 278 | 279 | pub fn decode_with<'de, D, V>(mut decoder: D, visitor: V) -> Result 280 | where 281 | D: Decoder<'de>, 282 | V: Visitor<'de, Value = T>, 283 | { 284 | decoder.ensure_size(4)?; 285 | let type_code = decoder.decode_u16(); 286 | let item_length = decoder.decode_u16(); 287 | let data = decoder.decode_sized(item_length as usize, visitor)?; 288 | Ok(Self { type_code, data }) 289 | } 290 | } 291 | 292 | impl<'de, T: Decode<'de> + 'static> Decode<'de> for CommonPacket { 293 | #[inline] 294 | fn decode(mut decoder: D) -> Result 295 | where 296 | D: Decoder<'de>, 297 | { 298 | let item_count: u16 = decoder.decode_any()?; 299 | let mut cpf = CommonPacket::new(); 300 | for _ in 0..item_count { 301 | cpf.push(decoder.decode_any()?); 302 | } 303 | Ok(cpf) 304 | } 305 | } 306 | 307 | impl<'de> Decode<'de> for CommonPacketItem { 308 | #[inline] 309 | fn decode(mut decoder: D) -> Result 310 | where 311 | D: Decoder<'de>, 312 | { 313 | decoder.ensure_size(4)?; 314 | let type_code = decoder.decode_u16(); 315 | let item_length = decoder.decode_u16() as usize; 316 | decoder.ensure_size(item_length)?; 317 | Ok(Self { 318 | type_code, 319 | data: decoder.buf_mut().copy_to_bytes(item_length), 320 | }) 321 | } 322 | } 323 | 324 | impl<'de, T: Decode<'de> + 'static> Decode<'de> for CommonPacketItem { 325 | #[inline] 326 | fn decode(decoder: D) -> Result 327 | where 328 | D: Decoder<'de>, 329 | { 330 | Self::decode_with(decoder, visitor::any()) 331 | } 332 | } 333 | 334 | impl Encode for CommonPacketItem { 335 | #[inline] 336 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> { 337 | debug_assert!(self.bytes_count() <= u16::MAX as usize); 338 | encoder.encode_u16(self.type_code, buf)?; 339 | encoder.encode_u16(self.data.bytes_count() as u16, buf)?; 340 | encoder.encode(self.data, buf)?; 341 | Ok(()) 342 | } 343 | #[inline] 344 | fn encode_by_ref( 345 | &self, 346 | buf: &mut BytesMut, 347 | encoder: &mut A, 348 | ) -> Result<(), A::Error> { 349 | debug_assert!(self.bytes_count() <= u16::MAX as usize); 350 | encoder.encode_u16(self.type_code, buf)?; 351 | encoder.encode_u16(self.data.bytes_count() as u16, buf)?; 352 | encoder.encode_by_ref(&self.data, buf)?; 353 | Ok(()) 354 | } 355 | #[inline] 356 | fn bytes_count(&self) -> usize { 357 | 4 + self.data.bytes_count() 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /core/src/cip/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod common_packet; 8 | 9 | pub use common_packet::{CommonPacket, CommonPacketItem, CommonPacketIter}; 10 | -------------------------------------------------------------------------------- /core/src/codec/decode.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod impls; 8 | mod little_endian; 9 | pub mod visitor; 10 | 11 | use crate::Error; 12 | use bytes::Buf; 13 | pub use little_endian::LittleEndianDecoder; 14 | pub use visitor::Visitor; 15 | 16 | pub trait Decoder<'de> { 17 | type Error: Error; 18 | type Buf: Buf; 19 | /// inner buffer 20 | fn buf(&self) -> &Self::Buf; 21 | 22 | /// inner buffer 23 | fn buf_mut(&mut self) -> &mut Self::Buf; 24 | 25 | /// check remaining buffer size 26 | #[inline(always)] 27 | fn ensure_size(&self, expected: usize) -> Result<(), Self::Error> { 28 | let actual_len = self.buf().remaining(); 29 | if actual_len < expected { 30 | Err(Self::Error::invalid_length(actual_len, expected)) 31 | } else { 32 | Ok(()) 33 | } 34 | } 35 | 36 | /// decode any type `T` that derive [`Decode`] 37 | #[inline] 38 | fn decode_any(&mut self) -> Result 39 | where 40 | T: Decode<'de>, 41 | Self: Sized, 42 | { 43 | T::decode(self) 44 | } 45 | 46 | /// get bool unchecked 47 | #[inline(always)] 48 | fn decode_bool(&mut self) -> bool { 49 | self.buf_mut().get_u8() != 0 50 | } 51 | 52 | /// get i8 unchecked 53 | #[inline(always)] 54 | fn decode_i8(&mut self) -> i8 { 55 | self.buf_mut().get_i8() 56 | } 57 | 58 | /// get u8 unchecked 59 | #[inline(always)] 60 | fn decode_u8(&mut self) -> u8 { 61 | self.buf_mut().get_u8() 62 | } 63 | 64 | /// get i16 unchecked 65 | #[inline(always)] 66 | fn decode_i16(&mut self) -> i16 { 67 | self.buf_mut().get_i16_le() 68 | } 69 | 70 | /// get u16 unchecked 71 | #[inline(always)] 72 | fn decode_u16(&mut self) -> u16 { 73 | self.buf_mut().get_u16_le() 74 | } 75 | 76 | /// get i32 unchecked 77 | #[inline(always)] 78 | fn decode_i32(&mut self) -> i32 { 79 | self.buf_mut().get_i32_le() 80 | } 81 | 82 | /// get u32 unchecked 83 | #[inline(always)] 84 | fn decode_u32(&mut self) -> u32 { 85 | self.buf_mut().get_u32_le() 86 | } 87 | 88 | /// get i64 unchecked 89 | #[inline(always)] 90 | fn decode_i64(&mut self) -> i64 { 91 | self.buf_mut().get_i64_le() 92 | } 93 | 94 | /// get u64 unchecked 95 | #[inline(always)] 96 | fn decode_u64(&mut self) -> u64 { 97 | self.buf_mut().get_u64_le() 98 | } 99 | 100 | /// get f32 unchecked 101 | #[inline(always)] 102 | fn decode_f32(&mut self) -> f32 { 103 | self.buf_mut().get_f32_le() 104 | } 105 | 106 | /// get f64 unchecked 107 | #[inline(always)] 108 | fn decode_f64(&mut self) -> f64 { 109 | self.buf_mut().get_f64_le() 110 | } 111 | 112 | /// get i128 unchecked 113 | #[inline(always)] 114 | fn decode_i128(&mut self) -> i128 { 115 | self.buf_mut().get_i128_le() 116 | } 117 | 118 | /// get u128 unchecked 119 | #[inline(always)] 120 | fn decode_u128(&mut self) -> u128 { 121 | self.buf_mut().get_u128_le() 122 | } 123 | 124 | #[inline(always)] 125 | fn remaining(&mut self) -> usize { 126 | self.buf().remaining() 127 | } 128 | 129 | #[inline(always)] 130 | fn has_remaining(&mut self) -> bool { 131 | self.buf().has_remaining() 132 | } 133 | 134 | /// decode with a dedicated [`Visitor`]. A [`Visitor`] gives you some context information while decoding. 135 | #[inline] 136 | fn decode_with>(&mut self, visitor: V) -> Result 137 | where 138 | Self: Sized, 139 | { 140 | visitor.visit(self) 141 | } 142 | 143 | /// take specified number of bytes, and decode it with the specified visitor 144 | fn decode_sized>( 145 | &mut self, 146 | size: usize, 147 | visitor: V, 148 | ) -> Result 149 | where 150 | Self: Sized; 151 | } 152 | 153 | pub trait Decode<'de>: Sized { 154 | fn decode(decoder: D) -> Result 155 | where 156 | D: Decoder<'de>; 157 | 158 | #[doc(hidden)] 159 | #[inline] 160 | fn decode_in_place(decoder: D, place: &mut Self) -> Result<(), D::Error> 161 | where 162 | D: Decoder<'de>, 163 | { 164 | *place = Self::decode(decoder)?; 165 | Ok(()) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /core/src/codec/decode/impls.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | #![allow(non_snake_case)] 8 | 9 | use smallvec::SmallVec; 10 | 11 | use super::*; 12 | use core::marker::PhantomData; 13 | use std::mem; 14 | 15 | impl<'de, T: Decoder<'de>> Decoder<'de> for &mut T { 16 | type Error = T::Error; 17 | type Buf = T::Buf; 18 | 19 | #[inline] 20 | fn buf(&self) -> &Self::Buf { 21 | (**self).buf() 22 | } 23 | 24 | #[inline] 25 | fn buf_mut(&mut self) -> &mut Self::Buf { 26 | (**self).buf_mut() 27 | } 28 | 29 | #[inline] 30 | fn decode_sized>( 31 | &mut self, 32 | size: usize, 33 | visitor: V, 34 | ) -> Result 35 | where 36 | Self: Sized, 37 | { 38 | (**self).decode_sized(size, visitor) 39 | } 40 | } 41 | 42 | macro_rules! impl_primitive { 43 | ($ty:ty, $m:tt, $s:tt) => { 44 | impl<'de> Decode<'de> for $ty { 45 | #[inline] 46 | fn decode(mut decoder: D) -> Result 47 | where 48 | D: Decoder<'de>, 49 | { 50 | decoder.ensure_size($s)?; 51 | Ok(decoder.$m()) 52 | } 53 | } 54 | }; 55 | } 56 | 57 | impl_primitive!(bool, decode_bool, 1); 58 | impl_primitive!(i8, decode_i8, 1); 59 | impl_primitive!(u8, decode_u8, 1); 60 | impl_primitive!(i16, decode_i16, 2); 61 | impl_primitive!(u16, decode_u16, 2); 62 | impl_primitive!(i32, decode_i32, 4); 63 | impl_primitive!(u32, decode_u32, 4); 64 | impl_primitive!(i64, decode_i64, 8); 65 | impl_primitive!(u64, decode_u64, 8); 66 | impl_primitive!(f32, decode_f32, 4); 67 | impl_primitive!(f64, decode_f64, 8); 68 | impl_primitive!(i128, decode_i128, 16); 69 | impl_primitive!(u128, decode_u128, 16); 70 | 71 | impl<'de> Decode<'de> for () { 72 | #[inline] 73 | fn decode(_decoder: D) -> Result 74 | where 75 | D: Decoder<'de>, 76 | { 77 | Ok(()) 78 | } 79 | } 80 | 81 | impl<'de, T> Decode<'de> for PhantomData 82 | where 83 | T: 'de, 84 | { 85 | #[inline] 86 | fn decode(_decoder: D) -> Result 87 | where 88 | D: Decoder<'de>, 89 | { 90 | Ok(Default::default()) 91 | } 92 | } 93 | 94 | impl<'de, T> Decode<'de> for Option 95 | where 96 | T: Decode<'de>, 97 | { 98 | #[inline] 99 | fn decode(decoder: D) -> Result 100 | where 101 | D: Decoder<'de>, 102 | { 103 | let v = T::decode(decoder)?; 104 | Ok(Some(v)) 105 | } 106 | } 107 | 108 | impl<'de, T, const N: usize> Decode<'de> for [T; N] 109 | where 110 | T: Decode<'de> + Default, 111 | { 112 | #[inline] 113 | fn decode(mut decoder: D) -> Result 114 | where 115 | D: Decoder<'de>, 116 | { 117 | let mut buffer = mem::MaybeUninit::<[T; N]>::uninit(); 118 | { 119 | let buffer = unsafe { &mut *buffer.as_mut_ptr() }; 120 | for item in buffer.iter_mut() { 121 | if decoder.has_remaining() { 122 | T::decode_in_place(&mut decoder, item)?; 123 | } else { 124 | *item = Default::default(); 125 | } 126 | } 127 | } 128 | Ok(unsafe { buffer.assume_init() }) 129 | } 130 | } 131 | 132 | impl<'de, T> Decode<'de> for Vec 133 | where 134 | T: Decode<'de>, 135 | { 136 | #[inline] 137 | fn decode(mut decoder: D) -> Result 138 | where 139 | D: Decoder<'de>, 140 | { 141 | let mut res = Vec::new(); 142 | while decoder.has_remaining() { 143 | let v = T::decode(&mut decoder)?; 144 | res.push(v); 145 | } 146 | Ok(res) 147 | } 148 | } 149 | 150 | impl<'de, A> Decode<'de> for SmallVec 151 | where 152 | A: smallvec::Array, 153 | A::Item: Decode<'de>, 154 | { 155 | #[inline] 156 | fn decode(mut decoder: D) -> Result 157 | where 158 | D: Decoder<'de>, 159 | { 160 | let mut res = Self::new(); 161 | while decoder.has_remaining() { 162 | let v = A::Item::decode(&mut decoder)?; 163 | res.push(v); 164 | } 165 | Ok(res) 166 | } 167 | } 168 | 169 | macro_rules! impl_tuple { 170 | ($($n:tt $name:ident)+) => { 171 | impl<'de, $($name,)+> Decode<'de> for ($($name,)+) 172 | where 173 | $($name: Decode<'de>,)+ 174 | { 175 | #[inline] 176 | fn decode(mut decoder: D) -> Result 177 | where 178 | D: Decoder<'de>, 179 | { 180 | $( 181 | let $name = decoder.decode_any()?; 182 | )+ 183 | Ok(($($name,)+)) 184 | } 185 | } 186 | } 187 | } 188 | 189 | impl_tuple!(0 T0); 190 | impl_tuple!(0 T0 1 T1); 191 | impl_tuple!(0 T0 1 T1 2 T2); 192 | impl_tuple!(0 T0 1 T1 2 T2 3 T3); 193 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4); 194 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5); 195 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6); 196 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7); 197 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8); 198 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9); 199 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10); 200 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11); 201 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12); 202 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13); 203 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14); 204 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15); 205 | -------------------------------------------------------------------------------- /core/src/codec/decode/little_endian.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::*; 8 | use bytes::Bytes; 9 | use core::marker::PhantomData; 10 | 11 | #[derive(Debug)] 12 | pub struct LittleEndianDecoder { 13 | buf: Bytes, 14 | _marker: PhantomData, 15 | } 16 | 17 | impl LittleEndianDecoder { 18 | pub fn new(buf: Bytes) -> Self { 19 | Self { 20 | buf, 21 | _marker: Default::default(), 22 | } 23 | } 24 | 25 | pub fn into_inner(self) -> Bytes { 26 | self.buf 27 | } 28 | } 29 | 30 | impl<'de, E: Error> Decoder<'de> for LittleEndianDecoder { 31 | type Buf = Bytes; 32 | type Error = E; 33 | 34 | #[inline(always)] 35 | fn buf(&self) -> &Self::Buf { 36 | &self.buf 37 | } 38 | 39 | #[inline(always)] 40 | fn buf_mut(&mut self) -> &mut Self::Buf { 41 | &mut self.buf 42 | } 43 | 44 | #[inline] 45 | fn decode_sized>( 46 | &mut self, 47 | size: usize, 48 | visitor: V, 49 | ) -> Result 50 | where 51 | Self: Sized, 52 | { 53 | self.ensure_size(size)?; 54 | let buf = self.buf.split_to(size); 55 | let decoder = Self::new(buf); 56 | visitor.visit(decoder) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/codec/decode/visitor.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::*; 8 | use crate::Either; 9 | use core::marker::PhantomData; 10 | 11 | pub trait Visitor<'de> { 12 | type Value; 13 | fn visit>(self, decoder: D) -> Result; 14 | 15 | #[inline] 16 | fn map(self, f: F) -> MapVisitor<'de, Self, F, R> 17 | where 18 | Self: Sized, 19 | F: FnOnce(Self::Value) -> R, 20 | { 21 | MapVisitor { 22 | v: self, 23 | f, 24 | _marker: Default::default(), 25 | } 26 | } 27 | 28 | #[inline] 29 | fn and(self, visitor: A) -> AndVisitor<'de, Self, A> 30 | where 31 | Self: Sized, 32 | A: Visitor<'de>, 33 | { 34 | AndVisitor { 35 | a: self, 36 | b: visitor, 37 | _marker: Default::default(), 38 | } 39 | } 40 | 41 | #[inline] 42 | fn or(self, visitor: A) -> EitherVisitor<'de, Self, A> 43 | where 44 | Self: Sized, 45 | A: Visitor<'de>, 46 | { 47 | EitherVisitor { 48 | a: self, 49 | b: visitor, 50 | _marker: Default::default(), 51 | } 52 | } 53 | } 54 | 55 | /// decode any type 56 | #[inline] 57 | pub fn any<'a, R>() -> AnyVisitor<'a, R> { 58 | AnyVisitor(Default::default()) 59 | } 60 | 61 | /// directly returns specified value 62 | #[inline] 63 | pub fn from_value(v: R) -> FromValueVisitor { 64 | FromValueVisitor(v) 65 | } 66 | 67 | #[derive(Debug)] 68 | pub struct FromValueVisitor(R); 69 | 70 | impl<'de, R> Visitor<'de> for FromValueVisitor { 71 | type Value = R; 72 | 73 | #[inline(always)] 74 | fn visit>(self, _decoder: D) -> Result { 75 | Ok(self.0) 76 | } 77 | } 78 | 79 | #[derive(Debug)] 80 | pub struct AnyVisitor<'de, R>(PhantomData<&'de R>); 81 | 82 | impl<'de, R: Decode<'de>> Visitor<'de> for AnyVisitor<'de, R> { 83 | type Value = R; 84 | 85 | #[inline] 86 | fn visit>(self, mut decoder: D) -> Result { 87 | decoder.decode_any() 88 | } 89 | } 90 | 91 | #[derive(Debug)] 92 | pub struct MapVisitor<'de, V, F, R> 93 | where 94 | V: Visitor<'de>, 95 | F: FnOnce(V::Value) -> R, 96 | { 97 | v: V, 98 | f: F, 99 | _marker: PhantomData<&'de R>, 100 | } 101 | 102 | impl<'de, V, F, R> Visitor<'de> for MapVisitor<'de, V, F, R> 103 | where 104 | V: Visitor<'de>, 105 | F: FnOnce(V::Value) -> R, 106 | { 107 | type Value = R; 108 | #[inline] 109 | fn visit>(self, decoder: D) -> Result { 110 | let prev = self.v.visit(decoder)?; 111 | let v = (self.f)(prev); 112 | Ok(v) 113 | } 114 | } 115 | 116 | #[derive(Debug)] 117 | pub struct AndVisitor<'de, A, B> 118 | where 119 | A: Visitor<'de>, 120 | B: Visitor<'de>, 121 | { 122 | a: A, 123 | b: B, 124 | _marker: PhantomData<&'de A>, 125 | } 126 | 127 | impl<'de, A, B> Visitor<'de> for AndVisitor<'de, A, B> 128 | where 129 | A: Visitor<'de>, 130 | B: Visitor<'de>, 131 | { 132 | type Value = (A::Value, B::Value); 133 | #[inline] 134 | fn visit>(self, mut decoder: D) -> Result { 135 | let a = self.a.visit(&mut decoder)?; 136 | let b = self.b.visit(decoder)?; 137 | Ok((a, b)) 138 | } 139 | } 140 | 141 | #[derive(Debug)] 142 | pub struct EitherVisitor<'de, A, B> 143 | where 144 | A: Visitor<'de>, 145 | B: Visitor<'de>, 146 | { 147 | a: A, 148 | b: B, 149 | _marker: PhantomData<&'de A>, 150 | } 151 | 152 | impl<'de, A, B> Visitor<'de> for EitherVisitor<'de, A, B> 153 | where 154 | A: Visitor<'de>, 155 | B: Visitor<'de>, 156 | { 157 | type Value = Either; 158 | #[inline] 159 | fn visit>(self, mut decoder: D) -> Result { 160 | if let Ok(v) = self.a.visit(&mut decoder) { 161 | Ok(Either::Left(v)) 162 | } else { 163 | let v = self.b.visit(decoder)?; 164 | Ok(Either::Right(v)) 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /core/src/codec/encode.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod impls; 8 | mod slice; 9 | 10 | use crate::Error; 11 | use bytes::BytesMut; 12 | pub use slice::SliceContainer; 13 | 14 | pub trait Encoder { 15 | type Error: Error; 16 | 17 | fn encode_bool(&mut self, item: bool, buf: &mut BytesMut) -> Result<(), Self::Error>; 18 | fn encode_i8(&mut self, item: i8, buf: &mut BytesMut) -> Result<(), Self::Error>; 19 | fn encode_u8(&mut self, item: u8, buf: &mut BytesMut) -> Result<(), Self::Error>; 20 | fn encode_i16(&mut self, item: i16, buf: &mut BytesMut) -> Result<(), Self::Error>; 21 | fn encode_u16(&mut self, item: u16, buf: &mut BytesMut) -> Result<(), Self::Error>; 22 | fn encode_i32(&mut self, item: i32, buf: &mut BytesMut) -> Result<(), Self::Error>; 23 | fn encode_u32(&mut self, item: u32, buf: &mut BytesMut) -> Result<(), Self::Error>; 24 | fn encode_i64(&mut self, item: i64, buf: &mut BytesMut) -> Result<(), Self::Error>; 25 | fn encode_u64(&mut self, item: u64, buf: &mut BytesMut) -> Result<(), Self::Error>; 26 | fn encode_f32(&mut self, item: f32, buf: &mut BytesMut) -> Result<(), Self::Error>; 27 | fn encode_f64(&mut self, item: f64, buf: &mut BytesMut) -> Result<(), Self::Error>; 28 | fn encode_i128(&mut self, item: i128, buf: &mut BytesMut) -> Result<(), Self::Error>; 29 | fn encode_u128(&mut self, item: u128, buf: &mut BytesMut) -> Result<(), Self::Error>; 30 | 31 | #[inline] 32 | fn encode(&mut self, item: T, buf: &mut BytesMut) -> Result<(), Self::Error> 33 | where 34 | T: Encode + Sized, 35 | Self: Sized, 36 | { 37 | item.encode(buf, self) 38 | } 39 | 40 | #[inline] 41 | fn encode_by_ref(&mut self, item: &T, buf: &mut BytesMut) -> Result<(), Self::Error> 42 | where 43 | T: Encode + ?Sized, 44 | Self: Sized, 45 | { 46 | item.encode_by_ref(buf, self) 47 | } 48 | } 49 | 50 | pub trait Encode { 51 | /// encode by moved values 52 | #[inline] 53 | fn encode(self, buf: &mut BytesMut, encoder: &mut A) -> Result<(), A::Error> 54 | where 55 | Self: Sized, 56 | { 57 | self.encode_by_ref(buf, encoder) 58 | } 59 | 60 | /// encode by references 61 | fn encode_by_ref( 62 | &self, 63 | buf: &mut BytesMut, 64 | encoder: &mut A, 65 | ) -> Result<(), A::Error>; 66 | 67 | /// be careful to calculate the number of bytes 68 | fn bytes_count(&self) -> usize; 69 | } 70 | -------------------------------------------------------------------------------- /core/src/codec/encode/slice.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::*; 8 | 9 | pub struct SliceContainer<'a, T> { 10 | inner: &'a [T], 11 | bytes_count: Option, 12 | } 13 | 14 | impl<'a, T> SliceContainer<'a, T> { 15 | #[inline] 16 | pub fn new(inner: &'a [T]) -> Self { 17 | Self { 18 | inner, 19 | bytes_count: None, 20 | } 21 | } 22 | 23 | /// fast path to compute number of bytes 24 | #[inline] 25 | pub fn with_bytes_count(mut self, size: usize) -> Self { 26 | self.bytes_count = Some(size); 27 | self 28 | } 29 | } 30 | 31 | impl<'a, T> Encode for SliceContainer<'a, T> 32 | where 33 | T: Encode, 34 | { 35 | #[inline] 36 | fn encode_by_ref( 37 | &self, 38 | buf: &mut BytesMut, 39 | encoder: &mut A, 40 | ) -> Result<(), A::Error> { 41 | for item in self.inner.iter() { 42 | item.encode_by_ref(buf, encoder)?; 43 | } 44 | Ok(()) 45 | } 46 | 47 | #[inline] 48 | fn bytes_count(&self) -> usize { 49 | if let Some(v) = self.bytes_count { 50 | v 51 | } else { 52 | self.inner.iter().map(|v| v.bytes_count()).sum() 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /*! 8 | The library provides `Encode` to encode values, `Decode` to decode values, and `TagValue` to manipulate tag data values. The library already implements `Encode` and `Decode` for some rust types: `bool`,`i8`,`u8`,`i16`,`u16`,`i32`,`u32`,`i64`,`u64`,`f32`,`f64`,`i128`,`u128`,`()`,`Option`,`Tuple`,`Vec`,`[T;N]`,`SmallVec`. For structure type, you need to implement `Encode` and `Decode` by yourself. 9 | */ 10 | 11 | pub mod decode; 12 | pub mod encode; 13 | 14 | pub use decode::*; 15 | pub use encode::*; 16 | 17 | use bytes::{Buf, Bytes}; 18 | 19 | /// take remaining bytes 20 | #[derive(Debug, Clone)] 21 | pub struct BytesHolder(Bytes); 22 | 23 | impl From for Bytes { 24 | #[inline] 25 | fn from(src: BytesHolder) -> Self { 26 | src.0 27 | } 28 | } 29 | 30 | impl<'de> Decode<'de> for BytesHolder { 31 | #[inline] 32 | fn decode(mut decoder: D) -> Result 33 | where 34 | D: Decoder<'de>, 35 | { 36 | let size = decoder.remaining(); 37 | let data = decoder.buf_mut().copy_to_bytes(size); 38 | Ok(Self(data)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/either.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 8 | pub enum Either { 9 | Left(A), 10 | Right(B), 11 | } 12 | 13 | impl Either { 14 | #[inline] 15 | pub fn left(&self) -> Option<&A> { 16 | match self { 17 | Self::Left(ref v) => Some(v), 18 | _ => None, 19 | } 20 | } 21 | 22 | #[inline] 23 | pub fn left_mut(&mut self) -> Option<&mut A> { 24 | match self { 25 | Self::Left(ref mut v) => Some(v), 26 | _ => None, 27 | } 28 | } 29 | 30 | #[inline] 31 | pub fn into_left(self) -> Option { 32 | match self { 33 | Self::Left(v) => Some(v), 34 | _ => None, 35 | } 36 | } 37 | 38 | #[inline] 39 | pub fn right(&self) -> Option<&B> { 40 | match self { 41 | Self::Right(ref v) => Some(v), 42 | _ => None, 43 | } 44 | } 45 | 46 | #[inline] 47 | pub fn right_mut(&mut self) -> Option<&mut B> { 48 | match self { 49 | Self::Right(ref mut v) => Some(v), 50 | _ => None, 51 | } 52 | } 53 | 54 | #[inline] 55 | pub fn into_right(self) -> Option { 56 | match self { 57 | Self::Right(v) => Some(v), 58 | _ => None, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use core::fmt; 8 | pub use std::error::Error as StdError; 9 | use std::io; 10 | 11 | pub trait Error: Sized + StdError + From { 12 | fn with_kind(self, kind: &'static str) -> Self; 13 | 14 | /// Raised when there is general error when decoding a type. 15 | fn custom(msg: T) -> Self; 16 | 17 | /// Raised when receives a type different from what it was expecting. 18 | fn invalid_type(unexp: U, exp: E) -> Self { 19 | Self::custom(format_args!("invalid type: {}, expected {}", unexp, exp)) 20 | } 21 | 22 | /// Raised when receives a value of the right type but that 23 | /// is wrong for some other reason. 24 | fn invalid_value(unexp: U, exp: E) -> Self { 25 | Self::custom(format_args!("invalid value: {}, expected {}", unexp, exp)) 26 | } 27 | 28 | /// Raised when the input data contains too many 29 | /// or too few elements. 30 | fn invalid_length(len: usize, exp: E) -> Self { 31 | Self::custom(format_args!("invalid length: {}, expected {}", len, exp)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/hex.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use core::fmt::{self, Debug, LowerHex}; 8 | 9 | /// print hex 10 | pub struct Hex { 11 | inner: T, 12 | lower: bool, 13 | prefix: bool, 14 | } 15 | 16 | impl Hex { 17 | pub fn new(inner: T) -> Self { 18 | Self { 19 | inner, 20 | lower: true, 21 | prefix: true, 22 | } 23 | } 24 | 25 | /// print lower hex 26 | pub fn lower(mut self, lower: bool) -> Self { 27 | self.lower = lower; 28 | self 29 | } 30 | 31 | /// print prefix `0x` 32 | pub fn prefix(mut self, prefix: bool) -> Self { 33 | self.prefix = prefix; 34 | self 35 | } 36 | } 37 | 38 | impl fmt::Debug for Hex { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | if self.prefix { 41 | write!(f, "0x")?; 42 | } 43 | //Rust 1.26.0 and up 44 | if self.lower { 45 | write!(f, "{:02x?}", self.inner) 46 | } else { 47 | write!(f, "{:02X?}", self.inner) 48 | } 49 | } 50 | } 51 | 52 | impl fmt::Display for Hex { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | Debug::fmt(&self, f) 55 | } 56 | } 57 | 58 | pub trait AsHex { 59 | fn as_hex(&self) -> Hex<&dyn Debug>; 60 | } 61 | 62 | impl AsHex for T { 63 | fn as_hex(&self) -> Hex<&dyn Debug> { 64 | Hex::new(self) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/src/iter.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use smallvec::{Array, SmallVec}; 8 | 9 | /// smallvec IntoIter proxy 10 | pub struct IntoIter(smallvec::IntoIter); 11 | 12 | impl IntoIter { 13 | #[inline] 14 | pub fn new(vec: SmallVec) -> Self { 15 | Self(vec.into_iter()) 16 | } 17 | } 18 | 19 | impl Iterator for IntoIter { 20 | type Item = A::Item; 21 | #[inline] 22 | fn next(&mut self) -> Option { 23 | self.0.next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /*! 8 | # rseip-core 9 | core module for `rseip`, please look at [rseip project](https://github.com/Joylei/eip-rs) for more information. 10 | 11 | ## License 12 | 13 | MIT 14 | */ 15 | 16 | //#![warn(missing_docs)] 17 | #![allow(clippy::match_like_matches_macro)] 18 | 19 | #[cfg_attr(feature = "no_std", macro_use)] 20 | extern crate alloc; 21 | 22 | pub extern crate smallvec; 23 | 24 | #[cfg(feature = "cip")] 25 | pub mod cip; 26 | pub mod codec; 27 | mod either; 28 | mod error; 29 | pub mod hex; 30 | pub mod iter; 31 | mod string; 32 | 33 | pub use either::Either; 34 | pub use error::{Error, StdError}; 35 | pub use string::*; 36 | 37 | /// only for testing, not public visible 38 | #[doc(hidden)] 39 | #[cfg(feature = "cip")] 40 | pub mod tests { 41 | use crate::{ 42 | codec::{Encode, Encoder}, 43 | Error, 44 | }; 45 | use bytes::{BufMut, Bytes, BytesMut}; 46 | use core::fmt::{self, Debug, Display}; 47 | use std::io; 48 | 49 | pub trait EncodeExt: Encode { 50 | fn try_into_bytes(self) -> Result 51 | where 52 | Self: Sized; 53 | } 54 | 55 | impl EncodeExt for T { 56 | fn try_into_bytes(self) -> Result 57 | where 58 | Self: Sized, 59 | { 60 | let mut buf = BytesMut::new(); 61 | self.encode(&mut buf, &mut TestEncoder::default())?; 62 | Ok(buf.freeze()) 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | pub enum CodecError { 68 | Io(io::Error), 69 | Msg(String), 70 | } 71 | 72 | impl Error for CodecError { 73 | fn with_kind(self, _kind: &'static str) -> Self { 74 | self 75 | } 76 | fn custom(msg: T) -> Self { 77 | Self::Msg(msg.to_string()) 78 | } 79 | } 80 | 81 | impl From for CodecError { 82 | fn from(e: io::Error) -> Self { 83 | Self::Io(e) 84 | } 85 | } 86 | 87 | impl std::error::Error for CodecError {} 88 | 89 | impl Display for CodecError { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | match self { 92 | Self::Io(e) => write!(f, "{}", e), 93 | Self::Msg(e) => write!(f, "{}", e), 94 | } 95 | } 96 | } 97 | 98 | #[derive(Debug, Default)] 99 | pub struct TestEncoder {} 100 | 101 | impl Encoder for TestEncoder { 102 | type Error = CodecError; 103 | 104 | fn encode_bool( 105 | &mut self, 106 | item: bool, 107 | buf: &mut bytes::BytesMut, 108 | ) -> Result<(), Self::Error> { 109 | buf.put_u8(if item { 255 } else { 0 }); 110 | Ok(()) 111 | } 112 | 113 | fn encode_i8(&mut self, item: i8, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 114 | buf.put_i8(item); 115 | Ok(()) 116 | } 117 | 118 | fn encode_u8(&mut self, item: u8, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 119 | buf.put_u8(item); 120 | Ok(()) 121 | } 122 | 123 | fn encode_i16(&mut self, item: i16, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 124 | buf.put_i16_le(item); 125 | Ok(()) 126 | } 127 | 128 | fn encode_u16(&mut self, item: u16, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 129 | buf.put_u16_le(item); 130 | Ok(()) 131 | } 132 | 133 | fn encode_i32(&mut self, item: i32, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 134 | buf.put_i32_le(item); 135 | Ok(()) 136 | } 137 | 138 | fn encode_u32(&mut self, item: u32, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 139 | buf.put_u32_le(item); 140 | Ok(()) 141 | } 142 | 143 | fn encode_i64(&mut self, item: i64, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 144 | buf.put_i64_le(item); 145 | Ok(()) 146 | } 147 | 148 | fn encode_u64(&mut self, item: u64, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 149 | buf.put_u64_le(item); 150 | Ok(()) 151 | } 152 | 153 | fn encode_f32(&mut self, item: f32, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 154 | buf.put_f32_le(item); 155 | Ok(()) 156 | } 157 | 158 | fn encode_f64(&mut self, item: f64, buf: &mut bytes::BytesMut) -> Result<(), Self::Error> { 159 | buf.put_f64_le(item); 160 | Ok(()) 161 | } 162 | 163 | fn encode_i128( 164 | &mut self, 165 | item: i128, 166 | buf: &mut bytes::BytesMut, 167 | ) -> Result<(), Self::Error> { 168 | buf.put_i128_le(item); 169 | Ok(()) 170 | } 171 | 172 | fn encode_u128( 173 | &mut self, 174 | item: u128, 175 | buf: &mut bytes::BytesMut, 176 | ) -> Result<(), Self::Error> { 177 | buf.put_u128_le(item); 178 | Ok(()) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /core/src/string.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | pub use alloc::string::String as StdString; 8 | #[cfg(not(feature = "feat-inlinable-string"))] 9 | pub use alloc::string::{String, String as StringExt}; 10 | #[cfg(feature = "feat-inlinable-string")] 11 | pub use inlinable_string::{InlinableString, InlinableString as String, StringExt}; 12 | -------------------------------------------------------------------------------- /eip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rseip-eip" 3 | version = "0.2.0" 4 | edition = "2021" 5 | description = "encapsulation protocol for rseip" 6 | license = "MIT" 7 | homepage = "https://github.com/Joylei/eip-rs" 8 | repository = "https://github.com/Joylei/eip-rs.git" 9 | documentation = "https://docs.rs/crate/rseip-eip/" 10 | keywords = ["ethernet", "codec", "industry", "eip", "cip"] 11 | categories = ["asynchronous", "hardware-support"] 12 | authors = ["joylei "] 13 | resolver = "2" 14 | 15 | [dependencies] 16 | rseip-core = { path = "../core", default-features = false, features = [ 17 | "cip", 18 | ], version = "0.1" } 19 | bytes = "1" 20 | byteorder = "1" 21 | log = "0.4" 22 | tokio = { version = "1", features = ["net", "macros", "io-util", "rt", "time"] } 23 | tokio-util = { version = "0.7", features = ["codec", "net"] } 24 | futures-util = { version = "0.3", features = ["sink"] } 25 | smallvec = "1" 26 | 27 | [features] 28 | default = ["inlinable-string", "error-explain"] 29 | inlinable-string = ["rseip-core/feat-inlinable-string"] 30 | error-explain = [] 31 | -------------------------------------------------------------------------------- /eip/README.md: -------------------------------------------------------------------------------- 1 | # rseip-eip 2 | 3 | Encapsulation protocol for `rseip`, please look at [rseip project](https://github.com/Joylei/eip-rs) for more information. 4 | 5 | ## License 6 | 7 | MIT -------------------------------------------------------------------------------- /eip/src/codec/common_packet.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::{codec::Encoding, CommonPacket, CommonPacketItem, Result}; 8 | use bytes::{BufMut, BytesMut}; 9 | 10 | impl Encoding for CommonPacket { 11 | #[inline] 12 | fn encode(self: CommonPacket, dst: &mut BytesMut) -> Result<()> { 13 | debug_assert!(self.len() > 0 && self.len() <= 4); 14 | dst.put_u16_le(self.len() as u16); 15 | for item in self.into_iter() { 16 | item.encode(dst)?; 17 | } 18 | Ok(()) 19 | } 20 | 21 | #[inline] 22 | fn bytes_count(&self) -> usize { 23 | let count: usize = self.iter().map(|v| v.bytes_count()).sum(); 24 | count + 2 25 | } 26 | } 27 | 28 | impl Encoding for CommonPacketItem { 29 | #[inline] 30 | fn encode(self: CommonPacketItem, dst: &mut BytesMut) -> Result<()> { 31 | let bytes_count = self.bytes_count(); 32 | dst.reserve(bytes_count); 33 | dst.put_u16_le(self.type_code); 34 | debug_assert!(self.data.len() <= u16::MAX as usize); 35 | dst.put_u16_le(self.data.len() as u16); 36 | dst.put_slice(&self.data); 37 | Ok(()) 38 | } 39 | 40 | #[inline] 41 | fn bytes_count(&self) -> usize { 42 | 4 + self.data.len() 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod test { 48 | use bytes::Bytes; 49 | 50 | use super::*; 51 | 52 | #[test] 53 | fn test_common_packet_item() { 54 | let item = CommonPacketItem { 55 | type_code: 0x00, 56 | data: Bytes::from_static(&[0, 0]), 57 | }; 58 | assert_eq!(item.bytes_count(), 6); 59 | let buf = item.try_into_bytes().unwrap(); 60 | assert_eq!(&buf[..], &[0x00, 0x00, 0x02, 0x00, 0x00, 0x00,]); 61 | } 62 | 63 | #[test] 64 | fn test_common_packet() { 65 | let null_addr = CommonPacketItem { 66 | type_code: 0x00, 67 | data: Bytes::from_static(&[0, 0]), 68 | }; 69 | let data_item = CommonPacketItem { 70 | type_code: 0xB2, 71 | data: Bytes::from_static(&[ 72 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 73 | ]), 74 | }; 75 | let cpf = CommonPacket::from(vec![null_addr, data_item]); 76 | assert_eq!(cpf.bytes_count(), 2 + 4 + 2 + 4 + 9); 77 | let buf = cpf.try_into_bytes().unwrap(); 78 | assert_eq!( 79 | &buf[..], 80 | &[ 81 | 0x02, 0x00, // item count 82 | 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // addr item 83 | 0xB2, 0x00, 0x09, 0x00, 1, 2, 3, 4, 5, 6, 7, 8, 9, //data item 84 | ] 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /eip/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod command; 8 | 9 | use crate::{ 10 | consts::*, 11 | error::{eip_error, eip_error_code}, 12 | EncapsulationHeader, EncapsulationPacket, 13 | }; 14 | use byteorder::{ByteOrder, LittleEndian}; 15 | use bytes::{BufMut, Bytes, BytesMut}; 16 | use core::marker::PhantomData; 17 | use rseip_core::{ 18 | codec::{self, Decode, Encode, LittleEndianDecoder}, 19 | Error, 20 | }; 21 | use tokio_util::codec::{Decoder, Encoder}; 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub struct ClientCodec { 25 | _marker: PhantomData, 26 | } 27 | 28 | impl ClientCodec { 29 | pub(crate) fn new() -> Self { 30 | Self { 31 | _marker: Default::default(), 32 | } 33 | } 34 | } 35 | 36 | impl codec::Encoder for ClientCodec { 37 | type Error = E; 38 | 39 | #[inline(always)] 40 | fn encode_bool(&mut self, item: bool, buf: &mut BytesMut) -> Result<(), Self::Error> { 41 | buf.put_u8(if item { 255 } else { 0 }); 42 | Ok(()) 43 | } 44 | 45 | #[inline(always)] 46 | fn encode_i8(&mut self, item: i8, buf: &mut BytesMut) -> Result<(), Self::Error> { 47 | buf.put_i8(item); 48 | Ok(()) 49 | } 50 | 51 | #[inline(always)] 52 | fn encode_u8(&mut self, item: u8, buf: &mut BytesMut) -> Result<(), Self::Error> { 53 | buf.put_u8(item); 54 | Ok(()) 55 | } 56 | 57 | #[inline(always)] 58 | fn encode_i16(&mut self, item: i16, buf: &mut BytesMut) -> Result<(), Self::Error> { 59 | buf.put_i16_le(item); 60 | Ok(()) 61 | } 62 | 63 | #[inline(always)] 64 | fn encode_u16(&mut self, item: u16, buf: &mut BytesMut) -> Result<(), Self::Error> { 65 | buf.put_u16_le(item); 66 | Ok(()) 67 | } 68 | 69 | #[inline(always)] 70 | fn encode_i32(&mut self, item: i32, buf: &mut BytesMut) -> Result<(), Self::Error> { 71 | buf.put_i32_le(item); 72 | Ok(()) 73 | } 74 | 75 | #[inline(always)] 76 | fn encode_u32(&mut self, item: u32, buf: &mut BytesMut) -> Result<(), Self::Error> { 77 | buf.put_u32_le(item); 78 | Ok(()) 79 | } 80 | 81 | #[inline(always)] 82 | fn encode_i64(&mut self, item: i64, buf: &mut BytesMut) -> Result<(), Self::Error> { 83 | buf.put_i64_le(item); 84 | Ok(()) 85 | } 86 | 87 | #[inline(always)] 88 | fn encode_u64(&mut self, item: u64, buf: &mut BytesMut) -> Result<(), Self::Error> { 89 | buf.put_u64_le(item); 90 | Ok(()) 91 | } 92 | 93 | #[inline(always)] 94 | fn encode_f32(&mut self, item: f32, buf: &mut BytesMut) -> Result<(), Self::Error> { 95 | buf.put_f32_le(item); 96 | Ok(()) 97 | } 98 | 99 | #[inline(always)] 100 | fn encode_f64(&mut self, item: f64, buf: &mut BytesMut) -> Result<(), Self::Error> { 101 | buf.put_f64_le(item); 102 | Ok(()) 103 | } 104 | 105 | #[inline(always)] 106 | fn encode_i128(&mut self, item: i128, buf: &mut BytesMut) -> Result<(), Self::Error> { 107 | buf.put_i128_le(item); 108 | Ok(()) 109 | } 110 | 111 | #[inline(always)] 112 | fn encode_u128(&mut self, item: u128, buf: &mut BytesMut) -> Result<(), Self::Error> { 113 | buf.put_u128_le(item); 114 | Ok(()) 115 | } 116 | } 117 | 118 | impl Encoder> for ClientCodec 119 | where 120 | I: codec::Encode + Sized, 121 | E: Error, 122 | { 123 | type Error = E; 124 | #[inline] 125 | fn encode( 126 | &mut self, 127 | item: EncapsulationPacket, 128 | buf: &mut BytesMut, 129 | ) -> Result<(), Self::Error> { 130 | item.encode(buf, self) 131 | } 132 | } 133 | 134 | impl Decoder for ClientCodec { 135 | type Error = E; 136 | type Item = EncapsulationPacket; 137 | #[inline] 138 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 139 | if src.len() < ENCAPSULATION_HEADER_LEN { 140 | return Ok(None); 141 | } 142 | let data_len = LittleEndian::read_u16(&src[2..4]) as usize; 143 | //verify data length 144 | if ENCAPSULATION_HEADER_LEN + data_len > u16::MAX as usize { 145 | return Err(E::invalid_length( 146 | ENCAPSULATION_HEADER_LEN + data_len, 147 | "below u16::MAX", 148 | )); 149 | } 150 | if src.len() < ENCAPSULATION_HEADER_LEN + data_len { 151 | return Ok(None); 152 | } 153 | if src.len() > ENCAPSULATION_HEADER_LEN + data_len { 154 | // should no remaining buffer 155 | return Err(E::invalid_length( 156 | src.len(), 157 | ENCAPSULATION_HEADER_LEN + data_len, 158 | )); 159 | } 160 | let header_bytes = src.split_to(ENCAPSULATION_HEADER_LEN).freeze(); 161 | let decoder = LittleEndianDecoder::::new(header_bytes); 162 | let hdr = EncapsulationHeader::decode(decoder)?; 163 | match hdr.status { 164 | 0 => {} 165 | v if v > u16::MAX as u32 => { 166 | return Err(eip_error(format_args!("invalid status code {:#04x?}", v))); 167 | } 168 | v => return Err(eip_error_code(v as u16)), 169 | } 170 | let data = src.split_to(data_len).freeze(); 171 | Ok(Some(EncapsulationPacket { hdr, data })) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /eip/src/command.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::consts::*; 8 | use rseip_core::codec::Encode; 9 | 10 | /// encapsulation command 11 | pub trait Command: Encode { 12 | fn command_code() -> u16; 13 | } 14 | 15 | /// NOP command 16 | #[derive(Debug, Default)] 17 | pub struct Nop { 18 | pub data: D, 19 | } 20 | 21 | impl Command for Nop { 22 | #[inline(always)] 23 | fn command_code() -> u16 { 24 | EIP_COMMAND_NOP 25 | } 26 | } 27 | 28 | /// List_Identity command 29 | #[derive(Debug)] 30 | pub struct ListIdentity; 31 | 32 | impl Command for ListIdentity { 33 | #[inline(always)] 34 | fn command_code() -> u16 { 35 | EIP_COMMAND_LIST_IDENTITY 36 | } 37 | } 38 | 39 | /// ListInterface command 40 | #[derive(Debug)] 41 | pub struct ListInterfaces; 42 | 43 | impl Command for ListInterfaces { 44 | #[inline(always)] 45 | fn command_code() -> u16 { 46 | EIP_COMMAND_LIST_INTERFACES 47 | } 48 | } 49 | 50 | /// ListService command 51 | #[derive(Debug)] 52 | pub struct ListServices; 53 | 54 | impl Command for ListServices { 55 | #[inline(always)] 56 | fn command_code() -> u16 { 57 | EIP_COMMAND_LIST_SERVICE 58 | } 59 | } 60 | 61 | /// RegisterSession command 62 | #[derive(Debug)] 63 | pub struct RegisterSession; 64 | 65 | impl Command for RegisterSession { 66 | #[inline(always)] 67 | fn command_code() -> u16 { 68 | EIP_COMMAND_REGISTER_SESSION 69 | } 70 | } 71 | 72 | /// UnRegisterSession command 73 | #[derive(Debug)] 74 | pub struct UnRegisterSession { 75 | pub session_handle: u32, 76 | } 77 | 78 | impl Command for UnRegisterSession { 79 | #[inline(always)] 80 | fn command_code() -> u16 { 81 | EIP_COMMAND_UNREGISTER_SESSION 82 | } 83 | } 84 | 85 | /// SendRRData command, for UCMM (unconnected message), sent by originator 86 | #[derive(Debug)] 87 | pub struct SendRRData { 88 | pub session_handle: u32, 89 | /// operation timeout, in seconds; 90 | /// - set to 0, rely on the timeout mechanism of the encapsulated protocol 91 | /// - usually set to 0 for CIP 92 | pub timeout: u16, 93 | /// Data to be Sent via Unconnected Message 94 | pub data: D, 95 | } 96 | 97 | impl Command for SendRRData { 98 | #[inline(always)] 99 | fn command_code() -> u16 { 100 | EIP_COMMAND_SEND_RRDATA 101 | } 102 | } 103 | 104 | /// SendUnitData command, for connected message, sent by either end, no reply 105 | #[derive(Debug)] 106 | pub struct SendUnitData { 107 | pub session_handle: u32, 108 | pub connection_id: u32, 109 | pub sequence_number: u16, 110 | /// Data to be Sent via Connected Message 111 | pub data: D, 112 | } 113 | 114 | impl Command for SendUnitData { 115 | #[inline(always)] 116 | fn command_code() -> u16 { 117 | EIP_COMMAND_SEND_UNIT_DATA 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /eip/src/consts.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /// default port for EtherNet/IP over TCP/IP class 3 8 | pub const EIP_DEFAULT_PORT: u16 = 0xAF12; 9 | /// default port for EtherNet/IP over TCP/IP class 0 and class 1 10 | pub const EIP_DEFAULT_UDP_PORT: u16 = 0x08AE; 11 | 12 | pub const ENCAPSULATION_HEADER_LEN: usize = 24; 13 | pub const ENCAPSULATION_DATA_MAX_LEN: usize = u16::MAX as usize - ENCAPSULATION_HEADER_LEN; 14 | 15 | pub const EIP_COMMAND_NOP: u16 = 0x0000; 16 | pub const EIP_COMMAND_LIST_IDENTITY: u16 = 0x0063; 17 | pub const EIP_COMMAND_LIST_INTERFACES: u16 = 0x0064; 18 | pub const EIP_COMMAND_LIST_SERVICE: u16 = 0x0004; 19 | pub const EIP_COMMAND_REGISTER_SESSION: u16 = 0x0065; 20 | pub const EIP_COMMAND_UNREGISTER_SESSION: u16 = 0x0066; 21 | pub const EIP_COMMAND_SEND_RRDATA: u16 = 0x006F; 22 | pub const EIP_COMMAND_SEND_UNIT_DATA: u16 = 0x0070; 23 | -------------------------------------------------------------------------------- /eip/src/context.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{ 8 | command::{self, Command}, 9 | framed::Framed, 10 | EncapsulationPacket, 11 | }; 12 | use crate::{codec::ClientCodec, *}; 13 | use byteorder::{ByteOrder, LittleEndian}; 14 | use bytes::{BufMut, Bytes, BytesMut}; 15 | use core::fmt; 16 | use futures_util::{SinkExt, StreamExt}; 17 | use rseip_core::{ 18 | cip::CommonPacketIter, 19 | codec::{Encode, LittleEndianDecoder}, 20 | }; 21 | use tokio::io::{AsyncRead, AsyncWrite}; 22 | 23 | pub type CommonPacket<'a, E> = CommonPacketIter<'a, LittleEndianDecoder>; 24 | 25 | /// EIP context 26 | pub struct EipContext { 27 | framed: Framed>, 28 | session_handle: u32, 29 | #[allow(unused)] 30 | sender_context: Bytes, 31 | } 32 | 33 | impl fmt::Debug for EipContext { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | f.debug_struct("EipContext") 36 | .field("session_handle", &self.session_handle) 37 | .field("sender_context", &self.sender_context) 38 | .field("framed", &"") 39 | .finish() 40 | } 41 | } 42 | 43 | impl EipContext { 44 | /// set sender context 45 | #[allow(unused)] 46 | #[inline] 47 | pub fn with_sender_context(&mut self, sender_context: [u8; 8]) -> &mut Self { 48 | let mut buf = BytesMut::new(); 49 | buf.put_slice(&sender_context); 50 | self.sender_context = buf.freeze(); 51 | self 52 | } 53 | 54 | /// current session handle 55 | #[inline] 56 | pub fn session_handle(&self) -> Option { 57 | if self.session_handle > 0 { 58 | Some(self.session_handle) 59 | } else { 60 | None 61 | } 62 | } 63 | 64 | /// session registered? 65 | #[inline] 66 | pub fn has_session(&self) -> bool { 67 | self.session_handle > 0 68 | } 69 | } 70 | 71 | impl EipContext 72 | where 73 | T: AsyncRead + AsyncWrite + Unpin, 74 | E: Error + 'static, 75 | { 76 | /// create [`EipContext`] 77 | #[inline] 78 | pub fn new(transport: T) -> Self { 79 | let framed = Framed::new(transport, ClientCodec::new()); 80 | 81 | Self { 82 | framed, 83 | session_handle: 0, 84 | sender_context: Bytes::from_static(&[0, 0, 0, 0, 0, 0, 0, 0]), 85 | } 86 | } 87 | 88 | /// send and wait for reply 89 | #[inline] 90 | async fn send_and_reply(&mut self, cmd: C) -> Result, E> 91 | where 92 | C: Command, 93 | { 94 | let code = C::command_code(); 95 | log::trace!("send command: {:#0x?}", code); 96 | self.framed.send(cmd).await?; 97 | match self.framed.next().await { 98 | Some(item) => { 99 | let pkt: EncapsulationPacket = item?; 100 | pkt.hdr.ensure_command::(code)?; 101 | Ok(pkt) 102 | } 103 | None => Err(E::custom("transport closed")), 104 | } 105 | } 106 | 107 | /// send command: NOP 108 | #[inline] 109 | pub async fn nop(&mut self, data: D) -> Result<(), E> { 110 | log::trace!("send command: NOP"); 111 | self.framed.send(command::Nop { data }).await?; 112 | Ok(()) 113 | } 114 | 115 | /// send command: ListIdentity 116 | #[allow(unused)] 117 | #[inline] 118 | pub async fn list_identity<'de>(&mut self) -> Result, E> { 119 | let pkt = self.send_and_reply(command::ListIdentity).await?; 120 | let res = CommonPacketIter::new(LittleEndianDecoder::::new(pkt.data))?; 121 | Ok(res) 122 | } 123 | 124 | /// send command: ListServices 125 | #[allow(unused)] 126 | #[inline] 127 | pub async fn list_service<'de>(&mut self) -> Result, E> { 128 | let pkt = self.send_and_reply(command::ListServices).await?; 129 | CommonPacket::new(LittleEndianDecoder::::new(pkt.data)) 130 | } 131 | 132 | /// send command: ListInterface 133 | #[allow(unused)] 134 | #[inline] 135 | pub async fn list_interface<'de>(&mut self) -> Result, E> { 136 | let pkt = self.send_and_reply(command::ListInterfaces).await?; 137 | CommonPacket::new(LittleEndianDecoder::::new(pkt.data)) 138 | } 139 | 140 | /// send command: RegisterSession 141 | #[inline] 142 | pub async fn register_session(&mut self) -> Result { 143 | let pkt = self.send_and_reply(command::RegisterSession).await?; 144 | let session_handle = pkt.hdr.session_handle; 145 | let reply_data = pkt.data; 146 | if reply_data.len() != 4 { 147 | return Err(E::invalid_length(reply_data.len(), 4)); 148 | } 149 | #[cfg(debug_assertions)] 150 | { 151 | let protocol_version = LittleEndian::read_u16(&reply_data[0..2]); 152 | debug_assert_eq!(protocol_version, 1); 153 | let session_options = LittleEndian::read_u16(&reply_data[2..4]); 154 | debug_assert_eq!(session_options, 0); 155 | } 156 | if session_handle == 0 { 157 | return Err(E::invalid_value("session handle 0", ">0")); 158 | } 159 | self.session_handle = session_handle; 160 | Ok(session_handle) 161 | } 162 | 163 | /// send command: UnRegisterSession 164 | #[inline] 165 | pub async fn unregister_session(&mut self) -> Result<(), E> { 166 | if self.session_handle == 0 { 167 | return Ok(()); 168 | } 169 | log::trace!("send command: UnRegisterSession"); 170 | self.framed 171 | .send(command::UnRegisterSession { 172 | session_handle: self.session_handle, 173 | }) 174 | .await?; 175 | Ok(()) 176 | } 177 | 178 | /// send command: SendRRData 179 | #[inline] 180 | pub async fn send_rrdata<'de, D>(&mut self, data: D) -> Result, E> 181 | where 182 | D: Encode, 183 | { 184 | let pkt = self 185 | .send_and_reply(command::SendRRData { 186 | session_handle: self.session_handle, 187 | timeout: 0, 188 | data, 189 | }) 190 | .await?; 191 | let interface_handle = LittleEndian::read_u32(&pkt.data[0..4]); // interface handle 192 | debug_assert_eq!(interface_handle, 0); 193 | // timeout = &pkt.data[4..6] 194 | CommonPacket::new(LittleEndianDecoder::::new(pkt.data.slice(6..))) 195 | } 196 | 197 | /// send command: SendUnitData 198 | #[inline] 199 | pub async fn send_unit_data<'de, D>( 200 | &mut self, 201 | connection_id: u32, 202 | sequence_number: u16, 203 | data: D, 204 | ) -> Result, E> 205 | where 206 | D: Encode, 207 | { 208 | let pkt = self 209 | .send_and_reply(command::SendUnitData { 210 | session_handle: self.session_handle, 211 | sequence_number, 212 | connection_id, 213 | data, 214 | }) 215 | .await?; 216 | let interface_handle = LittleEndian::read_u32(&pkt.data[0..4]); // interface handle 217 | debug_assert_eq!(interface_handle, 0); 218 | // timeout = &pkt.data[4..6] 219 | CommonPacketIter::new(LittleEndianDecoder::::new(pkt.data.slice(6..))) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /eip/src/discover.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::{ 8 | codec::ClientCodec, 9 | command::ListIdentity, 10 | consts::{EIP_COMMAND_LIST_IDENTITY, EIP_DEFAULT_PORT}, 11 | }; 12 | use bytes::Bytes; 13 | use core::marker::PhantomData; 14 | use futures_util::{stream, SinkExt, Stream, StreamExt}; 15 | use rseip_core::{ 16 | cip::{CommonPacketItem, CommonPacketIter}, 17 | codec::{Decode, LittleEndianDecoder}, 18 | Error, 19 | }; 20 | use std::{ 21 | io, 22 | net::{Ipv4Addr, SocketAddr, SocketAddrV4}, 23 | time::Duration, 24 | }; 25 | use tokio::{net::UdpSocket, time}; 26 | use tokio_util::udp::UdpFramed; 27 | 28 | /// device discovery 29 | #[derive(Debug)] 30 | pub struct EipDiscovery { 31 | listen_addr: SocketAddrV4, 32 | broadcast_addr: SocketAddrV4, 33 | times: Option, 34 | interval: Duration, 35 | _marker: PhantomData, 36 | } 37 | 38 | impl EipDiscovery { 39 | /// create [`EipDiscovery`] 40 | #[inline] 41 | pub fn new(listen_addr: Ipv4Addr) -> Self { 42 | Self { 43 | listen_addr: SocketAddrV4::new(listen_addr, 0), 44 | broadcast_addr: SocketAddrV4::new(Ipv4Addr::BROADCAST, EIP_DEFAULT_PORT), 45 | times: Some(1), 46 | interval: Duration::from_secs(1), 47 | _marker: Default::default(), 48 | } 49 | } 50 | 51 | /// set broadcast address 52 | #[inline] 53 | pub fn broadcast(mut self, ip: Ipv4Addr) -> Self { 54 | self.broadcast_addr = SocketAddrV4::new(ip, EIP_DEFAULT_PORT); 55 | self 56 | } 57 | 58 | /// repeatedly send requests with limited times 59 | #[inline] 60 | pub fn repeat(mut self, times: usize) -> Self { 61 | self.times = Some(times); 62 | self 63 | } 64 | 65 | /// repeatedly send requests forever 66 | #[inline] 67 | pub fn forever(mut self) -> Self { 68 | self.times = None; 69 | self 70 | } 71 | 72 | /// request interval 73 | #[inline] 74 | pub fn interval(mut self, interval: Duration) -> Self { 75 | self.interval = interval; 76 | self 77 | } 78 | } 79 | 80 | impl EipDiscovery 81 | where 82 | E: Error + 'static, 83 | { 84 | /// send requests to discover devices 85 | pub async fn run<'de, I>(self) -> io::Result> 86 | where 87 | I: Decode<'de> + 'static, 88 | { 89 | let socket = UdpSocket::bind(self.listen_addr).await?; 90 | socket.set_broadcast(true)?; 91 | let service = UdpFramed::new(socket, ClientCodec::::new()); 92 | let (mut tx, rx) = service.split(); 93 | 94 | let tx_fut = { 95 | let broadcast_addr = self.broadcast_addr; 96 | let interval = self.interval; 97 | let mut times = self.times; 98 | 99 | async move { 100 | let rng = std::iter::from_fn(move || match times { 101 | Some(0) => None, 102 | Some(ref mut v) => { 103 | *v -= 1; 104 | Some(()) 105 | } 106 | None => Some(()), 107 | }); 108 | for _ in rng { 109 | if tx 110 | .send((ListIdentity, broadcast_addr.into())) 111 | .await 112 | .is_err() 113 | { 114 | break; 115 | } 116 | time::sleep(interval).await; 117 | } 118 | } 119 | }; 120 | 121 | let rx = stream::unfold((rx, Box::pin(tx_fut)), |mut state| async move { 122 | loop { 123 | tokio::select! { 124 | res = state.0.next() => { 125 | if let Some(res) =res { 126 | if let Some(v) = res.ok().and_then(|(pkt,addr)| { 127 | if pkt.hdr.command != EIP_COMMAND_LIST_IDENTITY { 128 | None 129 | } else { 130 | decode_identity::<'_,_,E>(pkt.data).ok().flatten().map(|v| (v, addr)) 131 | } 132 | }) { 133 | return Some((v, state)) 134 | } 135 | } else{ 136 | return None; 137 | } 138 | }, 139 | _ = Pin::new(&mut state.1) => { 140 | dbg!("cancel rx"); 141 | return None; 142 | }, 143 | } 144 | } 145 | }); 146 | Ok(rx) 147 | } 148 | } 149 | 150 | #[inline] 151 | fn decode_identity<'de, I, E>(data: Bytes) -> Result, E> 152 | where 153 | I: Decode<'de> + 'static, 154 | E: Error + 'static, 155 | { 156 | let mut cpf = CommonPacketIter::new(LittleEndianDecoder::::new(data))?; 157 | if let Some(item) = cpf.next_typed() { 158 | let item: CommonPacketItem = item?; 159 | item.ensure_type_code::(0x0C)?; 160 | return Ok(Some(item.data)); 161 | } 162 | Ok(None) 163 | } 164 | -------------------------------------------------------------------------------- /eip/src/encapsulation.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::consts::{ENCAPSULATION_DATA_MAX_LEN, ENCAPSULATION_HEADER_LEN}; 8 | use bytes::Buf; 9 | use rseip_core::{ 10 | codec::{Decode, Encode, Encoder}, 11 | hex::AsHex, 12 | Error, 13 | }; 14 | 15 | /// UCMM: 504 bytes 16 | /// max: 65535 17 | #[derive(Debug, Default)] 18 | pub struct EncapsulationPacket { 19 | pub hdr: EncapsulationHeader, 20 | /// max length: 65511 21 | pub data: T, 22 | } 23 | 24 | /// header: 24 bytes 25 | #[derive(Debug, Default)] 26 | pub struct EncapsulationHeader { 27 | pub command: u16, 28 | /// Length, in bytes, of the data portion of the message 29 | pub length: u16, 30 | pub session_handle: u32, 31 | pub status: u32, 32 | pub sender_context: [u8; 8], 33 | /// shall be 0, receiver should ignore the command if not zero 34 | pub options: u32, 35 | } 36 | 37 | impl EncapsulationHeader { 38 | #[inline] 39 | pub fn ensure_command(&self, command_code: u16) -> Result<(), E> { 40 | if self.command != command_code { 41 | return Err(E::invalid_value( 42 | format_args!("command code {:#0x?}", self.command), 43 | command_code.as_hex(), 44 | )); 45 | } 46 | Ok(()) 47 | } 48 | } 49 | 50 | impl Encode for EncapsulationPacket { 51 | #[inline] 52 | fn encode( 53 | mut self, 54 | buf: &mut bytes::BytesMut, 55 | encoder: &mut A, 56 | ) -> Result<(), A::Error> 57 | where 58 | Self: Sized, 59 | { 60 | let data_len = self.data.bytes_count(); 61 | debug_assert!(data_len <= ENCAPSULATION_DATA_MAX_LEN); 62 | 63 | self.hdr.length = data_len as u16; 64 | self.hdr.encode(buf, encoder)?; 65 | self.data.encode(buf, encoder)?; 66 | Ok(()) 67 | } 68 | 69 | #[inline] 70 | fn encode_by_ref( 71 | &self, 72 | buf: &mut bytes::BytesMut, 73 | encoder: &mut A, 74 | ) -> Result<(), A::Error> { 75 | let data_len = self.data.bytes_count(); 76 | debug_assert!(data_len <= ENCAPSULATION_DATA_MAX_LEN); 77 | 78 | //encode hdr 79 | encoder.encode_u16(self.hdr.command, buf)?; 80 | encoder.encode_u16(data_len as u16, buf)?; 81 | encoder.encode_u32(self.hdr.session_handle, buf)?; 82 | encoder.encode_u32(self.hdr.status, buf)?; 83 | self.hdr.sender_context.encode_by_ref(buf, encoder)?; 84 | encoder.encode_u32(self.hdr.options, buf)?; 85 | 86 | //encode data 87 | self.data.encode_by_ref(buf, encoder)?; 88 | Ok(()) 89 | } 90 | 91 | #[inline] 92 | fn bytes_count(&self) -> usize { 93 | ENCAPSULATION_HEADER_LEN + self.data.bytes_count() 94 | } 95 | } 96 | 97 | impl Encode for EncapsulationHeader { 98 | #[inline] 99 | fn encode(self, buf: &mut bytes::BytesMut, encoder: &mut A) -> Result<(), A::Error> 100 | where 101 | Self: Sized, 102 | { 103 | encoder.encode_u16(self.command, buf)?; 104 | encoder.encode_u16(self.length, buf)?; 105 | encoder.encode_u32(self.session_handle, buf)?; 106 | encoder.encode_u32(self.status, buf)?; 107 | self.sender_context.encode_by_ref(buf, encoder)?; 108 | encoder.encode_u32(self.options, buf)?; 109 | Ok(()) 110 | } 111 | 112 | #[inline] 113 | fn encode_by_ref( 114 | &self, 115 | buf: &mut bytes::BytesMut, 116 | encoder: &mut A, 117 | ) -> Result<(), A::Error> { 118 | encoder.encode_u16(self.command, buf)?; 119 | encoder.encode_u16(self.length, buf)?; 120 | encoder.encode_u32(self.session_handle, buf)?; 121 | encoder.encode_u32(self.status, buf)?; 122 | self.sender_context.encode_by_ref(buf, encoder)?; 123 | encoder.encode_u32(self.options, buf)?; 124 | Ok(()) 125 | } 126 | 127 | #[inline(always)] 128 | fn bytes_count(&self) -> usize { 129 | ENCAPSULATION_HEADER_LEN 130 | } 131 | } 132 | 133 | impl<'de> Decode<'de> for EncapsulationHeader { 134 | #[inline] 135 | fn decode(mut decoder: D) -> Result 136 | where 137 | D: rseip_core::codec::Decoder<'de>, 138 | { 139 | decoder.ensure_size(ENCAPSULATION_HEADER_LEN)?; 140 | let hdr = EncapsulationHeader { 141 | command: decoder.decode_u16(), 142 | length: decoder.decode_u16(), 143 | session_handle: decoder.decode_u32(), 144 | status: decoder.decode_u32(), 145 | sender_context: { 146 | let mut dst = [0; 8]; 147 | decoder.buf_mut().copy_to_slice(&mut dst); 148 | dst 149 | }, 150 | options: decoder.decode_u32(), 151 | }; 152 | 153 | Ok(hdr) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /eip/src/error.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use core::fmt; 8 | use rseip_core::Error; 9 | 10 | pub fn eip_error(msg: U) -> E { 11 | E::custom(format_args!("Encapsulation error: {}", msg)) 12 | } 13 | 14 | macro_rules! build_error { 15 | ($err_code:expr) => { 16 | format_args!("Encapsulation error code: {:#04x?}", $err_code) 17 | }; 18 | ($err_code:expr, $detail:tt) => { 19 | format_args!( 20 | "Encapsulation error code: {:#04x?}\n\t{}", 21 | $err_code, $detail 22 | ) 23 | }; 24 | } 25 | 26 | #[cfg(feature = "error-explain")] 27 | pub(crate) fn eip_error_code(err_code: u16) -> E { 28 | let msg = 29 | match err_code { 30 | 0x0001 => "The sender issued an invalid or unsupported encapsulation command", 31 | 0x0002 => "Insufficient memory resources in the receiver to handle the command", 32 | 0x0003 => "Poorly formed or incorrect data in the data portion of the encapsulation message", 33 | 0x0064 => "An originator used an invalid session handle when sending an encapsulation message to the target", 34 | 0x0065 => "The target received a message of invalid length", 35 | 0x0069 => "Unsupported encapsulation protocol revision", 36 | _ => return E::custom(build_error!(err_code)), 37 | }; 38 | E::custom(build_error!(err_code, msg)) 39 | } 40 | 41 | #[cfg(not(feature = "error-explain"))] 42 | pub(crate) fn eip_error_code(err_code: u16) -> E { 43 | E::custom(build_error!(err_code)) 44 | } 45 | -------------------------------------------------------------------------------- /eip/src/framed.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::EncapsulationPacket; 8 | use crate::consts::EIP_COMMAND_NOP; 9 | use bytes::Bytes; 10 | use futures_util::{Sink, Stream}; 11 | use std::{ 12 | io, 13 | pin::Pin, 14 | task::{Context, Poll}, 15 | }; 16 | use tokio::io::{AsyncRead, AsyncWrite}; 17 | use tokio_util::codec::{self, Decoder, Encoder}; 18 | 19 | /// special Framed for EIP, 20 | /// will ignore NOP from received packets 21 | pub(crate) struct Framed { 22 | inner: codec::Framed, 23 | } 24 | 25 | impl Framed 26 | where 27 | T: AsyncRead + AsyncWrite, 28 | { 29 | #[inline] 30 | pub fn new(inner: T, codec: U) -> Self { 31 | Self { 32 | inner: codec::Framed::new(inner, codec), 33 | } 34 | } 35 | } 36 | 37 | impl Stream for Framed 38 | where 39 | T: AsyncRead + Unpin, 40 | U: Decoder>, 41 | { 42 | type Item = Result; 43 | 44 | #[inline] 45 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 46 | let inner = Pin::new(&mut self.get_mut().inner); 47 | match inner.poll_next(cx) { 48 | Poll::Ready(Some(Ok(item))) => { 49 | if item.hdr.command == EIP_COMMAND_NOP { 50 | Poll::Pending 51 | } else { 52 | Poll::Ready(Some(Ok(item))) 53 | } 54 | } 55 | v => v, 56 | } 57 | } 58 | } 59 | 60 | impl Sink for Framed 61 | where 62 | T: AsyncWrite + Unpin, 63 | U: Encoder, 64 | U::Error: From, 65 | { 66 | type Error = U::Error; 67 | 68 | #[inline] 69 | fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 70 | let inner = Pin::new(&mut self.get_mut().inner); 71 | inner.poll_ready(cx) 72 | } 73 | 74 | #[inline] 75 | fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { 76 | let inner = Pin::new(&mut self.get_mut().inner); 77 | inner.start_send(item) 78 | } 79 | 80 | #[inline] 81 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | let inner = Pin::new(&mut self.get_mut().inner); 83 | inner.poll_flush(cx) 84 | } 85 | 86 | #[inline] 87 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 88 | let inner = Pin::new(&mut self.get_mut().inner); 89 | inner.poll_close(cx) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /eip/src/lib.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | //#![warn(missing_docs)] 8 | #![allow(clippy::match_like_matches_macro)] 9 | 10 | mod codec; 11 | mod command; 12 | pub mod consts; 13 | pub(crate) mod context; 14 | mod discover; 15 | pub mod encapsulation; 16 | mod error; 17 | mod framed; 18 | 19 | pub use context::EipContext; 20 | pub use discover::EipDiscovery; 21 | pub use encapsulation::{EncapsulationHeader, EncapsulationPacket}; 22 | pub use rseip_core::{ 23 | cip::{CommonPacket, CommonPacketItem}, 24 | Error, 25 | }; 26 | -------------------------------------------------------------------------------- /examples/ab-list-tag.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | //! list symbol instances 8 | 9 | use anyhow::Result; 10 | use futures_util::StreamExt; 11 | use rseip::precludes::*; 12 | 13 | #[tokio::main] 14 | pub async fn main() -> Result<()> { 15 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 16 | .await? 17 | .with_connection_path(PortSegment::default()); 18 | { 19 | let stream = client.list_tag().call(); 20 | stream 21 | .for_each(|item| async move { 22 | println!("{:?}", item); 23 | }) 24 | .await; 25 | } 26 | client.close().await?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/ab-multiple-service.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::{ 9 | cip::{connection::OpenOptions, MessageReply, MessageRequest, REPLY_MASK}, 10 | client::ab_eip::*, 11 | precludes::*, 12 | }; 13 | 14 | #[tokio::main] 15 | pub async fn main() -> Result<()> { 16 | let mut client = 17 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 18 | let mr = client 19 | .multiple_service() 20 | .push(MessageRequest::new( 21 | SERVICE_READ_TAG, 22 | EPath::parse_tag("test_car1_x")?, 23 | 1_u16, // number of elements to read, u16 24 | )) 25 | .push(MessageRequest::new( 26 | SERVICE_READ_TAG, 27 | EPath::parse_tag("test_car2_x")?, 28 | 1_u16, // number of elements to read, u16 29 | )); 30 | let mut iter = mr.call().await?; 31 | while let Some(item) = iter.next() { 32 | let item: MessageReply> = item?; 33 | assert_eq!(item.reply_service, SERVICE_READ_TAG + REPLY_MASK); 34 | if item.status.is_err() { 35 | println!("error read tag: {}", item.status); 36 | } else { 37 | let value = item.data; 38 | println!("tag value: {:?}", value); 39 | } 40 | } 41 | client.close().await?; 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /examples/ab-program-tag.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::client::ab_eip::*; 9 | use rseip::precludes::*; 10 | 11 | #[tokio::main] 12 | pub async fn main() -> Result<()> { 13 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 14 | .await? 15 | .with_connection_path(PortSegment::default()); 16 | let tag = EPath::parse_tag("proGram:MainProgram.test")?; 17 | println!("read tag..."); 18 | let mut holder: TagValue = client.read_tag(tag.clone()).await?; 19 | println!("tag value: {:?}", holder); 20 | holder.value = !holder.value; 21 | client.write_tag(tag, holder).await?; 22 | println!("write tag - done"); 23 | client.close().await?; 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /examples/ab-read-modify-write.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::client::ab_eip::*; 9 | use rseip::precludes::*; 10 | 11 | #[tokio::main] 12 | pub async fn main() -> Result<()> { 13 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 14 | .await? 15 | .with_connection_path(PortSegment::default()); 16 | let tag = EPath::parse_tag("test_car1_x")?; 17 | let prev_value: TagValue = client.read_tag(tag.clone()).await?; 18 | println!("tag value: {:#02x?}", prev_value); 19 | client 20 | .write_tag( 21 | tag.clone(), 22 | TagValue { 23 | tag_type: TagType::Dint, 24 | value: 0x12_68_72_40_i32, 25 | }, 26 | ) 27 | .await?; 28 | println!("read tag..."); 29 | let prev_value: (TagType, [u8; 4]) = client.read_tag(tag.clone()).await?; 30 | println!("tag value before read_modify_write: {:#02x?}", prev_value); 31 | let req = { 32 | let mut req = ReadModifyWriteRequest::<4>::new().tag(tag.clone()); 33 | req.or_mask_mut()[0] = 0x04; // set bit 2 34 | req.and_mask_mut()[0] = 0xDF; // reset bit 5 35 | req 36 | }; 37 | client.read_modify_write(req).await?; 38 | let value: (TagType, [u8; 4]) = client.read_tag(tag).await?; 39 | println!("value after read_modify_write: {:#02x?}", value); 40 | client.close().await?; 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/ab-read-template.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use futures_util::{future, StreamExt, TryStreamExt}; 9 | use rseip::client::ab_eip::*; 10 | use rseip::precludes::*; 11 | 12 | #[tokio::main] 13 | pub async fn main() -> Result<()> { 14 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 15 | .await? 16 | .with_connection_path(PortSegment::default()); 17 | 18 | let instance_id = 2336; 19 | // here use a known instance_id, please uncomment below line to fetch one from PLC controller. 20 | //let instance_id = first_struct_instance(&mut client).await?.unwrap(); 21 | let template = client.find_template(instance_id).await?; 22 | println!("template instance:\n{:?}", template); 23 | { 24 | let mut req = client.read_template(&template); 25 | let info = req.call().await?; 26 | println!("template definition:\n{:?}", info); 27 | } 28 | client.close().await?; 29 | Ok(()) 30 | } 31 | 32 | #[allow(unused)] 33 | async fn first_struct_instance(client: &mut AbEipClient) -> Result> { 34 | let stream = client.list_tag().call(); 35 | tokio::pin!(stream); 36 | let res = stream 37 | .try_filter_map(|item| future::ready(Ok(item.symbol_type.instance_id()))) 38 | .next() 39 | .await; 40 | match res { 41 | Some(Ok(v)) => Ok(Some(v)), 42 | Some(Err(e)) => Err(e.into()), 43 | None => Ok(None), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/ab-symbol-instance-address.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::client::ab_eip::*; 9 | use rseip::precludes::*; 10 | 11 | #[tokio::main] 12 | pub async fn main() -> Result<()> { 13 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 14 | .await? 15 | .with_connection_path(PortSegment::default()); 16 | // test_car1_x, its instance id is 0x66b9 17 | // see example ab-list-tag for how to fetch symbol instances 18 | let tag = EPath::default() 19 | .with_class(CLASS_SYMBOL) 20 | .with_instance(0x66b9); 21 | println!("read tag..."); 22 | let value: TagValue = client.read_tag(tag.clone()).await?; 23 | println!("tag value: {:?}", value); 24 | client.write_tag(tag, value).await?; 25 | println!("write tag - done"); 26 | client.close().await?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/ab-tag-read-fragmented.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | #![allow(unused)] 8 | 9 | use anyhow::Result; 10 | use bytes::{BufMut, BytesMut}; 11 | use rseip::precludes::*; 12 | use rseip::{client::ab_eip::*, ClientError}; 13 | use rseip_core::codec::{Decoder, LittleEndianDecoder}; 14 | 15 | #[tokio::main] 16 | pub async fn main() -> Result<()> { 17 | let mut client = 18 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 19 | let tag = EPath::parse_tag("test_frag")?; 20 | println!("read tag..."); 21 | let mut buf = BytesMut::new(); 22 | let mut tag_type = None; 23 | loop { 24 | let req = ReadFragmentedRequest::new() 25 | .tag(tag.clone()) 26 | .offset(buf.len() as u16); 27 | let (has_more, value) = client.read_tag_fragmented(req).await.unwrap(); 28 | tag_type = Some(value.tag_type); 29 | let bytes = value.value; 30 | dbg!(bytes.len()); 31 | buf.put_slice(&bytes[..]); 32 | if !has_more { 33 | break; 34 | } 35 | } 36 | println!("buf.len: {}", buf.len()); 37 | let mut decoder = LittleEndianDecoder::::new(buf.freeze()); 38 | let udt: BigUdt = decoder.decode_any()?; 39 | println!("tag type: {:?}", tag_type); 40 | println!("tag value: {:?}", udt); 41 | 42 | client.close().await?; 43 | Ok(()) 44 | } 45 | 46 | const DEFAULT_STRING_CAPACITY: usize = 82; 47 | /// total bytes = 4 + 82 48 | #[derive(Debug, Default)] 49 | struct AbString { 50 | data: String, 51 | } 52 | 53 | impl Encode for AbString { 54 | fn encode_by_ref( 55 | &self, 56 | buf: &mut BytesMut, 57 | encoder: &mut A, 58 | ) -> Result<(), A::Error> { 59 | let mut data = self.data.as_bytes(); 60 | if data.len() > DEFAULT_STRING_CAPACITY { 61 | data = &data[0..DEFAULT_STRING_CAPACITY]; 62 | } 63 | // LEN 64 | buf.put_u16_le(data.len() as u16); 65 | buf.put_u16_le(0); 66 | //DATA 67 | let remaining = DEFAULT_STRING_CAPACITY - data.len(); 68 | buf.put_slice(data); 69 | if remaining > 0 { 70 | buf.put_bytes(0, remaining); 71 | } 72 | Ok(()) 73 | } 74 | 75 | fn bytes_count(&self) -> usize { 76 | 4 + DEFAULT_STRING_CAPACITY 77 | } 78 | } 79 | 80 | impl<'de> Decode<'de> for AbString { 81 | fn decode(mut decoder: D) -> Result 82 | where 83 | D: rseip_core::codec::Decoder<'de>, 84 | { 85 | decoder.ensure_size(4 + DEFAULT_STRING_CAPACITY)?; 86 | let len = decoder.decode_u16() as usize; 87 | let _ = decoder.decode_u16(); 88 | let mut data = [0; DEFAULT_STRING_CAPACITY]; 89 | let mut i = 0; 90 | while i < len { 91 | data[i] = decoder.decode_u8(); 92 | i += 1; 93 | } 94 | Ok(Self { 95 | data: String::from_utf8_lossy(&data[0..len]).to_string(), 96 | }) 97 | } 98 | } 99 | 100 | /// total bytes = (4 + 82) * 16 = 1408 101 | #[derive(Debug, Default)] 102 | struct BigUdt { 103 | member1: AbString, 104 | member2: AbString, 105 | member3: AbString, 106 | member4: AbString, 107 | member5: AbString, 108 | member6: AbString, 109 | member7: AbString, 110 | member8: AbString, 111 | member9: AbString, 112 | member10: AbString, 113 | member11: AbString, 114 | member12: AbString, 115 | member13: AbString, 116 | member14: AbString, 117 | member15: AbString, 118 | member16: AbString, 119 | } 120 | 121 | impl Encode for BigUdt { 122 | fn encode_by_ref( 123 | &self, 124 | buf: &mut BytesMut, 125 | encoder: &mut A, 126 | ) -> Result<(), A::Error> { 127 | self.member1.encode_by_ref(buf, encoder)?; 128 | self.member2.encode_by_ref(buf, encoder)?; 129 | self.member3.encode_by_ref(buf, encoder)?; 130 | self.member4.encode_by_ref(buf, encoder)?; 131 | self.member5.encode_by_ref(buf, encoder)?; 132 | self.member6.encode_by_ref(buf, encoder)?; 133 | self.member8.encode_by_ref(buf, encoder)?; 134 | self.member9.encode_by_ref(buf, encoder)?; 135 | self.member10.encode_by_ref(buf, encoder)?; 136 | self.member11.encode_by_ref(buf, encoder)?; 137 | self.member12.encode_by_ref(buf, encoder)?; 138 | self.member13.encode_by_ref(buf, encoder)?; 139 | self.member14.encode_by_ref(buf, encoder)?; 140 | self.member15.encode_by_ref(buf, encoder)?; 141 | self.member16.encode_by_ref(buf, encoder)?; 142 | Ok(()) 143 | } 144 | 145 | fn bytes_count(&self) -> usize { 146 | 16 * 86 147 | } 148 | } 149 | 150 | impl<'de> Decode<'de> for BigUdt { 151 | fn decode(mut decoder: D) -> Result 152 | where 153 | D: rseip_core::codec::Decoder<'de>, 154 | { 155 | let mut res = Self::default(); 156 | res.member1 = decoder.decode_any()?; 157 | res.member2 = decoder.decode_any()?; 158 | res.member3 = decoder.decode_any()?; 159 | res.member4 = decoder.decode_any()?; 160 | res.member5 = decoder.decode_any()?; 161 | res.member6 = decoder.decode_any()?; 162 | res.member7 = decoder.decode_any()?; 163 | res.member8 = decoder.decode_any()?; 164 | res.member9 = decoder.decode_any()?; 165 | res.member10 = decoder.decode_any()?; 166 | res.member11 = decoder.decode_any()?; 167 | res.member12 = decoder.decode_any()?; 168 | res.member13 = decoder.decode_any()?; 169 | res.member14 = decoder.decode_any()?; 170 | res.member15 = decoder.decode_any()?; 171 | res.member16 = decoder.decode_any()?; 172 | Ok(res) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /examples/ab-tag-rw-connected.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::cip::connection::OpenOptions; 9 | use rseip::client::ab_eip::*; 10 | use rseip::precludes::*; 11 | 12 | #[tokio::main] 13 | pub async fn main() -> Result<()> { 14 | let mut client = 15 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 16 | let tag = EPath::parse_tag("test_car1_x")?; 17 | println!("read tag..."); 18 | let value: TagValue = client.read_tag(tag.clone()).await?; 19 | println!("tag value: {:?}", value); 20 | client.write_tag(tag, value).await?; 21 | println!("write tag - done"); 22 | client.close().await?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/ab-tag-rw.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use rseip::client::ab_eip::*; 9 | use rseip::precludes::*; 10 | 11 | #[tokio::main] 12 | pub async fn main() -> Result<()> { 13 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 14 | .await? 15 | .with_connection_path(PortSegment::default()); 16 | let tag = EPath::parse_tag("test_car1_x")?; 17 | println!("read tag..."); 18 | let value: TagValue = client.read_tag(tag.clone()).await?; 19 | println!("tag value: {:?}", value); 20 | client.write_tag(tag, value).await?; 21 | println!("write tag - done"); 22 | client.close().await?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/eip-discovery.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use futures_util::StreamExt; 9 | use rseip::{cip::identity::IdentityObject, client::EipDiscovery}; 10 | use std::time::Duration; 11 | 12 | #[tokio::main] 13 | pub async fn main() -> Result<()> { 14 | let stream = EipDiscovery::new("192.168.0.22".parse()?) 15 | .repeat(3) 16 | .interval(Duration::from_secs(3)) 17 | .run::() 18 | .await?; 19 | 20 | stream 21 | .for_each(|item| { 22 | println!("{:?}", item); 23 | futures_util::future::ready(()) 24 | }) 25 | .await; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/get-attribute-all.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2023, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use bytes::Buf; 9 | use core::{slice, str}; 10 | use rseip::precludes::*; 11 | use rseip_cip::Revision; 12 | use std::borrow::Cow; 13 | 14 | #[tokio::main] 15 | pub async fn main() -> Result<()> { 16 | let mut client = EipClient::new_host_lookup("192.168.0.83") 17 | .await? 18 | .with_connection_path(PortSegment::default()); 19 | let identity_object_class = 0x01; 20 | let path = EPath::new().with_class(identity_object_class); 21 | // raw bytes 22 | //let value: BytesHolder = client.get_attribute_all(path).await?; 23 | //dbg!(value); 24 | let value: TheIdentity = client.get_attribute_all(path).await?; 25 | dbg!(value); 26 | client.close().await?; 27 | Ok(()) 28 | } 29 | 30 | #[derive(Debug)] 31 | struct TheIdentity<'a> { 32 | /// device manufacturers vendor id 33 | pub vendor_id: u16, 34 | /// device type of product 35 | pub device_type: u16, 36 | /// product code 37 | pub product_code: u16, 38 | /// device revision 39 | pub revision: Revision, 40 | /// current status of device 41 | pub status: u16, 42 | /// serial number of device 43 | pub serial_number: u32, 44 | /// short string 45 | pub product_name: Cow<'a, str>, 46 | } 47 | 48 | impl<'de> Decode<'de> for TheIdentity<'de> { 49 | fn decode(mut decoder: D) -> rseip::StdResult 50 | where 51 | D: rseip_core::codec::Decoder<'de>, 52 | { 53 | let identity = TheIdentity { 54 | vendor_id: decoder.decode_u16(), 55 | device_type: decoder.decode_u16(), 56 | product_code: decoder.decode_u16(), 57 | revision: Revision { 58 | major: decoder.decode_u8(), 59 | minor: decoder.decode_u8(), 60 | }, 61 | status: decoder.decode_u16(), 62 | serial_number: decoder.decode_u32(), 63 | product_name: { 64 | let name_len = decoder.decode_u8(); 65 | decoder.ensure_size(name_len as usize)?; 66 | let data = decoder.buf_mut().copy_to_bytes(name_len as usize); 67 | unsafe { 68 | let buf = data.as_ptr(); 69 | let buf = slice::from_raw_parts(buf, name_len as usize); 70 | let name = str::from_utf8_unchecked(buf); 71 | Cow::from(name) 72 | } 73 | }, 74 | }; 75 | 76 | Ok(identity) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/get-attribute-single.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2023, Joylei 5 | // License: MIT 6 | 7 | use anyhow::Result; 8 | use bytes::Buf; 9 | use core::{slice, str}; 10 | use rseip::precludes::*; 11 | 12 | #[tokio::main] 13 | pub async fn main() -> Result<()> { 14 | let mut client = EipClient::new_host_lookup("192.168.0.83") 15 | .await? 16 | .with_connection_path(PortSegment::default()); 17 | get_device_type(&mut client).await?; 18 | get_product_name(&mut client).await?; 19 | client.close().await?; 20 | Ok(()) 21 | } 22 | 23 | async fn get_device_type(client: &mut EipClient) -> anyhow::Result<()> { 24 | let identity_object_class = 0x01; 25 | let attr_id = 0x02; // device type 26 | let path = EPath::new() 27 | .with_class(identity_object_class) 28 | .with_instance(1) 29 | .with_attribute(attr_id); 30 | // raw bytes 31 | // let value: BytesHolder = client.get_attribute_single(path).await?; 32 | // dbg!(value); 33 | let value: u16 = client.get_attribute_single(path).await?; 34 | println!("device type: {}", value); 35 | Ok(()) 36 | } 37 | 38 | async fn get_product_name(client: &mut EipClient) -> anyhow::Result<()> { 39 | let identity_object_class = 0x01; 40 | let attr_id = 0x07; // product name 41 | let path = EPath::new() 42 | .with_class(identity_object_class) 43 | .with_instance(1) 44 | .with_attribute(attr_id); 45 | // raw bytes 46 | // let value: BytesHolder = client.get_attribute_single(path).await?; 47 | // dbg!(value); 48 | let value: ProductName = client.get_attribute_single(path).await?; 49 | println!("product name: {}", value.0); 50 | Ok(()) 51 | } 52 | 53 | #[derive(Debug)] 54 | struct ProductName(String); 55 | 56 | impl<'de> Decode<'de> for ProductName { 57 | fn decode(mut decoder: D) -> rseip::StdResult 58 | where 59 | D: rseip_core::codec::Decoder<'de>, 60 | { 61 | let name_len = decoder.decode_u8(); 62 | decoder.ensure_size(name_len as usize)?; 63 | let data = decoder.buf_mut().copy_to_bytes(name_len as usize); 64 | let product_name = unsafe { 65 | let buf = data.as_ptr(); 66 | let buf = slice::from_raw_parts(buf, name_len as usize); 67 | let name = str::from_utf8_unchecked(buf); 68 | name.to_string() 69 | }; 70 | 71 | Ok(Self(product_name)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | array_width = 50 -------------------------------------------------------------------------------- /src/adapters/eip.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::*; 8 | use crate::{cip::epath::EPATH_CONNECTION_MANAGER, cip::service::*, ClientError, Result}; 9 | use rseip_cip::codec::decode::message_reply; 10 | use rseip_core::codec::{Decode, Encode}; 11 | use rseip_eip::EipContext; 12 | use tokio::io::{AsyncRead, AsyncWrite}; 13 | 14 | #[async_trait::async_trait] 15 | impl Service for EipContext 16 | where 17 | T: AsyncRead + AsyncWrite + Unpin + Send + Sync, 18 | { 19 | /// context is open? 20 | fn is_open(&self) -> bool { 21 | self.session_handle().is_some() 22 | } 23 | 24 | /// open context 25 | async fn open(&mut self) -> Result<()> { 26 | if !self.has_session() { 27 | self.register_session().await?; 28 | } 29 | Ok(()) 30 | } 31 | 32 | /// close context 33 | async fn close(&mut self) -> Result<()> { 34 | if self.has_session() { 35 | self.unregister_session().await?; 36 | } 37 | Ok(()) 38 | } 39 | 40 | /// send Heartbeat message to keep underline transport alive 41 | #[inline] 42 | async fn heartbeat(&mut self) -> Result<()> { 43 | self.nop(()).await?; 44 | Ok(()) 45 | } 46 | 47 | /// send CIP message request without CIP connection 48 | #[inline] 49 | async fn unconnected_send<'de, CP, P, D, R>( 50 | &mut self, 51 | request: UnconnectedSend>, 52 | ) -> Result 53 | where 54 | CP: Encode + Send + Sync, 55 | P: Encode + Send + Sync, 56 | D: Encode + Send + Sync, 57 | R: MessageReplyInterface + Decode<'de> + 'static, 58 | { 59 | let service_code = request.data.service_code; 60 | 61 | let unconnected_send: MessageRequest<&[u8], _> = MessageRequest { 62 | service_code: SERVICE_UNCONNECTED_SEND, 63 | path: EPATH_CONNECTION_MANAGER, 64 | data: request, 65 | }; 66 | 67 | let cpf = self.send_rrdata(unconnected_send).await?; 68 | let reply: R = message_reply::decode_unconnected_send(cpf)?; 69 | reply.expect_service::(service_code + 0x80)?; 70 | Ok(reply) 71 | } 72 | 73 | /// send CIP message request with CIP explicit messaging connection 74 | #[inline] 75 | async fn connected_send<'de, P, D, R>( 76 | &mut self, 77 | connection_id: u32, 78 | sequence_number: u16, 79 | request: MessageRequest, 80 | ) -> Result 81 | where 82 | P: Encode + Send + Sync, 83 | D: Encode + Send + Sync, 84 | R: MessageReplyInterface + Decode<'de> + 'static, 85 | { 86 | let service_code = request.service_code; 87 | let cpf = self 88 | .send_unit_data(connection_id, sequence_number, request) 89 | .await?; 90 | 91 | let (seq_reply, reply): (_, R) = message_reply::decode_connected_send(cpf)?; 92 | debug_assert_eq!(sequence_number, seq_reply); 93 | reply.expect_service::(service_code + 0x80)?; 94 | Ok(reply) 95 | } 96 | 97 | /// open CIP connection 98 | #[inline] 99 | async fn forward_open

(&mut self, request: OpenOptions

) -> Result 100 | where 101 | P: Encode + Send + Sync, 102 | { 103 | let req: MessageRequest<&[u8], _> = MessageRequest { 104 | service_code: SERVICE_FORWARD_OPEN, 105 | path: EPATH_CONNECTION_MANAGER, 106 | data: request, 107 | }; 108 | 109 | let cpf = self.send_rrdata(req).await?; 110 | let reply: ForwardOpenReply = message_reply::decode_unconnected_send(cpf)?; 111 | Ok(reply) 112 | } 113 | 114 | /// close CIP connection 115 | #[inline] 116 | async fn forward_close

( 117 | &mut self, 118 | request: ForwardCloseRequest

, 119 | ) -> Result 120 | where 121 | P: Encode + Send + Sync, 122 | { 123 | let req: MessageRequest<&[u8], _> = MessageRequest { 124 | service_code: SERVICE_FORWARD_CLOSE, 125 | path: EPATH_CONNECTION_MANAGER, 126 | data: request, 127 | }; 128 | 129 | let cpf = self.send_rrdata(req).await?; 130 | let reply: ForwardCloseReply = message_reply::decode_unconnected_send(cpf)?; 131 | Ok(reply) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/adapters/mod.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod eip; 8 | 9 | use crate::{ 10 | cip::{ 11 | connection::{ForwardCloseReply, ForwardCloseRequest, ForwardOpenReply, OpenOptions}, 12 | service::request::UnconnectedSend, 13 | MessageRequest, 14 | }, 15 | Result, 16 | }; 17 | use rseip_cip::MessageReplyInterface; 18 | use rseip_core::codec::{Decode, Encode}; 19 | 20 | /// abstraction for basic CIP services; 21 | /// different transport protocols derive this trait, eg EIP, DF1 22 | #[async_trait::async_trait] 23 | pub trait Service: Send + Sync { 24 | /// context is open? 25 | fn is_open(&self) -> bool; 26 | 27 | /// open context, eg register session for EIP 28 | async fn open(&mut self) -> Result<()>; 29 | 30 | /// close context, eg unregister session for EIP 31 | async fn close(&mut self) -> Result<()>; 32 | 33 | /// send Nop to keep underline transport alive 34 | async fn heartbeat(&mut self) -> Result<()> { 35 | Ok(()) 36 | } 37 | 38 | /// unconnected send 39 | async fn unconnected_send<'de, CP, P, D, R>( 40 | &mut self, 41 | request: UnconnectedSend>, 42 | ) -> Result 43 | where 44 | CP: Encode + Send + Sync, 45 | P: Encode + Send + Sync, 46 | D: Encode + Send + Sync, 47 | R: MessageReplyInterface + Decode<'de> + 'static; 48 | 49 | /// connected send 50 | async fn connected_send<'de, P, D, R>( 51 | &mut self, 52 | connection_id: u32, 53 | sequence_number: u16, 54 | request: MessageRequest, 55 | ) -> Result 56 | where 57 | P: Encode + Send + Sync, 58 | D: Encode + Send + Sync, 59 | R: MessageReplyInterface + Decode<'de> + 'static; 60 | 61 | /// forward open 62 | async fn forward_open

(&mut self, request: OpenOptions

) -> Result 63 | where 64 | P: Encode + Send + Sync; 65 | 66 | /// forward close 67 | async fn forward_close

( 68 | &mut self, 69 | request: ForwardCloseRequest

, 70 | ) -> Result 71 | where 72 | P: Encode + Send + Sync; 73 | } 74 | -------------------------------------------------------------------------------- /src/client/ab_eip.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | pub(crate) mod interceptor; 8 | mod path; 9 | mod service; 10 | mod symbol; 11 | pub mod template; 12 | pub mod value; 13 | 14 | use super::*; 15 | use futures_util::future::BoxFuture; 16 | pub use path::{PathError, PathParser}; 17 | use rseip_cip::Status; 18 | pub use rseip_eip::EipContext; 19 | pub use service::*; 20 | use std::net::SocketAddrV4; 21 | pub use symbol::{GetInstanceAttributeList, SymbolInstance}; 22 | pub use template::AbTemplateService; 23 | use tokio::net::TcpStream; 24 | pub use value::*; 25 | 26 | pub const CLASS_SYMBOL: u16 = 0x6B; 27 | pub const CLASS_TEMPLATE: u16 = 0x6C; 28 | 29 | pub const SERVICE_READ_TAG: u8 = 0x4C; 30 | pub const SERVICE_WRITE_TAG: u8 = 0x4D; 31 | pub const SERVICE_READ_TAG_FRAGMENTED: u8 = 0x52; 32 | pub const SERVICE_WRITE_TAG_FRAGMENTED: u8 = 0x53; 33 | pub const SERVICE_READ_MODIFY_WRITE_TAG: u8 = 0x4E; 34 | pub const SERVICE_TEMPLATE_READ: u8 = 0x4C; 35 | 36 | pub type EipDiscovery = rseip_eip::EipDiscovery; 37 | 38 | /// AB EIP Client 39 | pub type AbEipClient = Client; 40 | 41 | /// AB EIP Connection 42 | pub type AbEipConnection = Connection; 43 | 44 | /// AB EIP driver 45 | pub struct AbEipDriver; 46 | 47 | impl Driver for AbEipDriver { 48 | type Endpoint = SocketAddrV4; 49 | type Service = EipContext; 50 | 51 | #[inline] 52 | fn build_service(addr: Self::Endpoint) -> BoxFuture<'static, Result> { 53 | EipDriver::build_service(addr) 54 | } 55 | } 56 | 57 | /// has more data 58 | pub trait HasMore { 59 | /// true: has more data to retrieve 60 | fn has_more(&self) -> bool; 61 | } 62 | 63 | impl HasMore for Status { 64 | /// true: has more data to retrieve 65 | #[inline] 66 | fn has_more(&self) -> bool { 67 | self.general == 0x06 68 | } 69 | } 70 | 71 | impl HasMore for MessageReply { 72 | #[inline] 73 | fn has_more(&self) -> bool { 74 | self.status.has_more() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/client/ab_eip/interceptor.rs: -------------------------------------------------------------------------------- 1 | use super::HasMore; 2 | use rseip_cip::{ 3 | codec::decode::message_reply::decode_service_and_status, error::cip_error_status, MessageReply, 4 | MessageReplyInterface, Status, 5 | }; 6 | use rseip_core::codec::{Decode, Decoder}; 7 | 8 | #[derive(Debug)] 9 | pub(crate) struct HasMoreInterceptor(pub MessageReply); 10 | 11 | impl MessageReplyInterface for HasMoreInterceptor { 12 | type Value = T; 13 | 14 | fn reply_service(&self) -> u8 { 15 | self.0.reply_service 16 | } 17 | 18 | fn status(&self) -> &Status { 19 | &self.0.status 20 | } 21 | 22 | fn value(&self) -> &Self::Value { 23 | &self.0.data 24 | } 25 | 26 | fn into_value(self) -> Self::Value { 27 | self.0.data 28 | } 29 | } 30 | 31 | impl<'de, T> Decode<'de> for HasMoreInterceptor 32 | where 33 | T: Decode<'de>, 34 | { 35 | #[inline] 36 | fn decode(mut decoder: D) -> Result 37 | where 38 | D: Decoder<'de>, 39 | { 40 | let (reply_service, status) = decode_service_and_status(&mut decoder)?; 41 | if status.is_err() && !status.has_more() { 42 | return Err(cip_error_status(status)); 43 | } 44 | let data = decoder.decode_any()?; 45 | Ok(Self(MessageReply::new(reply_service, status, data))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/client/ab_eip/path.rs: -------------------------------------------------------------------------------- 1 | use crate::cip::epath::{EPath, Segment}; 2 | use core::fmt; 3 | use core::str; 4 | 5 | pub enum PathError { 6 | Empty, 7 | UnexpectedByte(u8), 8 | SyntaxError, 9 | NumberParseError, 10 | NameTooLong, 11 | NameParseError, 12 | Eof, 13 | } 14 | 15 | impl fmt::Debug for PathError { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | match self { 18 | Self::Empty => write!(f, "input buffer is empty"), 19 | Self::UnexpectedByte(c) => write!(f, "syntax error - unexpected byte: {:#02x?}", c), 20 | Self::SyntaxError => write!(f, "syntax error"), 21 | Self::NumberParseError => write!(f, "syntax error - parse number failure"), 22 | Self::NameTooLong => write!(f, "syntax error - name too long"), 23 | Self::NameParseError => write!(f, "syntax error - parse name failure"), 24 | Self::Eof => write!(f, "syntax error - unexpected end of input buffer"), 25 | } 26 | } 27 | } 28 | 29 | impl fmt::Display for PathError { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | fmt::Debug::fmt(&self, f) 32 | } 33 | } 34 | 35 | impl std::error::Error for PathError {} 36 | 37 | /// AB tag path parser 38 | pub trait PathParser: Sized { 39 | /// parse tag path 40 | fn parse_tag(path: impl AsRef<[u8]>) -> Result; 41 | } 42 | 43 | impl PathParser for EPath { 44 | /// parse tag path 45 | /// 46 | /// tag path examples 47 | /// - `struct_a` 48 | /// - `struct_a.1` 49 | /// - `profile[0,1,257]` 50 | /// - `a.b.c` 51 | /// - `struct_a[1].a.b[1]` 52 | /// - `Program:MainProgram.test` 53 | /// 54 | #[inline] 55 | fn parse_tag(path: impl AsRef<[u8]>) -> Result { 56 | let mut buf = path.as_ref(); 57 | if buf.is_empty() { 58 | return Err(PathError::Empty); 59 | } 60 | let mut res = EPath::default(); 61 | parse_symbol_and_optional_numbers(&mut buf, &mut res, true)?; 62 | while let Some(c) = buf.first() { 63 | match c { 64 | &b'.' => { 65 | buf = &buf[1..]; 66 | // 2 cases 67 | match buf.first() { 68 | Some(c) if is_digit(*c) => { 69 | // case 1: bool member 70 | let num = parse_number(&mut buf)?; 71 | // no remaining 72 | if !buf.is_empty() { 73 | return Err(PathError::SyntaxError); 74 | } 75 | // 0-7 or 0-31 if DWORD 76 | if num >= 32 { 77 | return Err(PathError::SyntaxError); 78 | } 79 | res.push(Segment::Element(num)); 80 | } 81 | Some(_) => { 82 | // case 2 83 | parse_symbol_and_optional_numbers(&mut buf, &mut res, false)?; 84 | } 85 | None => return Err(PathError::Eof), 86 | } 87 | } 88 | c => return Err(PathError::UnexpectedByte(*c)), 89 | } 90 | } 91 | Ok(res) 92 | } 93 | } 94 | 95 | #[inline] 96 | fn parse_symbol_and_optional_numbers( 97 | buf: &mut &[u8], 98 | res: &mut EPath, 99 | allow_colon: bool, 100 | ) -> Result<(), PathError> { 101 | let sym = parse_symbol(buf, allow_colon)?; 102 | res.push(Segment::Symbol(sym.into())); 103 | if let Some(&b'[') = buf.first() { 104 | *buf = &buf[1..]; 105 | parse_numbers(buf, res)?; 106 | } 107 | Ok(()) 108 | } 109 | 110 | #[inline] 111 | fn parse_numbers(buf: &mut &[u8], res: &mut EPath) -> Result<(), PathError> { 112 | let mut count = 0; 113 | loop { 114 | let idx = parse_number(buf)?; 115 | res.push(Segment::Element(idx)); 116 | count += 1; 117 | match buf.first() { 118 | Some(b',') => { 119 | *buf = &buf[1..]; 120 | } 121 | Some(b']') => { 122 | *buf = &buf[1..]; 123 | break; 124 | } 125 | Some(c) => { 126 | return Err(PathError::UnexpectedByte(*c)); 127 | } 128 | _ => return Err(PathError::Eof), 129 | } 130 | } 131 | 132 | if count > 0 { 133 | Ok(()) 134 | } else { 135 | Err(PathError::SyntaxError) 136 | } 137 | } 138 | 139 | #[inline] 140 | fn parse_number(buf: &mut &[u8]) -> Result { 141 | const MAX_LEN: usize = 10; // u32::MAX = 4294967295 142 | check_eof(buf)?; 143 | let digits_buf = take_one_plus(buf, is_digit) 144 | .and_then(|v| if v.len() > MAX_LEN { None } else { Some(v) }) 145 | .ok_or(PathError::NumberParseError)?; 146 | 147 | // safety: all digits 148 | let text = unsafe { str::from_utf8_unchecked(digits_buf) }; 149 | let num = text.parse().map_err(|_| PathError::NumberParseError)?; 150 | Ok(num) 151 | } 152 | 153 | #[inline] 154 | fn parse_symbol<'a>(buf: &'a mut &[u8], allow_colon: bool) -> Result<&'a str, PathError> { 155 | const MAX_LEN: usize = 40; // see 1756-pm020_-en-p.pdf page 12 156 | 157 | //check first byte 158 | buf.first().map_or_else( 159 | || Err(PathError::Eof), 160 | |c| { 161 | if *c == b'_' || is_alphabet(*c) { 162 | Ok(()) 163 | } else { 164 | Err(PathError::NameParseError) 165 | } 166 | }, 167 | )?; 168 | 169 | let name_buf = if allow_colon && has_program(buf) { 170 | let temp = &buf[..]; 171 | *buf = &buf[8..]; 172 | take_one_plus(buf, is_valid_char).map(|v| &temp[..8 + v.len()]) 173 | } else { 174 | take_one_plus(buf, is_valid_char) 175 | }; 176 | let name_buf = name_buf.map_or_else( 177 | || Err(PathError::NameParseError), 178 | |v| { 179 | if v.len() > MAX_LEN { 180 | Err(PathError::NameTooLong) 181 | } else { 182 | Ok(v) 183 | } 184 | }, 185 | )?; 186 | 187 | // safety: all ASCII 188 | let name = unsafe { str::from_utf8_unchecked(name_buf) }; 189 | Ok(name) 190 | } 191 | 192 | // === chars ==== 193 | 194 | /// check program prefix ignore case 195 | #[inline] 196 | fn has_program(buf: &[u8]) -> bool { 197 | if buf.len() >= 8 { 198 | let temp = unsafe { str::from_utf8_unchecked(&buf[..8]) }; 199 | temp.eq_ignore_ascii_case("program:") 200 | } else { 201 | false 202 | } 203 | } 204 | 205 | #[inline] 206 | fn take_one_plus<'a>(buf: &mut &'a [u8], f: impl FnMut(u8) -> bool) -> Option<&'a [u8]> { 207 | if let Some((first, rest)) = split(buf, f) { 208 | *buf = rest; 209 | Some(first) 210 | } else { 211 | None 212 | } 213 | } 214 | 215 | /// split buf if `pred` matches more than one bytes 216 | #[inline] 217 | fn split(buf: &[u8], mut pred: impl FnMut(u8) -> bool) -> Option<(&[u8], &[u8])> { 218 | if !buf.is_empty() { 219 | let mut i = 0; 220 | for c in buf.iter() { 221 | if !pred(*c) { 222 | break; 223 | } 224 | i += 1; 225 | } 226 | if i > 0 { 227 | return Some(buf.split_at(i)); 228 | } 229 | } 230 | None 231 | } 232 | #[inline] 233 | const fn check_eof(buf: &[u8]) -> Result<(), PathError> { 234 | if buf.is_empty() { 235 | Err(PathError::Eof) 236 | } else { 237 | Ok(()) 238 | } 239 | } 240 | 241 | #[inline] 242 | const fn is_valid_char(c: u8) -> bool { 243 | c == b'_' || is_digit(c) || is_alphabet(c) 244 | } 245 | 246 | #[inline] 247 | const fn is_digit(c: u8) -> bool { 248 | c >= b'0' && c <= b'9' 249 | } 250 | 251 | #[inline] 252 | const fn is_alphabet(c: u8) -> bool { 253 | if c >= b'a' && c <= b'z' { 254 | true 255 | } else { 256 | c >= b'A' && c <= b'Z' 257 | } 258 | } 259 | 260 | #[cfg(test)] 261 | mod tests { 262 | use super::*; 263 | 264 | #[test] 265 | fn test_valid_tag_paths() { 266 | let path = EPath::parse_tag("struct_a").unwrap(); 267 | assert_eq!(path, EPath::from_symbol("struct_a")); 268 | 269 | let path = EPath::parse_tag("_under").unwrap(); 270 | assert_eq!(path, EPath::from_symbol("_under")); 271 | 272 | let path = EPath::parse_tag("struct_a.1").unwrap(); 273 | assert_eq!(path, EPath::from_symbol("struct_a").with_element(1)); 274 | 275 | let path = EPath::parse_tag("profile[0,1,257]").unwrap(); 276 | assert_eq!( 277 | path, 278 | EPath::from_symbol("profile") 279 | .with_element(0) 280 | .with_element(1) 281 | .with_element(257) 282 | ); 283 | 284 | let path = EPath::parse_tag("a.b.c").unwrap(); 285 | assert_eq!( 286 | path, 287 | EPath::from_symbol("a").with_symbol("b").with_symbol("c") 288 | ); 289 | 290 | let path = EPath::parse_tag("ProGram:MainProgram.test").unwrap(); 291 | assert_eq!( 292 | path, 293 | EPath::from_symbol("ProGram:MainProgram").with_symbol("test") 294 | ); 295 | 296 | let path = EPath::parse_tag("struct_a[1]._abc.efg[2,3]").unwrap(); 297 | assert_eq!( 298 | path, 299 | EPath::from_symbol("struct_a") 300 | .with_element(1) 301 | .with_symbol("_abc") 302 | .with_symbol("efg") 303 | .with_element(2) 304 | .with_element(3) 305 | ); 306 | } 307 | 308 | #[test] 309 | fn test_invalid_tag_paths() { 310 | let paths = [ 311 | "", 312 | ".", 313 | "[", 314 | "124", 315 | "123534546456565756", 316 | "_abc-", 317 | ".1234", 318 | "[12345]", 319 | "abc[1,]", 320 | "abc[1,3,]", 321 | "abc[1,3", 322 | "abc[1,3,", 323 | "my.heart:on", 324 | ]; 325 | 326 | for item in paths { 327 | let res = EPath::parse_tag(item); 328 | assert!(res.is_err()); 329 | } 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/client/eip.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::*; 8 | use futures_util::future::BoxFuture; 9 | pub use rseip_eip::{consts::*, EipContext}; 10 | use std::{ 11 | borrow::Cow, 12 | net::{SocketAddr, SocketAddrV4}, 13 | }; 14 | use tokio::net::{lookup_host, TcpSocket, TcpStream}; 15 | 16 | pub type EipDiscovery = rseip_eip::EipDiscovery; 17 | 18 | /// Generic EIP Client 19 | pub type EipClient = Client; 20 | 21 | /// Generic EIP Connection 22 | pub type EipConnection = Connection; 23 | 24 | /// Generic EIP driver 25 | pub struct EipDriver; 26 | 27 | impl Driver for EipDriver { 28 | type Endpoint = SocketAddrV4; 29 | type Service = EipContext; 30 | 31 | fn build_service(addr: Self::Endpoint) -> BoxFuture<'static, Result> { 32 | let fut = async move { 33 | let socket = TcpSocket::new_v4()?; 34 | let stream = socket.connect(addr.into()).await?; 35 | let service = EipContext::new(stream); 36 | Ok(service) 37 | }; 38 | Box::pin(fut) 39 | } 40 | } 41 | 42 | impl> Client { 43 | /// create connection from specified host, with default port if port not specified 44 | pub async fn new_host_lookup(host: impl AsRef) -> io::Result { 45 | let addr = resolve_host(host).await?; 46 | Ok(Self::new(addr)) 47 | } 48 | } 49 | 50 | impl> Connection { 51 | /// create connection from specified host, with default port if port not specified 52 | pub async fn new_host_lookup(host: impl AsRef, options: OpenOptions) -> io::Result { 53 | let addr = resolve_host(host).await?; 54 | Ok(Self::new(addr, options)) 55 | } 56 | } 57 | 58 | async fn resolve_host(host: impl AsRef) -> io::Result { 59 | let host: Cow<_> = { 60 | let host = host.as_ref(); 61 | if host.is_empty() { 62 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid host")); 63 | } 64 | if !host.contains(':') { 65 | Cow::Owned(format!("{}:{}", host, EIP_DEFAULT_PORT)) 66 | } else { 67 | host.into() 68 | } 69 | }; 70 | let addr = lookup_host(host.as_ref()) 71 | .await? 72 | .filter_map(|item| match item { 73 | SocketAddr::V4(addr) => Some(addr), 74 | _ => None, 75 | }) 76 | .next(); 77 | if let Some(addr) = addr { 78 | Ok(addr) 79 | } else { 80 | Err(io::Error::new( 81 | io::ErrorKind::InvalidInput, 82 | "dns lookup failure", 83 | )) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::client::ab_eip::PathError; 8 | use core::fmt; 9 | use rseip_core::{Error, String}; 10 | use std::io; 11 | 12 | /// client error 13 | #[derive(Debug)] 14 | pub enum ClientError { 15 | Io { kind: &'static str, err: io::Error }, 16 | Custom { kind: &'static str, msg: String }, 17 | } 18 | 19 | impl ClientError { 20 | pub fn kind(&self) -> &'static str { 21 | match self { 22 | Self::Io { kind, .. } => kind, 23 | Self::Custom { kind, .. } => kind, 24 | } 25 | } 26 | 27 | pub fn kind_mut(&mut self) -> &mut &'static str { 28 | match self { 29 | Self::Io { kind, .. } => kind, 30 | Self::Custom { kind, .. } => kind, 31 | } 32 | } 33 | } 34 | 35 | impl std::error::Error for ClientError {} 36 | 37 | impl fmt::Display for ClientError { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | match self { 40 | Self::Io { kind, err } => write!(f, "{} - {}", kind, err), 41 | Self::Custom { kind, msg } => write!(f, "{} - {}", kind, msg), 42 | } 43 | } 44 | } 45 | 46 | impl Error for ClientError { 47 | fn with_kind(mut self, kind: &'static str) -> Self { 48 | *self.kind_mut() = kind; 49 | self 50 | } 51 | 52 | fn custom(msg: T) -> Self { 53 | Self::Custom { 54 | kind: "custom", 55 | msg: msg.to_string().into(), 56 | } 57 | } 58 | } 59 | 60 | impl From for ClientError { 61 | fn from(e: io::Error) -> Self { 62 | Self::Io { kind: "io", err: e } 63 | } 64 | } 65 | 66 | impl From for ClientError { 67 | fn from(e: PathError) -> Self { 68 | Self::custom(e).with_kind("tag path error") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // rseip 2 | // 3 | // rseip - Ethernet/IP (CIP) in pure Rust. 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /*! 8 | # rseip 9 | 10 | [![crates.io](https://img.shields.io/crates/v/rseip.svg)](https://crates.io/crates/rseip) 11 | [![docs](https://docs.rs/rseip/badge.svg)](https://docs.rs/rseip) 12 | [![build](https://github.com/joylei/eip-rs/workflows/build/badge.svg?branch=main)](https://github.com/joylei/eip-rs/actions?query=workflow%3A%22build%22) 13 | [![license](https://img.shields.io/crates/l/rseip.svg)](https://github.com/joylei/eip-rs/blob/master/LICENSE) 14 | 15 | Ethernet/IP (CIP) client in pure Rust, for generic CIP and AB PLC 16 | 17 | ## Features 18 | 19 | - Pure Rust Library 20 | - Asynchronous 21 | - Prefer static dispatch 22 | - Extensible 23 | - Explicit Messaging (Connected / Unconnected) 24 | - Open Source 25 | 26 | ### Services Supported for AB PLC 27 | 28 | - Read Tag 29 | - Write Tag 30 | - Read Tag Fragmented 31 | - Write Tag Fragmented 32 | - Read Modify Write Tag 33 | - Get Instance Attribute List (list tag) 34 | - Read Template 35 | 36 | ## How to use 37 | 38 | Add `rseip` to your cargo project's dependencies 39 | 40 | ```toml 41 | rseip="0.3" 42 | ``` 43 | 44 | Please find detailed guides and examples from below sections. 45 | 46 | 47 | ## Example 48 | 49 | ### Tag Read/Write for Allen-bradley CompactLogIx device 50 | 51 | ```rust,no_run 52 | use anyhow::Result; 53 | use rseip::client::ab_eip::*; 54 | use rseip::precludes::*; 55 | 56 | #[tokio::main] 57 | pub async fn main() -> Result<()> { 58 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 59 | .await? 60 | .with_connection_path(PortSegment::default()); 61 | let tag = EPath::parse_tag("test_car1_x")?; 62 | println!("read tag..."); 63 | let value: TagValue = client.read_tag(tag.clone()).await?; 64 | println!("tag value: {:?}", value); 65 | client.write_tag(tag, value).await?; 66 | println!("write tag - done"); 67 | client.close().await?; 68 | Ok(()) 69 | } 70 | ``` 71 | 72 | Please find more examples within [examples](https://github.com/Joylei/eip-rs/tree/main/examples). 73 | 74 | ## Guides 75 | ### Quick start 76 | 77 | Add `rseip` to your cargo project's dependencies 78 | 79 | ```toml 80 | rseip="0.3" 81 | ``` 82 | 83 | Then, import modules of `rseip` to your project 84 | ```rust,ignore 85 | use rseip::client::ab_eip::*; 86 | use rseip::precludes::*; 87 | ``` 88 | 89 | Then, create an unconnected client 90 | ```rust,ignore 91 | let mut client = AbEipClient::new_host_lookup("192.168.0.83") 92 | .await? 93 | .with_connection_path(PortSegment::default()); 94 | ``` 95 | 96 | or create a connection 97 | ```rust,ignore 98 | let mut client = 99 | AbEipConnection::new_host_lookup("192.168.0.83", OpenOptions::default()).await?; 100 | ``` 101 | 102 | #### Read from a tag 103 | ```rust,ignore 104 | let tag = EPath::parse_tag("test_car1_x")?; 105 | println!("read tag..."); 106 | let value: TagValue = client.read_tag(tag.clone()).await?; 107 | ``` 108 | #### Write to a tag 109 | ```rust,ignore 110 | let tag = EPath::parse_tag("test_car1_x")?; 111 | let value = TagValue { 112 | tag_type: TagType::Dint, 113 | value: 10_i32, 114 | }; 115 | client.write_tag(tag, value).await?; 116 | println!("write tag - done"); 117 | ``` 118 | 119 | ### About `TagValue`, `Decode`, and `Encode` 120 | 121 | As you may know, there are atomic types, structure types, and array type of tags. The library provides `Encode` to encode values, `Decode` to decode values, and `TagValue` to manipulate tag data values. The library already implements `Encode` and `Decode` for some rust types: `bool`,`i8`,`u8`,`i16`,`u16`,`i32`,`u32`,`i64`,`u64`,`f32`,`f64`,`i128`,`u128`,`()`,`Option`,`Tuple`,`Vec`,`[T;N]`,`SmallVec`. For structure type, you need to implement `Encode` and `Decode` by yourself. 122 | 123 | #### Read 124 | 125 | To get a single value (atomic/structure), and you know the exact mapped type, do like this 126 | ```rust,ignore 127 | let value: TagValue = client.read_tag(tag).await?; 128 | println!("{:?}",value); 129 | ``` 130 | 131 | To get the tag type, and you do not care about the data part, do like this: 132 | ```rust,ignore 133 | let value: TagValue<()> = client.read_tag(tag).await?; 134 | println!("{:?}",value.tag_type); 135 | ``` 136 | 137 | To get the raw bytes whatever the data part holds, do like this: 138 | ```rust,ignore 139 | let value: TagValue = client.read_tag(tag).await?; 140 | ``` 141 | 142 | To iterate values, and you know the exact mapped type, do like this: 143 | ```rust,ignore 144 | let iter: TagValueTypedIter = client.read_tag(tag).await?; 145 | println!("{:?}", iter.tag_type()); 146 | while let Some(res) = iter.next(){ 147 | println!("{:?}", res); 148 | } 149 | ``` 150 | 151 | To iterate values, and you do not know the exact mapped type, do like this: 152 | ```rust,ignore 153 | let iter: TagValueIter = client.read_tag(tag).await?; 154 | println!("{:?}", iter.tag_type()); 155 | let res = iter.next::().unwrap(); 156 | println!("{:?}", res); 157 | let res = iter.next::().unwrap(); 158 | println!("{:?}", res); 159 | let res = iter.next::().unwrap(); 160 | println!("{:?}", res); 161 | ``` 162 | 163 | To read more than 1 elements of an `Array`, do like this: 164 | ```rust,ignore 165 | let value: TagValue> = client.read_tag(tag).await?; 166 | println!("{:?}",value); 167 | ``` 168 | 169 | #### Write 170 | 171 | You must provide the tag type before you write to a tag. Normally, you can retrieve it by reading the tag. For structure type, you cannot reply on or persist the tag type (so called `structure handle`), it might change because it is a calculated value (CRC based). 172 | 173 | To write a single value (atomic/structure), do like this: 174 | ```rust,ignore 175 | let value = TagValue { 176 | tag_type: TagType::Dint, 177 | value: 10_i32, 178 | }; 179 | client.write_tag(tag, value).await?; 180 | ``` 181 | 182 | To write raw bytes, do like this: 183 | ```rust,ignore 184 | let bytes:&[u8] = &[0,1,2,3]; 185 | let value = TagValue { 186 | tag_type: TagType::Dint, 187 | value: bytes, 188 | }; 189 | client.write_tag(tag, value).await?; 190 | ``` 191 | 192 | To write multiple values to an array, do like this: 193 | ```rust,ignore 194 | let items: Vec = ...; 195 | let value = TagValue { 196 | tag_type: TagType::Dint, 197 | value: items, 198 | }; 199 | client.write_tag(tag, value).await?; 200 | ``` 201 | 202 | ### Moreover 203 | 204 | For some reasons, `TagValue` does not work for all type that implements `Encode` or `Decode`. 205 | 206 | But you can work without `TagValue`. You can define your own value holder, as long as it implements `Encode` and `Decode`. 207 | 208 | For simple cases, `Tuple` should be a good option. 209 | ```rust,ignore 210 | let (tag_type,value):(TagType,i32) = client.read_tag(tag).await?; 211 | client.write_tag(tag,(tag_type, 1_u16, value)).await?; 212 | ``` 213 | 214 | ## License 215 | 216 | MIT 217 | 218 | 219 | */ 220 | 221 | //#![warn(missing_docs)] 222 | 223 | #![allow(clippy::match_like_matches_macro)] 224 | 225 | pub extern crate futures_util; 226 | 227 | /// adapters 228 | pub mod adapters; 229 | /// client 230 | pub mod client; 231 | mod error; 232 | 233 | #[doc(inline)] 234 | pub use error::ClientError; 235 | pub use rseip_cip as cip; 236 | /// library result 237 | pub type Result = core::result::Result; 238 | pub use core::result::Result as StdResult; 239 | pub use rseip_core::{ 240 | codec::BytesHolder, 241 | codec::{Decode, Encode}, 242 | Either, String, StringExt, 243 | }; 244 | 245 | /// reexport types for easy usage 246 | pub mod precludes { 247 | pub use crate::{ 248 | cip::{epath::*, service::*}, 249 | client::*, 250 | }; 251 | pub use rseip_core::{ 252 | codec::BytesHolder, 253 | codec::{Decode, Encode}, 254 | }; 255 | } 256 | 257 | #[cfg(test)] 258 | mod test { 259 | use std::future::Future; 260 | 261 | #[allow(unused)] 262 | #[inline] 263 | pub(crate) fn block_on(f: F) 264 | where 265 | F: Future>, 266 | { 267 | let mut builder = tokio::runtime::Builder::new_current_thread(); 268 | builder.enable_all(); 269 | let rt = builder.build().unwrap(); 270 | rt.block_on(f).unwrap(); 271 | } 272 | } 273 | --------------------------------------------------------------------------------